mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-14 11:11:00 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f6264e155 | |||
| 92e9b1365d | |||
| 541a0560e7 | |||
| 89462d6da3 | |||
| b4728d5c5e | |||
| be6f734cfc | |||
| 25ee4eef5a | |||
| 9446132608 | |||
| 91bd1b0b4e | |||
| e568a924ae | |||
| 11f82c8488 | |||
| d0c10e6d5c | |||
| 45ceab7dc7 | |||
| a9ccb1f9b4 | |||
| e7e40a0ded | |||
| 94b14b079b | |||
| 8cd10a6136 | |||
| 0742227c5a | |||
| 1e0cce0fa8 | |||
| b6a19c97b1 | |||
| d97d094a8a | |||
| 975b610d9f | |||
| ad61b6e3c9 | |||
| e78f3cc419 | |||
| a1090cfa5a | |||
| dd48baadc7 | |||
| a3a5c7380d | |||
| 79aaca1a85 | |||
| 8ee1860f12 | |||
| 878ca91689 |
@@ -7,23 +7,54 @@ name: "Download LLVM"
|
|||||||
inputs:
|
inputs:
|
||||||
target:
|
target:
|
||||||
required: true
|
required: true
|
||||||
|
version:
|
||||||
|
description: "LLVM release version (e.g., 'llvm-18.1.8'). If not specified, uses the latest LLVM release."
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
|
- name: detect llvm version from submodule
|
||||||
|
id: detect-version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
if [ -z "${{ inputs.version }}" ]; then
|
||||||
|
# Get the full version from the LLVM submodule.
|
||||||
|
git submodule update --init --recursive
|
||||||
|
cd llvm
|
||||||
|
# Try to get the most recent tag (e.g., "llvmorg-18.1.8")
|
||||||
|
LLVM_TAG=$(git describe --tags --abbrev=0 2>/dev/null)
|
||||||
|
if [ -n "$LLVM_TAG" ]; then
|
||||||
|
echo "Detected LLVM version from submodule: $LLVM_TAG"
|
||||||
|
# Convert "llvmorg-x.y.z" to "llvm-x.y.z"
|
||||||
|
LLVM_VERSION=$(echo "$LLVM_TAG" | sed 's/^llvmorg-/llvm-/')
|
||||||
|
echo "Detected LLVM version from submodule: $LLVM_VERSION"
|
||||||
|
echo "version_prefix=$LLVM_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Could not detect LLVM version from submodule, will use latest release"
|
||||||
|
echo "version_prefix=" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
cd ..
|
||||||
|
else
|
||||||
|
echo "Using explicitly provided version: ${{ inputs.version }}"
|
||||||
|
echo "version_prefix=${{ inputs.version }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- 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 }}
|
||||||
|
version_prefix: ${{ steps.detect-version.outputs.version_prefix }}
|
||||||
with:
|
with:
|
||||||
result-encoding: string
|
result-encoding: string
|
||||||
script: |
|
script: |
|
||||||
let page = 1;
|
let page = 1;
|
||||||
let releases = [];
|
let releases = [];
|
||||||
|
|
||||||
let releasePrefix = "llvm-"
|
|
||||||
let target = process.env.target
|
let target = process.env.target
|
||||||
|
let versionPrefix = process.env.version_prefix
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const res = await github.rest.repos.listReleases({
|
const res = await github.rest.repos.listReleases({
|
||||||
@@ -38,15 +69,31 @@ runs:
|
|||||||
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 llvmRelease;
|
||||||
return release.tag_name.startsWith(releasePrefix);
|
if (versionPrefix) {
|
||||||
});
|
// Search for latest release matching the version prefix
|
||||||
if (llvmLatestRelease){
|
llvmRelease = releases.find(release => {
|
||||||
let asset = llvmLatestRelease.assets.find(asset =>{
|
return release.tag_name.startsWith(versionPrefix);
|
||||||
|
});
|
||||||
|
if (llvmRelease) {
|
||||||
|
core.info(`Found LLVM release matching prefix '${versionPrefix}': ${llvmRelease.tag_name}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Find latest LLVM release
|
||||||
|
llvmRelease = releases.find(release => {
|
||||||
|
return release.tag_name.startsWith('llvm-');
|
||||||
|
});
|
||||||
|
if (llvmRelease) {
|
||||||
|
core.info(`Found latest LLVM version: ${llvmRelease.tag_name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (llvmRelease){
|
||||||
|
let asset = llvmRelease.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 ${llvmRelease.tag_name} (${llvmRelease.html_url})`);
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
return asset.browser_download_url;
|
return asset.browser_download_url;
|
||||||
@@ -55,7 +102,11 @@ runs:
|
|||||||
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.`);
|
if (versionPrefix) {
|
||||||
|
core.setFailed(`No LLVM releases matching prefix '${versionPrefix}' found! Please check the version.`);
|
||||||
|
} else {
|
||||||
|
core.setFailed(`No LLVM releases found! Please release LLVM before running this workflow.`);
|
||||||
|
}
|
||||||
process.exit();
|
process.exit();
|
||||||
|
|
||||||
- name: download
|
- name: download
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir -p solc
|
mkdir -p solc
|
||||||
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.30/${SOLC_NAME}
|
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.31/${SOLC_NAME}
|
||||||
|
|
||||||
- name: Make Solc Executable
|
- name: Make Solc Executable
|
||||||
if: ${{ runner.os == 'Windows' }}
|
if: ${{ runner.os == 'Windows' }}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: Check docs up-to-date
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
paths:
|
||||||
|
- 'book/**'
|
||||||
|
- 'docs/**'
|
||||||
|
- '.gitignore'
|
||||||
|
- 'Makefile'
|
||||||
|
- '.github/workflows/book.yml'
|
||||||
|
pull_request:
|
||||||
|
branches: ["main"]
|
||||||
|
types: [opened, synchronize]
|
||||||
|
paths:
|
||||||
|
- 'book/**'
|
||||||
|
- 'docs/**'
|
||||||
|
- '.gitignore'
|
||||||
|
- 'Makefile'
|
||||||
|
- '.github/workflows/book.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with: { fetch-depth: 0 }
|
||||||
|
|
||||||
|
- name: Install and test the mdBook
|
||||||
|
run: make test-book
|
||||||
|
|
||||||
|
- name: Build book
|
||||||
|
run: mdbook build book
|
||||||
|
|
||||||
|
- name: Build book to tmp
|
||||||
|
run: mdbook build book -d docs-tmp
|
||||||
|
|
||||||
|
- name: Compare with committed docs
|
||||||
|
run: |
|
||||||
|
if ! diff -r docs-tmp docs 2>/dev/null; then
|
||||||
|
echo "docs/ is not up-to-date. Run make test-book and commit the docs/ directory"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -54,11 +54,13 @@ jobs:
|
|||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
builder-arg: gnu
|
builder-arg: gnu
|
||||||
host: macos
|
host: macos
|
||||||
runner: macos-14
|
runner: macos-15
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
builder-arg: gnu
|
builder-arg: gnu
|
||||||
host: macos
|
host: macos
|
||||||
runner: macos-13
|
# `macos-15-intel` will be the last x86_64 `macos` image supported by GHA.
|
||||||
|
# It will be available until Aug. 2027 (see https://github.com/actions/runner-images/issues/13045).
|
||||||
|
runner: macos-15-intel
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
builder-arg: gnu
|
builder-arg: gnu
|
||||||
host: windows
|
host: windows
|
||||||
@@ -80,7 +82,10 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: ${{ matrix.host == 'linux' }}
|
if: ${{ matrix.host == 'linux' }}
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
|
cat /etc/apt/sources.list
|
||||||
|
sudo sed -i 's/jammy/noble/g' /etc/apt/sources.list
|
||||||
|
cat /etc/apt/sources.list
|
||||||
|
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl xz-utils libc6-dev gcc-multilib
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: ${{ matrix.host == 'macos' }}
|
if: ${{ matrix.host == 'macos' }}
|
||||||
@@ -96,10 +101,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo install --locked --force --path crates/llvm-builder
|
cargo install --locked --force --path crates/llvm-builder
|
||||||
|
|
||||||
- name: Clone LLVM
|
|
||||||
run: |
|
|
||||||
revive-llvm --target-env ${{ matrix.builder-arg }} clone
|
|
||||||
|
|
||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: ${{ matrix.target != 'wasm32-unknown-emscripten' }}
|
if: ${{ matrix.target != 'wasm32-unknown-emscripten' }}
|
||||||
run: |
|
run: |
|
||||||
@@ -108,6 +109,7 @@ jobs:
|
|||||||
- name: Build LLVM
|
- name: Build LLVM
|
||||||
if: ${{ matrix.target == 'wasm32-unknown-emscripten' }}
|
if: ${{ matrix.target == 'wasm32-unknown-emscripten' }}
|
||||||
run: |
|
run: |
|
||||||
|
revive-llvm emsdk
|
||||||
source emsdk/emsdk_env.sh
|
source emsdk/emsdk_env.sh
|
||||||
revive-llvm --target-env ${{ matrix.builder-arg }} build --llvm-projects lld
|
revive-llvm --target-env ${{ matrix.builder-arg }} build --llvm-projects lld
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
resolc-universal-apple-darwin_url: ${{ steps.set-output.outputs.resolc-universal-apple-darwin_url }}
|
resolc-universal-apple-darwin_url: ${{ steps.set-output.outputs.resolc-universal-apple-darwin_url }}
|
||||||
resolc-universal-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-universal-apple-darwin_sha }}
|
resolc-universal-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-universal-apple-darwin_sha }}
|
||||||
runs-on: macos-14
|
runs-on: macos-15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
create-release:
|
create-release:
|
||||||
if: startsWith(github.ref_name, 'v')
|
if: startsWith(github.ref_name, 'v')
|
||||||
needs: [check-version-changed, build]
|
needs: [check-version-changed, build]
|
||||||
runs-on: macos-14
|
runs-on: macos-15
|
||||||
environment: tags
|
environment: tags
|
||||||
steps:
|
steps:
|
||||||
- name: Download Artifacts
|
- name: Download Artifacts
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
|
|
||||||
npm-release:
|
npm-release:
|
||||||
needs: [create-release]
|
needs: [create-release]
|
||||||
runs-on: macos-14
|
runs-on: macos-15
|
||||||
environment: tags
|
environment: tags
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -66,10 +66,12 @@ jobs:
|
|||||||
runner: ubuntu-24.04
|
runner: ubuntu-24.04
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
type: native
|
type: native
|
||||||
runner: macos-14
|
runner: macos-15
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
type: native
|
type: native
|
||||||
runner: macos-13
|
# `macos-15-intel` will be the last x86_64 `macos` image supported by GHA.
|
||||||
|
# It will be available until Aug. 2027 (see https://github.com/actions/runner-images/issues/13045).
|
||||||
|
runner: macos-15-intel
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
type: native
|
type: native
|
||||||
runner: windows-2022
|
runner: windows-2022
|
||||||
@@ -175,7 +177,7 @@ jobs:
|
|||||||
- name: Basic Sanity Check
|
- name: Basic Sanity Check
|
||||||
run: |
|
run: |
|
||||||
mkdir -p solc
|
mkdir -p solc
|
||||||
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.30/soljson.js
|
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.31/soljson.js
|
||||||
node -e "
|
node -e "
|
||||||
const soljson = require('solc/soljson');
|
const soljson = require('solc/soljson');
|
||||||
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
|
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ on:
|
|||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
paths:
|
paths:
|
||||||
- 'LLVM.lock'
|
|
||||||
- 'crates/llvm-builder/**'
|
- 'crates/llvm-builder/**'
|
||||||
- '.github/workflows/test-llvm-builder.yml'
|
- '.github/workflows/test-llvm-builder.yml'
|
||||||
paths-ignore:
|
|
||||||
- "**.md"
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
@@ -21,10 +18,12 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
runner: [parity-large, macos-14, windows-2022]
|
runner: [parity-large, macos-15, windows-2022]
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
# without this it will override our rust flags
|
# without this it will override our rust flags
|
||||||
@@ -37,7 +36,7 @@ jobs:
|
|||||||
sudo apt update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
|
sudo apt update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: matrix.runner == 'macos-14'
|
if: matrix.runner == 'macos-15'
|
||||||
run: |
|
run: |
|
||||||
brew install ninja
|
brew install ninja
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
needs: build
|
needs: build
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: ["ubuntu-24.04", "macos-14", "windows-2022"]
|
os: ["ubuntu-24.04", "macos-15", "windows-2022"]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|||||||
+5
-1
@@ -3,12 +3,15 @@
|
|||||||
target-llvm
|
target-llvm
|
||||||
*.dot
|
*.dot
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.zed/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/*.sol
|
/*.sol
|
||||||
/*.yul
|
/*.yul
|
||||||
/*.ll
|
/*.ll
|
||||||
/*.s
|
/*.s
|
||||||
/llvm*
|
/llvm-*
|
||||||
|
# Allow llvm submodule directory
|
||||||
|
!/llvm
|
||||||
node_modules
|
node_modules
|
||||||
artifacts
|
artifacts
|
||||||
tmp
|
tmp
|
||||||
@@ -19,3 +22,4 @@ test-results
|
|||||||
playwright-report
|
playwright-report
|
||||||
.cache
|
.cache
|
||||||
emsdk
|
emsdk
|
||||||
|
docs-tmp
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "llvm"]
|
||||||
|
path = llvm
|
||||||
|
url = https://github.com/llvm/llvm-project.git
|
||||||
|
branch = release/18.x
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# This is no longer in .gitignore; we add it here (honored by tools like rg and fd).
|
||||||
|
llvm/
|
||||||
|
docs/
|
||||||
+18
-1
@@ -4,7 +4,24 @@
|
|||||||
|
|
||||||
This is a development pre-release.
|
This is a development pre-release.
|
||||||
|
|
||||||
Supported `polkadot-sdk` rev: `2509.0.0`
|
Supported `polkadot-sdk` rev: `unstable2507`
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- The comprehensive revive compiler book documentation page: https://paritytech.github.io/revive/
|
||||||
|
- Support for solc v0.8.31.
|
||||||
|
- Support for the `clz` Yul builtin.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Instruct the LLVM backend and linker to `--relax` (may lead to smaller contract code size).
|
||||||
|
- Standard JSON mode: Don't forward EVM bytecode related output selections to solc.
|
||||||
|
- The supported `polkadot-sdk` release is `unstable2507`.
|
||||||
|
- The `INVALID` opcode and OOB memory accesses now consume all remaining gas.
|
||||||
|
- Emit the `call_evm` and `delegate_call_evm` syscalls for contract calls.
|
||||||
|
|
||||||
|
### Fixed:
|
||||||
|
- The missing `STOP` instruction at the end of `code` blocks.
|
||||||
|
- The missing bounds check in the internal sbrk implementation.
|
||||||
|
- The call gas is no longer ignored.
|
||||||
|
|
||||||
## v0.5.0
|
## v0.5.0
|
||||||
|
|
||||||
|
|||||||
Generated
+1252
-1239
File diff suppressed because it is too large
Load Diff
+15
-15
@@ -34,13 +34,13 @@ revive-yul = { version = "0.4.0", path = "crates/yul" }
|
|||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
cc = "1.2"
|
cc = "1.2"
|
||||||
libc = "0.2.172"
|
libc = "0.2.172"
|
||||||
tempfile = "3.20"
|
tempfile = "3.23"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
semver = { version = "1.0", features = ["serde"] }
|
semver = { version = "1.0", features = ["serde"] }
|
||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
|
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
|
||||||
regex = "1.11"
|
regex = "1.12"
|
||||||
once_cell = "1.21"
|
once_cell = "1.21"
|
||||||
num = "0.4.3"
|
num = "0.4.3"
|
||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
@@ -48,20 +48,20 @@ sha3 = "0.10"
|
|||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
which = "8.0"
|
which = "8.0"
|
||||||
path-slash = "0.2"
|
path-slash = "0.2"
|
||||||
rayon = "1.10"
|
rayon = "1.11"
|
||||||
clap = { version = "4", default-features = false, features = ["derive"] }
|
clap = { version = "4", default-features = false, features = ["derive"] }
|
||||||
polkavm-common = "0.29.0"
|
polkavm-common = "0.30.0"
|
||||||
polkavm-linker = "0.29.0"
|
polkavm-linker = "0.30.0"
|
||||||
polkavm-disassembler = "0.29.0"
|
polkavm-disassembler = "0.30.0"
|
||||||
polkavm = "0.29.0"
|
polkavm = "0.30.0"
|
||||||
alloy-primitives = { version = "1.1", features = ["serde"] }
|
alloy-primitives = { version = "1.4", features = ["serde"] }
|
||||||
alloy-sol-types = "1.1"
|
alloy-sol-types = "1.4"
|
||||||
alloy-genesis = "1.0.41"
|
alloy-genesis = "1.1.2"
|
||||||
alloy-serde = "1.0"
|
alloy-serde = "1.1"
|
||||||
env_logger = { version = "0.11.8", default-features = false }
|
env_logger = { version = "0.11.8", default-features = false }
|
||||||
serde_stacker = "0.1.12"
|
serde_stacker = "0.1.14"
|
||||||
criterion = { version = "0.7", features = ["html_reports"] }
|
criterion = { version = "0.7", features = ["html_reports"] }
|
||||||
log = { version = "0.4.27" }
|
log = { version = "0.4.28" }
|
||||||
git2 = { version = "0.20.2", default-features = false }
|
git2 = { version = "0.20.2", default-features = false }
|
||||||
downloader = "0.2.8"
|
downloader = "0.2.8"
|
||||||
flate2 = "1.1"
|
flate2 = "1.1"
|
||||||
@@ -71,12 +71,12 @@ tar = "0.4"
|
|||||||
toml = "0.9"
|
toml = "0.9"
|
||||||
assert_cmd = "2"
|
assert_cmd = "2"
|
||||||
assert_fs = "1.1"
|
assert_fs = "1.1"
|
||||||
normpath = "1.3"
|
normpath = "1.5"
|
||||||
|
|
||||||
# polkadot-sdk and friends
|
# polkadot-sdk and friends
|
||||||
codec = { version = "3.7.5", default-features = false, package = "parity-scale-codec" }
|
codec = { version = "3.7.5", default-features = false, package = "parity-scale-codec" }
|
||||||
scale-info = { version = "2.11.6", default-features = false }
|
scale-info = { version = "2.11.6", default-features = false }
|
||||||
polkadot-sdk = { version = "2509.0.0" }
|
polkadot-sdk = { version = "=2507.4.0" }
|
||||||
|
|
||||||
# llvm
|
# llvm
|
||||||
[workspace.dependencies.inkwell]
|
[workspace.dependencies.inkwell]
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
url = "https://github.com/llvm/llvm-project.git"
|
|
||||||
branch = "release/18.x"
|
|
||||||
ref = "3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff"
|
|
||||||
@@ -10,16 +10,21 @@
|
|||||||
format \
|
format \
|
||||||
clippy \
|
clippy \
|
||||||
doc \
|
doc \
|
||||||
|
book \
|
||||||
machete \
|
machete \
|
||||||
test \
|
test \
|
||||||
test-integration \
|
test-integration \
|
||||||
test-resolc \
|
test-resolc \
|
||||||
|
test-yul \
|
||||||
test-workspace \
|
test-workspace \
|
||||||
test-wasm \
|
test-wasm \
|
||||||
test-llvm-builder
|
test-llvm-builder \
|
||||||
|
test-book \
|
||||||
bench \
|
bench \
|
||||||
bench-pvm \
|
bench-pvm \
|
||||||
bench-evm \
|
bench-evm \
|
||||||
|
bench-resolc \
|
||||||
|
bench-yul \
|
||||||
clean
|
clean
|
||||||
|
|
||||||
install: install-bin install-npm
|
install: install-bin install-npm
|
||||||
@@ -38,7 +43,7 @@ install-llvm-builder:
|
|||||||
cargo install --force --locked --path crates/llvm-builder
|
cargo install --force --locked --path crates/llvm-builder
|
||||||
|
|
||||||
install-llvm: install-llvm-builder
|
install-llvm: install-llvm-builder
|
||||||
revive-llvm clone
|
git submodule update --init --recursive --depth 1
|
||||||
revive-llvm build --llvm-projects lld --llvm-projects clang
|
revive-llvm build --llvm-projects lld --llvm-projects clang
|
||||||
|
|
||||||
install-revive-runner:
|
install-revive-runner:
|
||||||
@@ -56,20 +61,26 @@ clippy:
|
|||||||
doc:
|
doc:
|
||||||
cargo doc --all-features --workspace --document-private-items --no-deps
|
cargo doc --all-features --workspace --document-private-items --no-deps
|
||||||
|
|
||||||
|
book: test-book
|
||||||
|
mdbook serve book --open
|
||||||
|
|
||||||
machete:
|
machete:
|
||||||
cargo install cargo-machete
|
cargo install cargo-machete
|
||||||
cargo machete
|
cargo machete
|
||||||
|
|
||||||
test: format clippy machete test-workspace install-revive-runner install-revive-explorer doc
|
test: format clippy machete test-workspace install-revive-runner install-revive-explorer doc test-book
|
||||||
|
|
||||||
test-integration: install-bin
|
test-integration: install-bin
|
||||||
cargo test --package revive-integration
|
cargo test --package revive-integration
|
||||||
|
|
||||||
test-resolc: install
|
test-resolc: install
|
||||||
cargo test --package resolc
|
cargo test --package resolc --all-targets
|
||||||
|
|
||||||
|
test-yul:
|
||||||
|
cargo test --package revive-yul --all-targets
|
||||||
|
|
||||||
test-workspace: install
|
test-workspace: install
|
||||||
cargo test --workspace --exclude revive-llvm-builder
|
cargo test --workspace --all-targets --exclude revive-llvm-builder
|
||||||
|
|
||||||
test-wasm: install-wasm
|
test-wasm: install-wasm
|
||||||
npm run test:wasm
|
npm run test:wasm
|
||||||
@@ -78,6 +89,10 @@ test-llvm-builder:
|
|||||||
@echo "warning: the llvm-builder tests will take many hours"
|
@echo "warning: the llvm-builder tests will take many hours"
|
||||||
cargo test --package revive-llvm-builder -- --test-threads=1
|
cargo test --package revive-llvm-builder -- --test-threads=1
|
||||||
|
|
||||||
|
test-book:
|
||||||
|
cargo install mdbook --version 0.5.1 --locked
|
||||||
|
mdbook test book
|
||||||
|
|
||||||
bench: install-bin
|
bench: install-bin
|
||||||
cargo criterion --all --all-features --message-format=json \
|
cargo criterion --all --all-features --message-format=json \
|
||||||
| criterion-table > crates/benchmarks/BENCHMARKS.md
|
| criterion-table > crates/benchmarks/BENCHMARKS.md
|
||||||
@@ -90,6 +105,16 @@ bench-evm: install-bin
|
|||||||
cargo criterion --bench execute --features bench-evm --message-format=json \
|
cargo criterion --bench execute --features bench-evm --message-format=json \
|
||||||
| criterion-table > crates/benchmarks/EVM.md
|
| criterion-table > crates/benchmarks/EVM.md
|
||||||
|
|
||||||
|
bench-resolc: test-resolc
|
||||||
|
cargo criterion --package resolc --bench compile --message-format=json \
|
||||||
|
| criterion-table > crates/resolc/BENCHMARKS_M4PRO.md
|
||||||
|
|
||||||
|
bench-yul: test-yul
|
||||||
|
cargo criterion --package revive-yul --bench parse --message-format=json \
|
||||||
|
| criterion-table > crates/yul/BENCHMARKS_PARSE_M4PRO.md
|
||||||
|
cargo criterion --package revive-yul --bench lower --message-format=json \
|
||||||
|
| criterion-table > crates/yul/BENCHMARKS_LOWER_M4PRO.md
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cargo clean ; \
|
cargo clean ; \
|
||||||
revive-llvm clean ; \
|
revive-llvm clean ; \
|
||||||
@@ -97,3 +122,4 @@ clean:
|
|||||||
rm -rf crates/resolc/src/tests/cli/artifacts ; \
|
rm -rf crates/resolc/src/tests/cli/artifacts ; \
|
||||||
cargo uninstall resolc ; \
|
cargo uninstall resolc ; \
|
||||||
cargo uninstall revive-llvm-builder ;
|
cargo uninstall revive-llvm-builder ;
|
||||||
|
mdbook clean book
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||

|

|
||||||
[](https://contracts.polkadot.io/revive_compiler/)
|
[](https://paritytech.github.io/revive/)
|
||||||
|
|
||||||
# revive
|
# revive
|
||||||
|
|
||||||
YUL recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
|
Yul recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
|
||||||
|
|
||||||
Visit [contracts.polkadot.io](https://contracts.polkadot.io) to learn more about contracts on Polkadot!
|
Check the [docs](https://paritytech.github.io/revive/) or visit [contracts.polkadot.io](https://docs.polkadot.com/develop/smart-contracts/) to learn more about `revive` and contracts on Polkadot!
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
@@ -14,11 +14,13 @@ 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
|
||||||
|
|
||||||
Building Solidity contracts for PolkaVM requires installing the following two compilers:
|
Building Solidity contracts for PolkaVM requires installing the following two compilers:
|
||||||
- `resolc`: The revive Solidity compiler YUL frontend and PolkaVM code generator (provided by this repository).
|
- `resolc`: The revive Solidity compiler Yul frontend and PolkaVM code generator (provided by this repository).
|
||||||
- `solc`: The [Ethereum Solidity reference compiler](https://github.com/ethereum/solidity/) implemenation.`resolc` uses `solc` during the compilation process, please refer to the [Ethereum Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html) for installation instructions.
|
- `solc`: The [Ethereum Solidity reference compiler](https://github.com/ethereum/solidity/) implemenation.`resolc` uses `solc` during the compilation process, please refer to the [Ethereum Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html) for installation instructions.
|
||||||
|
|
||||||
### `resolc` binary releases
|
### `resolc` binary releases
|
||||||
|
|
||||||
`resolc` is distributed as a standalone binary (with `solc` as the only external dependency). Please download one of our [binary releases](https://github.com/paritytech/revive/releases) for your target platform and mind the platform specific instructions below.
|
`resolc` is distributed as a standalone binary (with `solc` as the only external dependency). Please download one of our [binary releases](https://github.com/paritytech/revive/releases) for your target platform and mind the platform specific instructions below.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -44,6 +46,7 @@ Building Solidity contracts for PolkaVM requires installing the following two co
|
|||||||
|
|
||||||
|
|
||||||
### `resolc` NPM package
|
### `resolc` NPM package
|
||||||
|
|
||||||
We distribute the revive compiler as [node.js module](https://www.npmjs.com/package/@parity/resolc) and [hardhat plugin](https://www.npmjs.com/package/@parity/hardhat-polkadot-resolc).
|
We distribute the revive compiler as [node.js module](https://www.npmjs.com/package/@parity/resolc) and [hardhat plugin](https://www.npmjs.com/package/@parity/hardhat-polkadot-resolc).
|
||||||
|
|
||||||
Note: The `solc` dependency is bundled via NPM packaging and defaults to the latest supported version.
|
Note: The `solc` dependency is bundled via NPM packaging and defaults to the latest supported version.
|
||||||
@@ -96,47 +99,10 @@ make install-bin
|
|||||||
resolc --version
|
resolc --version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cross-compilation to Wasm
|
|
||||||
|
|
||||||
Cross-compile the `resolc.js` frontend executable to Wasm for running it in a Node.js or browser environment. The `REVIVE_LLVM_TARGET_PREFIX` environment variable is used to control the target environment LLVM dependency.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Instructions for cross-compilation to wasm32-unknown-emscripten</summary>
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Build the host LLVM dependency with PolkaVM target support
|
|
||||||
make install-llvm
|
|
||||||
export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final
|
|
||||||
|
|
||||||
# Build the target LLVM dependency with PolkaVM target support
|
|
||||||
revive-llvm --target-env emscripten clone
|
|
||||||
source emsdk/emsdk_env.sh
|
|
||||||
revive-llvm --target-env emscripten build --llvm-projects lld
|
|
||||||
export REVIVE_LLVM_TARGET_PREFIX=${PWD}/target-llvm/emscripten/target-final
|
|
||||||
|
|
||||||
# Build the resolc frontend executable
|
|
||||||
make install-wasm
|
|
||||||
make test-wasm
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks.
|
Please consult the [Developer Guide](https://paritytech.github.io/revive/developer_guide/contributing.html) to learn more about how contribute to the project.
|
||||||
Ensure that your branch passes `make test` locally when submitting a pull request.
|
|
||||||
|
|
||||||
### Design overview
|
|
||||||
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
Before running the tests, ensure that Geth (Go Ethereum) is installed on your system. Follow the installation guide here: [Installing Geth](https://geth.ethereum.org/docs/getting-started/installing-geth).
|
|
||||||
Once Geth is installed, you can run the tests using the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
make test
|
|
||||||
```
|
|
||||||
# Acknowledgements
|
# Acknowledgements
|
||||||
|
|
||||||
The revive compiler project, after some early experiments with EVM bytecode translations, decided to fork the `era-compiler` framework.
|
The revive compiler project, after some early experiments with EVM bytecode translations, decided to fork the `era-compiler` framework.
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
book
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[book]
|
||||||
|
title = "revive compiler book"
|
||||||
|
authors = ["xermicus"]
|
||||||
|
language = "en"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
build-dir = "../docs"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
- [Welcome](./welcome.md)
|
||||||
|
- [`resolc` user guide](./user_guide.md)
|
||||||
|
- [Installation](./user_guide/installation.md)
|
||||||
|
- [Command Line Interface](./user_guide/cli.md)
|
||||||
|
- [JS NPM package](./user_guide/js.md)
|
||||||
|
- [Tooling integration](./user_guide/tooling.md)
|
||||||
|
- [Standard JSON interface](./user_guide/std_json.md)
|
||||||
|
- [Differences to EVM](./user_guide/differences.md)
|
||||||
|
- [Rust contract libraries](./user_guide/rust_libraries.md)
|
||||||
|
- [`revive-runner` sandbox](./revive_runner.md)
|
||||||
|
- [Developer Guide](./developer_guide.md)
|
||||||
|
- [Contributor guide](./developer_guide/contributing.md)
|
||||||
|
- [Compiler architecture](./developer_guide/architecture.md)
|
||||||
|
- [PVM and the pallet-revive runtime target](./developer_guide/target.md)
|
||||||
|
- [Testing strategy](./developer_guide/testing.md)
|
||||||
|
- [Cross compilation](./developer_guide/cross_compilation.md)
|
||||||
|
- [FAQ](./faq.md)
|
||||||
|
- [Roadmap and Vision](./roadmap.md)
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Developer guide
|
||||||
|
|
||||||
|
This chapter covers internal aspects of the compiler and helps contributors getting started with the `revive` codebase.
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Compiler architecture and internals
|
||||||
|
|
||||||
|
`revive` relies on `solc`, the [Ethereum Solidity compiler](https://github.com/argotorg/solidity), as the Solidity frontend to process smart contracts written in Solidity. [LLVM](https://github.com/llvm/llvm-project), a popular and powerful compiler framework, is used as the compiler backend and does the heavy lifting in terms of optimizitations and RISC-V code generation.
|
||||||
|
|
||||||
|
`revive` mainly takes care of lowering the Yul intermediate representation (IR) produced by `solc` to LLVM IR. This approach provides a good balance between maintaining a high level of Ethereum compatibility, good contract performance and feasible engineering efforts.
|
||||||
|
|
||||||
|
## `resolc`
|
||||||
|
|
||||||
|
`resolc` is the overarching compiler driver library and binary.
|
||||||
|
|
||||||
|
When compiling a Solidity source file with `resolc`, the following steps happen under the hood:
|
||||||
|
1. `solc` is used to lower the Solidity source code into [YUL intermediate representation](https://docs.soliditylang.org/en/latest/yul.html).
|
||||||
|
2. `revive` lowers the YUL IR into LLVM IR.
|
||||||
|
3. LLVM optimizes the code and emits a RISC-V ELF shared object (through LLD).
|
||||||
|
4. The [PolkaVM](https://github.com/paritytech/polkavm) linker finally links the ELF shared object into a PolkaVM blob.
|
||||||
|
|
||||||
|
This compilation process can be visualized as follows:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Reproducible contract builds
|
||||||
|
|
||||||
|
Because on-chain contract code is identified via its code blob hash, it is crucial to maintain reproducible contract builds. A given compiler version must reproduce the contract build _exactly_ on every target platform `resolc` supports via the official binary releases.
|
||||||
|
|
||||||
|
To ensure this, we employ the following measures:
|
||||||
|
- The code generation must be fully deterministic. For example iterating over standard `HashMap` invalidates this due to its internal state, making it an invalid operation in `revive`. To circumvent that, a `BTreeMap` can be used instead.
|
||||||
|
- We release fully statically linked `resolc` binaries. This prevents dynamic linking of potentially differentiating libraries.
|
||||||
|
- The only non-bundled dependency is the `solc` compiler. This is considered fine because the same properties apply to `solc`.
|
||||||
|
|
||||||
|
## The `revive` compiler libraries
|
||||||
|
|
||||||
|
The main compiler logic is implemented in the `revive-yul` and `revive-llvm-context` crates.
|
||||||
|
|
||||||
|
The Yul library implements a lexer and parser and lowers the resulting tree into LLVM IR. It does so by emitting LL using the LLVM builder and our own `revive-llvm-context` compiler context crate. The revive LLVM context crate encapsulates code generation logic (decoupled from the parser).
|
||||||
|
|
||||||
|
The Yul library also implements a simple [visitor](https://en.wikipedia.org/wiki/Visitor_pattern) interface (see [visitor.rs](https://github.com/paritytech/revive/blob/main/crates/yul/src/visitor.rs)). If you want to work with the AST, it is strongly recommended to implement visitors. The LLVM code generation is implemented using a dedicated trait for historical reasons only.
|
||||||
|
|
||||||
|
## EVM heap memory
|
||||||
|
|
||||||
|
PVM doesn't offer a similar API. Hence the emitted contract code emulates the linear EVM heap memory using a static byte buffer. Data inside this byte buffer is kept big endian for EVM compatibility reasons (unaligned access is allowed and makes optimizing this non-trivial).
|
||||||
|
|
||||||
|
Unlike with the EVM, where heap memory usage is gas metered, our heap size is static (the size is user controllable via a setting flag). The compiler emits bound checks to prevent overflows.
|
||||||
|
|
||||||
|
## The LLVM dependency
|
||||||
|
|
||||||
|
LLVM is a special non Rust dependency. We interface its builder interface via the [inkwell](https://crates.io/crates/inkwell) wrapper crate.
|
||||||
|
|
||||||
|
We use upstream LLVM, but release and use our custom builds. We require the compiler builtins specifically built for the PVM `rv64emacb` target and always leave assertions on. Furthermore, we need cross builds because `resolc` itself targets emscripten and musl. The [revive-llvm-builer](https://crates.io/crates/revive-llvm-builder) functions as a cross-platform build script and is used to build and release the LLVM dependency.
|
||||||
|
|
||||||
|
We also maintain the [lld-sys crate](https://crates.io/crates/lld-sys) for interfacing with `LLD`. The LLVM linker is used during the compilation process, but we don't want to distribute another binary.
|
||||||
|
|
||||||
|
|
||||||
|
## Custom optimizations
|
||||||
|
|
||||||
|
At the moment, no significant custom optimizations are implemented. Thus, we are missing some optimization opportunities that neither `solc` nor LLVM can realize (due to their lack of domain specific knowledge about the semantics of our target environment). Furthermore, `solc` optimizes for EVM gas and a target machine orthogonal to our target (BE 256-bit stack machine EVM vs. 64-bit LE RISC architecture PVM). We have started working on an additional IR layer between Yul and LLVM to capture missed optimization opportunities, though.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Contributor guide
|
||||||
|
|
||||||
|
The `revive` compiler is an open source software project and we gladly accept quality contributions from anyone!
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
A quick reference on how to build the Solidity compiler is maintained in the project's [README.md](https://github.com/paritytech/revive?tab=readme-ov-file#building-from-source).
|
||||||
|
|
||||||
|
### Using the `Makefile`
|
||||||
|
|
||||||
|
The [Makefile](https://github.com/paritytech/revive/blob/main/Makefile) comprehensively encapsulates all development aspects of this codebase. It is kept concise and readable. Please read and use it! You'll learn for example:
|
||||||
|
|
||||||
|
- How to build and install a `resolc` development version.
|
||||||
|
- How to run tests and benchmarks.
|
||||||
|
- How to cross-compile `resolc`.
|
||||||
|
|
||||||
|
As a general rule-of-thumb: If `make test` runs fine locally, chances for green CI pipelines are good.
|
||||||
|
|
||||||
|
### Codebase organization
|
||||||
|
|
||||||
|
For the most parts, `revive` is a rather standard Rust workspace codebase. There are some non-Rust dependencies, which sometimes complicates things a little bit.
|
||||||
|
|
||||||
|
#### The `crates/` dir
|
||||||
|
|
||||||
|
All Rust crates live under the `crates/` directory. The workspace automatically considers any crate found therein. If you need to add a new create, please implement it there.
|
||||||
|
|
||||||
|
Compiler library crates should be named with the `revive-` prefix. The crate location doesn't need the prefix.
|
||||||
|
|
||||||
|
#### Dependencies
|
||||||
|
|
||||||
|
Dependencies should be added as workspace dependencies. Try to avoid pinning dependencies whenever possible. If possible, add dev dependencies as `dev-dependencies` only.
|
||||||
|
|
||||||
|
Please do always include the `Cargo.lock` dependency lock file with your PR. Please don't run `cargo update` together with other changes (it is preferred to update the lock file in a dedicated dependency update PR).
|
||||||
|
|
||||||
|
## Contribution rules
|
||||||
|
|
||||||
|
1. Changes must be submitted via a pull request (PR) to the github upstream repository.
|
||||||
|
2. Ensure that your branch passes `make test` locally when submitting a pull request.
|
||||||
|
3. A PR must not be merged until CI fully passes. Exceptions can be made (for example to fix CI issues itself).
|
||||||
|
4. No force pushes to the `main` branch and open PR branches.
|
||||||
|
5. Maintainers can request changes or deny contributions at their own discretion.
|
||||||
|
|
||||||
|
## Style guide
|
||||||
|
|
||||||
|
We require the official Rust formatter and clippy linter. In addition to that, please also consider the following best-effort aspects:
|
||||||
|
|
||||||
|
- Avoid [magic numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) and strings. Instead, add them as module constants.
|
||||||
|
- Avoid abbreviated variable and function names. Always provide meaningful and readable symbols.
|
||||||
|
- Don't write macros and don't use third party macros for things that can easily be expressed in few lines of code or outlined into functions.
|
||||||
|
- Avoid import aliasing. Please use the parent or fully qualified path for conflicting symbols.
|
||||||
|
- Any inline comments must provide additional semantic meaning, explain counter-intuitive behavior or highlight non-obvious design decisions. In other words, try to make the code expressive enough to a degree it doesn't need comments expressing the same thing again in the English language. Delete such comments if your AI assistant generated them.
|
||||||
|
- Public items must have a meaningful doc comment.
|
||||||
|
- Provide meaningful panic messages to `.expect()` or just use `.unwrap()`.
|
||||||
|
|
||||||
|
## AI policy
|
||||||
|
|
||||||
|
Contributors may use whatever AI assistance tools they wish to whatever degree they wish in the process of creating their contribution, __given they acknowledge the following__:
|
||||||
|
|
||||||
|
_Project maintainers may reject any contribution (or portions of it) if the contribution shows signs of problematic involvement of generative AI_.
|
||||||
|
|
||||||
|
Judgement of "problematic involvement" lies at the sole discretion of project maintainers. No proof (whether a contribution was in fact AI generated or not) is required. Rationale:
|
||||||
|
|
||||||
|
- No one enjoys reading soulless and uncanny LLM slop. Please review and fix any AI slop yourself prior to submitting a PR.
|
||||||
|
- A Solidity compiler is security sensitive software. Even miniscule mistakes can ultimately lead to loss of funds. AI models are inherently stochastic. They regurarly fail to capture important nuances or produce straight hallucinations. Code that was "blindly" generated has no home here.
|
||||||
|
- `revive` is a large codebase. Generative AI assistants may not have enough "context window" to sufficiently capture correctness, consistency and style aspects of the codebase. We'd like to keep this codebase maintainable _by humans_ for the forseeable future.
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Cross compilation
|
||||||
|
|
||||||
|
We cross-compile the `resolc.js` frontend executable to Wasm for running it in a Node.js or browser environment.
|
||||||
|
|
||||||
|
The [musl](https://www.musl-libc.org/) target is used to obtain statically linked ELF binaries for Linux.
|
||||||
|
|
||||||
|
## Wasm via emscripten
|
||||||
|
|
||||||
|
The `REVIVE_LLVM_TARGET_PREFIX` environment variable is used to control the target environment LLVM dependency. This requires a compatible LLVM build, obtainable via the `revive-llvm` build script. Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Build the host LLVM dependency with PolkaVM target support
|
||||||
|
make install-llvm
|
||||||
|
export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final
|
||||||
|
|
||||||
|
# Build the target LLVM dependency with PolkaVM target support
|
||||||
|
revive-llvm emsdk
|
||||||
|
source emsdk/emsdk_env.sh
|
||||||
|
revive-llvm --target-env emscripten build --llvm-projects lld
|
||||||
|
export REVIVE_LLVM_TARGET_PREFIX=${PWD}/target-llvm/emscripten/target-final
|
||||||
|
|
||||||
|
# Build the resolc frontend executable
|
||||||
|
make install-wasm
|
||||||
|
make test-wasm
|
||||||
|
```
|
||||||
|
|
||||||
|
## musl libc
|
||||||
|
|
||||||
|
[rust-musl-cross](https://github.com/rust-cross/rust-musl-cross) is a straightforward way to cross compile Rust to musl. The [Dockerfile](https://github.com/paritytech/revive/blob/main/Dockerfile) is an executable example of how to do that.
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 290 KiB |
@@ -0,0 +1,29 @@
|
|||||||
|
# PVM and the pallet-revive runtime target
|
||||||
|
|
||||||
|
The `revive` compiler targets [PolkaVM (PVM)](https://github.com/paritytech/polkavm) via [pallet-revive](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive) on Polkadot.
|
||||||
|
|
||||||
|
## Target CPU configuration
|
||||||
|
|
||||||
|
The exact target CPU configuration can be found [here](https://github.com/paritytech/revive/blob/8cd10a613625428956eb33c39c9022a91bfbf103/crates/llvm-context/src/target_machine/mod.rs#L22-L32).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> The PVM linker requires fully relocatable ELF objects.
|
||||||
|
|
||||||
|
## Why PVM
|
||||||
|
|
||||||
|
PVM is a RISC-V based VM designed to overcome the flaws of [WebAssebmly (Wasm)](https://webassembly.org/). Wasm was believed to be a more efficient successor to the rather slow EVM. However, Wasm is far from an ideal target for smart contracts as some of its design decisions are unfavorable for short-lived workloads. The main problem is on-chain Wasm bytecode compilation or interpretation overhead. Prior benchmarks consistently ignoring this overhead seeded the blockchain industry with flawed assumptions: _Only when ignoring the startup overhead_ Wasm is much faster than the slow computing EVM. In practice however, gains are nullified entirely and Wasm loses completely even against very slow VMs like the EVM. Executing Wasm contracts is in fact so inefficient that typical contract workloads are _orders of magnitude_ more expensive than the equivalent EVM variant.
|
||||||
|
|
||||||
|
On the other hand, since RISC-V is similar to CPUs found in validator hardware (x86 and ARM), bytecode translation mostly boils down to a linear mapping from one instruction to another. The _embedded_ ISA specification reduces the number of general purpose registers, in turn removing the need for expensive register allocation. This guarantees single-pass `O(n)` JIT compilation of contract bytecode. The close proximity of PVM bytecode with actual validator CPU bytecode effectively allows to move all expensive compilation workload off-chain. Benchmarks ([1](https://hackmd.io/@XXX9CM1uSSCWVNFRYaSB5g/HJarTUhJA#Results-execute), [2](https://github.com/paritytech/polkavm/blob/master/BENCHMARKS.md)) show that with the PVM JIT, sandboxed PVM code executes at around half the speed of native code, which falls into the same ballpark of the state-of-the-art `wasmtime` Wasm implementation (while EVM sits somewhere around 1/10 to less than 1/100 of native speed). However, the PVM JIT compiler only uses a fraction of the time `wasmtime` requires to compile the code.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> The PVM JIT isn't available yet in `pallet-revive`. At the time of writing, the contract code is interpreted, which is orders of magnitude slower than the JIT.
|
||||||
|
|
||||||
|
## Host environment: `pallet-revive`
|
||||||
|
|
||||||
|
The `revive` compiler targets the [`pallet-revive` runtime environment](https://docs.rs/pallet-revive/).
|
||||||
|
|
||||||
|
`pallet-revive` exposes a [syscall like interface](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html) for contract interactions with the host environment. This is provided by the [revive-runtime-api](https://crates.io/crates/revive-runtime-api) library.
|
||||||
|
|
||||||
|
After the initial launch on the Polkadot Asset Hub blockchain, the runtime API is considered stable and backwards compatible indefinitively.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Testing strategy
|
||||||
|
|
||||||
|
Contributors are encouraged to implement some appropriate unit and integration tests together with any bug fixes or new feature implementations. However, when it comes to testing the code generation logic, our testing strategy goes way beyond simple unit and integration tests. This chapter explains how the `revive` compiler implementation is tested for correctness and how we define correctness.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
>
|
||||||
|
> Running the integration tests require the `evm` tool from `go-ethereum` in your `$PATH`.
|
||||||
|
>
|
||||||
|
> Either install it using your package manager or to build it from source:
|
||||||
|
> ```bash
|
||||||
|
> git clone https://github.com/ethereum/go-ethereum/
|
||||||
|
> cd go-ethereum
|
||||||
|
> make all
|
||||||
|
> export PATH=/path/to/go-ethereum/build/bin/:$PATH
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## Bug compatibility with Ethereum Solidity
|
||||||
|
|
||||||
|
As a Solidity compiler, we aim to preserve contract code semantics as close as possible to Solidity compiled to EVM with the `solc` reference implementation. As highlighted in the user guide, due to the underlying target difference, this isn't always possible. However, wherever it is possible, we follow the philosophy of [**bug compatibility**](https://en.wikipedia.org/wiki/Bug_compatibility) with the Ethereum contracts stack.
|
||||||
|
|
||||||
|
## Differential integration tests
|
||||||
|
|
||||||
|
A high level of bug compatibility with Ethereum is ensured through [**differential testing**](https://en.wikipedia.org/wiki/Differential_testing) with the Ethereum `solc` and EVM contracts stack. The [revive-integration](https://crates.io/crates/revive-integration) library is the central integration test utility, providing a set of Solidity integration test cases. Further, it implements differential tests against the reference implementation by combining the [revive-runner](https://crates.io/crates/revive-runner) sandbox, the [go-ethereum EVM tool](https://github.com/ethereum/go-ethereum/tree/master/cmd/evm) and the [revive-differential](https://crates.io/crates/revive-differential).
|
||||||
|
|
||||||
|
The `revive-runner` library provides a [**declarative**](https://en.wikipedia.org/wiki/Declarative_programming) test [specification format](https://github.com/paritytech/revive/blob/main/crates/runner/src/specs.rs). This vastly simplifies writing differential test cases and removes a lot of room for errors in test logic. Example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"differential": true,
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"Instantiate": {
|
||||||
|
"code": {
|
||||||
|
"Solidity": {
|
||||||
|
"contract": "Bitwise"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Call": {
|
||||||
|
"dest": {
|
||||||
|
"Instantiated": 0
|
||||||
|
},
|
||||||
|
"data": "3fa4f245"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Above example instantiates the `Bitwise` contract and calls it with some defined calldata. The `revive-runner` library implements a helper wrapper to execute test specs on the go-ethereum standalone `evm` tool. This allows the `revive-runner` to execute specs against the EVM and the `pallet-revive` runtime. Key to differential testing is setting `"differential": true`, resulting in the following:
|
||||||
|
|
||||||
|
1. The `Bitwise` contract is compiled to EVM and PVM code.
|
||||||
|
2. The runner executes the defined `actions` on the EVM and collects all state changes (storage, balance) and execution results.
|
||||||
|
3. The runner executes each action on the PVM. Observed state changes _after each step_ as well as the final execution result is asserted to match the EVM counterparts __exactly__.
|
||||||
|
|
||||||
|
__Note how we never defined any expected outcome manually.__ Instead, we simply observe and collect the data defining the "correct" outcome.
|
||||||
|
|
||||||
|
Differential testing in combination with declarative test specifications proved to be simple, yet very effective, in ensuring expected Ethereum Solidity semantics on `pallet-revive`.
|
||||||
|
|
||||||
|
## The differential testing utility
|
||||||
|
|
||||||
|
A lot of nuanced bugs caused by tiny implementation details inside the `revive` compiler _and_ the `pallet-revive` runtime could be identified and eliminated early on thanks to the differential testing strategy. Thus, we decided to take this approach further and created a comprehensive test runner and a large suite of more complex test cases.
|
||||||
|
|
||||||
|
The [Revive Differential Tests](https://github.com/paritytech/revive-differential-tests/) follow the exact same strategy but implement a much more powerful test spec format, spec runner and reports. This allows differentially testing much more complex test cases (for example testing Uniswap pair creations and swaps), executed via transactions sent to actual blockchain nodes.
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# FAQ
|
||||||
|
|
||||||
|
## What EVM version do you support?
|
||||||
|
|
||||||
|
We neither do nor don't support any EVM version. We support Solidity versions, starting from `solc` version 0.8.0 onwards.
|
||||||
|
|
||||||
|
## Is inline assembly supported
|
||||||
|
|
||||||
|
Yes, almost all inline assembly features are supported ([see the differences in Yul translation chapter](user_guide/differences.md)).
|
||||||
|
|
||||||
|
## Do you support opcode `XY`?
|
||||||
|
|
||||||
|
See above, the same applies.
|
||||||
|
|
||||||
|
## In what Solidity version should I write my dApp?
|
||||||
|
|
||||||
|
We generally recommend to always use the latest supported version to profit from latest bugfixes, features and performance improvements.
|
||||||
|
|
||||||
|
Find out about the latest supported version by running `resolc --supported-solc-versions` or checking [here](https://github.com/paritytech/resolc-bin).
|
||||||
|
|
||||||
|
## Tool `XY` says the contract size is larger than 24kb and will fail to deploy?
|
||||||
|
|
||||||
|
The 24kb code size restriction only exist for the EVM. Our limit is currently around 1mb and may increase further in the future.
|
||||||
|
|
||||||
|
## Is `resolc` a drop-in replacement for `solc`?
|
||||||
|
|
||||||
|
No. `resolc` aims to work similarly to `solc`, but it's not considered a drop-in replacement.
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
# `revive-runner` sandbox
|
||||||
|
|
||||||
|
Running contract code usually requires a blockchain node. While local dev nodes can be used, sometimes it's just not desirable to do so. Instead, it can be much more convenient to run and debug contract code with a stripped down environment.
|
||||||
|
|
||||||
|
This is where the `revive-runner` comes in handy. In a nutshell, it is a single-binary no-blockchain `pallet-revive` runtime.
|
||||||
|
|
||||||
|
## Installation and usage
|
||||||
|
|
||||||
|
Inside the root `revive` repository directory, install it from source (requires Rust installed):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make install-revive-runner
|
||||||
|
```
|
||||||
|
|
||||||
|
After installing, see `revive-runner --help` for usage help.
|
||||||
|
|
||||||
|
## Trace logs
|
||||||
|
|
||||||
|
The standard `RUST_LOG` environment variable controls the log output from the contract execution. This includes `revive` runtime logs and PVM execution trace logs. Sometimes it's convenient to have more fine granular insight. Some useful filters:
|
||||||
|
- `RUST_LOG=runtime=trace`: The `pallet-revive` runtime trace logs.
|
||||||
|
- `RUST_LOG=polkavm=trace`: Low level PolkaVM instruction tracing.
|
||||||
|
|
||||||
|
## Automatic contract instantiation
|
||||||
|
|
||||||
|
To avoid running the constract in an unitialized state, `revive-runner` automatically instantiates the contract before calling it (constructor arguments can be provided).
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Suppose we want to trace the syscalls of the execution of a compiled contract file `Flipper.pvm`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
RUST_LOG=runtime=trace revive-runner -f Flipper.pvm
|
||||||
|
[DEBUG runtime::revive] Contract memory usage: purgable=6144/3145728 KB baseline=103063/1572864
|
||||||
|
[TRACE runtime::revive::strace] call_data_size() = Ok(0) gas_consumed: Weight { ref_time: 985209, proof_size: 0 }
|
||||||
|
[TRACE runtime::revive::strace] value_transferred(out_ptr: 4294836096) = Ok(()) gas_consumed: Weight { ref_time: 2937634, proof_size: 0 }
|
||||||
|
[TRACE runtime::revive::strace] call_data_copy(out_ptr: 131216, out_len: 0, offset: 0) = Ok(()) gas_consumed: Weight { ref_time: 4084483, proof_size: 0 }
|
||||||
|
[TRACE runtime::revive::strace] seal_return(flags: 0, data_ptr: 131216, data_len: 0) = Err(TrapReason::Return(ReturnData { flags: 0, data: [] })) gas_consumed: Weight { ref_time: 5510615, proof_size: 0 }
|
||||||
|
[TRACE runtime::revive] frame finished with: Ok(ExecReturnValue { flags: (empty), data: [] })
|
||||||
|
[TRACE runtime::revive::strace] call_data_size() = Ok(0) gas_consumed: Weight { ref_time: 985209, proof_size: 0 }
|
||||||
|
[TRACE runtime::revive::strace] seal_return(flags: 1, data_ptr: 131088, data_len: 0) = Err(TrapReason::Return(ReturnData { flags: 1, data: [] })) gas_consumed: Weight { ref_time: 2456669, proof_size: 0 }
|
||||||
|
[TRACE runtime::revive] frame finished with: Ok(ExecReturnValue { flags: REVERT, data: [] })
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Roadmap and Vision
|
||||||
|
|
||||||
|
The `revive` compiler speeds up Solidity contracts by orders of magnitude. `revive` provides a decisive edge over other contract platforms. Notably, the compiler eliminates the need of rewriting Solidity dApps as single dApp parachains for scaling reasons. Retaining as high compatibility with Ethereum Solidity as possible keeps entry barriers low.
|
||||||
|
|
||||||
|
We believe in Dr. Gavin Wood's [ĐApps: What Web 3.0 Looks Like](https://gavwood.com/dappsweb3.html) manifesto and the ecosystem of the Solidity programming language. Our motivation lies in the realization that for a _true_ web3 revolution, significant scaling efforts, like the ones provided by the PVM and this project, are necessary to unfold.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
The first major release, `resolc` v1.0.0, emits functional PVM code from given Solidity sources. It relies on `solc` and LLVM for optimizations. The main priority of this release was delivering a mostly feature complete and safe Solidity v0.8.0 compiler.
|
||||||
|
|
||||||
|
Focus for the second major release is on the custom optimization pipeline, which aims to significantly improve emitted code blob sizes.
|
||||||
|
|
||||||
|
The below roadmap gives a rough overview of the project's development timeline.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# `resolc` user guide
|
||||||
|
|
||||||
|
`resolc` is a Solidity `v0.8` compiler for [Polkadot `native` smart contracts](https://docs.polkadot.com/develop/smart-contracts/overview/#native-smart-contracts). Solidity compiled with `resolc` executes orders of magnitude faster than the EVM. `resolc` also supports almost all Solidity `v0.8` features including inline assembly, offering a high level of comptability with the Ethereum Solidity reference implementation.
|
||||||
|
|
||||||
|
## `revive` vs. `resolc` nomenclature
|
||||||
|
|
||||||
|
`revive` is the name of the overarching "Solidity to PolkaVM" compiler project, which contains multiple components (for example the Yul parser, the code generation library, the `resolc` executable itself, and many more things).
|
||||||
|
|
||||||
|
`resolc` is the name of the compiler driver executable, combining many `revive` components in a single and easy to use binary application.
|
||||||
|
|
||||||
|
In other words, `revive` is the whole compiler infrastructure (more like `LLVM`) and `resolc` is a user-facing single-entrypoint compiler frontend (more like `clang`).
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# CLI usage
|
||||||
|
|
||||||
|
We aim to keep the `resolc` CLI usage close to `solc`. There are a few things and options worthwhile to know about in `resolc` which do not exist in the Ethereum world. This chapter explains those in more detail than the CLI help message.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
>
|
||||||
|
> For the complete help about CLI options, please see `resolc --help`.
|
||||||
|
|
||||||
|
### LLVM optimization levels
|
||||||
|
```bash
|
||||||
|
-O, --optimization <OPTIMIZATION>
|
||||||
|
```
|
||||||
|
|
||||||
|
`resolc` exposes the optimization level setting for the LLVM backend. The performance and size of compiled contracts varies wiedly between different optimization levels.
|
||||||
|
|
||||||
|
Valid levels are the following:
|
||||||
|
- `0`: No optimizations are applied.
|
||||||
|
- `1`: Basic optimizations for execution time.
|
||||||
|
- `2`: Advanced optimizations for execution time.
|
||||||
|
- `3`: Aggressive optimizations for execution time.
|
||||||
|
- `s`: Optimize for code size.
|
||||||
|
- `z`: Aggressively optimize for code size.
|
||||||
|
|
||||||
|
By default, `-Oz` is applied.
|
||||||
|
|
||||||
|
### Stack size
|
||||||
|
```bash
|
||||||
|
--stack-size <STACK_SIZE>
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You are incentivized to keep this value as small as possible:
|
||||||
|
1. Increasing the stack size will increase gas costs due to increased startup costs.
|
||||||
|
2. The stack size contributes to the total memory size a contract can use, which includes the contract's code size.
|
||||||
|
|
||||||
|
Default value: 32768
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> If the contract uses more stack memory than configured, it will compile fine but eventually revert execution at runtime!
|
||||||
|
|
||||||
|
### Heap size
|
||||||
|
```bash
|
||||||
|
--heap-size <HEAP_SIZE>
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You are incentivized 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 contract's code size
|
||||||
|
|
||||||
|
Default value: 65536
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> If the contract uses more heap memory than configured, it will compile fine but eventually revert execution at runtime!
|
||||||
|
|
||||||
|
### solc
|
||||||
|
```bash
|
||||||
|
--solc <SOLC>
|
||||||
|
```
|
||||||
|
|
||||||
|
Specify the path to the `solc` executable. By default, the one in `${PATH}` is used.
|
||||||
|
|
||||||
|
### Debug artifacts
|
||||||
|
```bash
|
||||||
|
--debug-output-dir <DEBUG_OUTPUT_DIRECTORY>
|
||||||
|
```
|
||||||
|
|
||||||
|
Dump all intermediary compiler artifacts to files in the specified directory. This includes the YUL IR, optimized and unoptimized LLVM IR, the ELF object and the PVM assembly. Useful for debugging and development purposes.
|
||||||
|
|
||||||
|
### Debug info
|
||||||
|
```bash
|
||||||
|
-g
|
||||||
|
```
|
||||||
|
Generate source based debug information in the output code file. Useful for debugging and development purposes and disabled by default.
|
||||||
|
|
||||||
|
### Deploy time linking
|
||||||
|
```bash
|
||||||
|
--link [--libraries <LIBRARIES>] <INPUT_FILES>
|
||||||
|
```
|
||||||
|
|
||||||
|
In Solidity, 3 things can happen with libraries:
|
||||||
|
|
||||||
|
1. They are not `extern`ally callable and thus can be inlined.
|
||||||
|
1. The solc Solidity optimizer inlines those (usually the case). Note: `resolc` always activates the solc Solidity optimizer.
|
||||||
|
2. If the solc Solidity optimizer is disabled or for some reason fails to inline them (both rare), they are not inlined and require linking.
|
||||||
|
2. They are `extern`ally callable but still linked at compile time. This is the case if at compile time the library address is known (i.e. `--libraries` supplied in CLI or the corresponding setting in STD JSON input).
|
||||||
|
3. They are linked at deploy time. This happens when the compiler does not know the library address (i.e. `--libraries` flag is missing or the provided libraries are incomplete, same for STD JSON input). This case is rare because it's discourage and should never be used by production dApps.
|
||||||
|
|
||||||
|
In cases `1.2` and `3`:
|
||||||
|
- Some of the produced code blobs will be in the "unlinked" raw `ELF` object format and not yet deployable.
|
||||||
|
- To make them deployable, they need to be "linked" (done using the `resolc --link` linker mode explained below).
|
||||||
|
- The compiler emitted `DELEGATECALL` instructions to call non-inlined (unlinked) libraries. The contract deployer must make sure to deploy any libraries prior to contract deployment.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> Using deploy time linking is officially **discouraged**. Mainly due to bytecode hashes changing after the fact. We decided to support it in `resolc` regardless, due to popular request.
|
||||||
|
|
||||||
|
Similar to how it works in `solc`, `--libraries` may be used to provide libraries during linking mode.
|
||||||
|
|
||||||
|
Unlike with `solc`, where linking implies a simple string substitution mechanism, `resolc` needs to resolve actual missing `ELF` symbols. This is due to how factory dependencies work in PVM. As a consequence, it isn't sufficient to just provide the unlinked blobs to the linker. Instead, they must be provided in the exact same directory structure the Solidity source code was found during compile time.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
- The contract `src/foo/bar.sol:Bar` is involved in deploy time linking. It may be a factory dependency.
|
||||||
|
- The contract blob needs to be provided inside a relative `src/foo/` directory to `--link`. Otherwise symbol resolution may fail.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> Tooling is supposed to take care of this. In the future, we may append explicit linkage data to simplify the deploy time linking feature.
|
||||||
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Differences to EVM
|
||||||
|
|
||||||
|
This section highlights some potentially observable differences in the [YUL EVM dialect](https://docs.soliditylang.org/en/latest/yul.html#evm-dialect) translation compared to Ethereum Solidity.
|
||||||
|
|
||||||
|
Solidity developers deploying dApps to [`pallet-revive`](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive) ought to read and understand this section well.
|
||||||
|
|
||||||
|
## Deploy code vs. runtime code
|
||||||
|
|
||||||
|
Our contract runtime does not differentiate between runtime code and deploy (constructor) code.
|
||||||
|
Instead, both are emitted into a single PVM contract code blob and live on-chain.
|
||||||
|
Therefore, in EVM terminology, the deploy code equals the runtime code.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
>
|
||||||
|
> In constructor code, the `codesize` instruction will return the call data size instead of the actual code blob size.
|
||||||
|
|
||||||
|
## Solidity
|
||||||
|
|
||||||
|
We are aware of the following differences in the translation of Solidity code.
|
||||||
|
|
||||||
|
### `address.creationCode`
|
||||||
|
|
||||||
|
This returns the bytecode keccak256 hash instead.
|
||||||
|
|
||||||
|
## YUL functions
|
||||||
|
|
||||||
|
The below list contains noteworthy differences in the translation of YUL functions.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> Many functions receive memory buffer offset pointer or size arguments. Since the PVM pointer size is 32 bit, supplying memory offset or buffer size values above `2^32-1` will trap the contract immediately.
|
||||||
|
|
||||||
|
The `solc` compiler ought to always emit valid memory references, so Solidity dApp authors don't need to worry about this unless they deal with low level `assembly` code.
|
||||||
|
|
||||||
|
### `mload`, `mstore`, `msize`, `mcopy` (memory related functions)
|
||||||
|
|
||||||
|
In general, revive preserves the memory layout, meaning low level memory operations are supported. However, a few caveats apply:
|
||||||
|
- The EVM linear heap memory is emulated using a fixed byte buffer of 64kb. This implies that the maximum memory a contract can use is limited to 64kbit (on Ethereum, contract memory is capped by gas and therefore varies).
|
||||||
|
- Thus, accessing memory offsets larger than the fixed buffer size will trap the contract at runtime with an `OutOfBound` error.
|
||||||
|
- The compiler might detect and optimize unused memory reads and writes, leading to a different `msize` compared to what the EVM would see.
|
||||||
|
|
||||||
|
### `calldataload`, `calldatacopy`
|
||||||
|
|
||||||
|
In the constructor code, the offset is ignored and this always returns `0`.
|
||||||
|
|
||||||
|
### `codecopy`
|
||||||
|
|
||||||
|
Only supported in constructor code.
|
||||||
|
|
||||||
|
### `invalid`
|
||||||
|
|
||||||
|
Traps the contract but does not consume the remaining gas.
|
||||||
|
|
||||||
|
### `create`, `create2`
|
||||||
|
|
||||||
|
Deployments on revive work different than on EVM. In a nutshell: Instead of supplying the deploy code concatenated with the constructor arguments (the EVM deploy model), the [revive runtime expects two pointers](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html#tymethod.instantiate):
|
||||||
|
1. A buffer containing the code hash to deploy.
|
||||||
|
2. The constructor arguments buffer.
|
||||||
|
|
||||||
|
To make contract instantiation using the `new` keyword in Solidity work seamlessly,
|
||||||
|
`revive` translates the `dataoffset` and `datasize` instructions so that they assume the contract hash instead of the contract code.
|
||||||
|
The hash is always of constant size.
|
||||||
|
Thus, `revive` is able to supply the expected code hash and constructor arguments pointer to the runtime.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
>
|
||||||
|
> This might fall apart in code creating contracts inside `assembly` blocks. **We strongly discourage using the `create` family opcodes to manually craft deployments in `assembly` blocks!** Usually, the reason for using `assembly` blocks is to save gas, which is futile on revive anyways due to lower transaction costs.
|
||||||
|
|
||||||
|
### `dataoffset`
|
||||||
|
|
||||||
|
Returns the contract hash.
|
||||||
|
|
||||||
|
### `datasize`
|
||||||
|
|
||||||
|
Returns the contract hash size (constant value of `32`).
|
||||||
|
|
||||||
|
### `prevrandao`, `difficulty`
|
||||||
|
|
||||||
|
Translates to a constant value of `2500000000000000`.
|
||||||
|
|
||||||
|
### `pc`, `extcodecopy`
|
||||||
|
|
||||||
|
Only valid to use in EVM (they also have no use case in PVM) and produce a compile time error.
|
||||||
|
|
||||||
|
### `blobhash`, `blobbasefee`
|
||||||
|
|
||||||
|
Related to the Ethereum rollup model and produce a compile time error. Polkadot offers a superior rollup model, removing the use case for blob data related opcodes.
|
||||||
|
|
||||||
|
## Difference regarding the `solc` `via-ir` mode
|
||||||
|
|
||||||
|
There are two different compilation pipelines available in `solc` and [there are small differences between them](https://docs.soliditylang.org/en/latest/ir-breaking-changes.html).
|
||||||
|
|
||||||
|
Since `resolc` processes the YUL IR, always assume the `solc` IR based codegen behavior for contracts compiled with the `revive` compiler.
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
Building Solidity contracts for PolkaVM requires installing the following two compilers:
|
||||||
|
- `solc`: The [Ethereum Solidity reference compiler](https://github.com/argotorg/solidity) implementation.
|
||||||
|
- `resolc`: The revive Solidity compiler YUL frontend and PolkaVM code generator.
|
||||||
|
|
||||||
|
## `resolc` binary releases
|
||||||
|
|
||||||
|
`resolc` is supported an all major operating systems and installation is straightforward.
|
||||||
|
Please find our [binary releases](https://github.com/paritytech/revive/releases) for the following platforms:
|
||||||
|
- Linux (MUSL)
|
||||||
|
- MacOS (universal)
|
||||||
|
- Windows
|
||||||
|
- Wasm via emscripten
|
||||||
|
|
||||||
|
## Installing the `solc` dependency
|
||||||
|
|
||||||
|
`resolc` uses `solc` during the compilation process, please refer to the [Ethereum Solidity documentation](https://docs.soliditylang.org/en/latest/installing-solidity.html) for installation instructions.
|
||||||
|
|
||||||
|
## `revive` NPM package
|
||||||
|
|
||||||
|
We distribute the revive compiler as [node.js module](https://github.com/paritytech/revive/tree/main/js/resolc).
|
||||||
|
|
||||||
|
## Buidling `resolc` from source
|
||||||
|
|
||||||
|
Please follow the build [instructions in the revive `README.md`](https://github.com/paritytech/revive?tab=readme-ov-file#building-from-source).
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# JS NPM package
|
||||||
|
|
||||||
|
The `resolc` compiler driver is published as an NPM package under [@parity/resolc](https://www.npmjs.com/package/@parity/resolc).
|
||||||
|
|
||||||
|
It's usable from `Node.js` code or directly from the command line:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npx @parity/resolc@latest --bin crates/integration/contracts/flipper.sol -o /tmp/out
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> While the npm package makes a nice portable option, it doesn't expose all options.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Rust contract libraries
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> This is not yet implemented but something for consideration on the roadmap.
|
||||||
|
|
||||||
|
Solidity - tightly coupled to the EVM - introduces some inherent inefficiencies that are by design and either needs to be followed or can't be easily worked around, even with efforts like better optimized compiler and VM implementations. This represents a technical dead end. So far the EVM sees no adoption beyond the blockchain industry. Chances are that [the EVM end up deprecated](https://ethereum-magicians.org/t/long-term-l1-execution-layer-proposal-replace-the-evm-with-risc-v) for technical reasons (or maybe not and the RISC-V idea gets abandoned, who knows).
|
||||||
|
|
||||||
|
PVM, however, is a general purpose VM. It supports LLVM based mainstream programming languages like Rust. It's a common software engineering practice to compose applications from pieces written in multiple languages, using each to their own strength. For example, AI solutions traditionally use the python scripting language for convenient developer experience, while the underlying AI models get implemented in a lower level language such as C++.
|
||||||
|
|
||||||
|
The same pattern can of course be applied to dApps, where we'd expect application specific languages like Solidity mixed with libraries implementing computationally complex algorithms in a lower level language. Business logic and user interfaces are naturally implemented as regular Solidity dApps which can include (link against) Rust libraries. Rust is a fast, safe low level language and the Polkadot SDK is written in Rust itself, making it an excellent choice.
|
||||||
|
|
||||||
|
For example, [ZK proof verifiers](https://en.wikipedia.org/wiki/Zero-knowledge_proof) or expensive [DeFi](https://en.wikipedia.org/wiki/Decentralized_finance) primitives would benefit greatly from Rust implementations.
|
||||||
|
|
||||||
|
`revive` provides tooling support and a small Rust contracts SDK for seamless integration with Rust libraries.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Standard JSON interface
|
||||||
|
|
||||||
|
The `revive` compiler is mostly compatible with the `solc` standard JSON interface. There are a few additional (PVM related) __input__ configurations:
|
||||||
|
|
||||||
|
## The `settings.polkavm` object
|
||||||
|
|
||||||
|
Used to configure PVM specific compiler settings.
|
||||||
|
|
||||||
|
### `settings.polkavm.debugInformation`
|
||||||
|
|
||||||
|
A boolean value allowing to enable debug information. Corresponds to `resolc -g`.
|
||||||
|
|
||||||
|
### The `settings.polkavm.memoryConfig` object
|
||||||
|
|
||||||
|
Used to apply PVM specific memory configuration settings.
|
||||||
|
|
||||||
|
#### `settings.polkavm.heapSize`
|
||||||
|
|
||||||
|
A numerical value allowing to configure the contract heap size. Corresponds to `resolc --heap-size`.
|
||||||
|
|
||||||
|
#### `settings.polkavm.stackSize`
|
||||||
|
|
||||||
|
A numerical value allowing to configure the contract stack size. Corresponds to `resolc --stack-size`.
|
||||||
|
|
||||||
|
## The `settings.optimizer` object
|
||||||
|
|
||||||
|
The `settings.optimizer` object is augmented with support for PVM specific optimization settings.
|
||||||
|
|
||||||
|
### `settings.optimizer.mode`
|
||||||
|
|
||||||
|
A single char value to configure the LLVM optimizer settings. Corresponds to `resolc -O`.
|
||||||
|
|
||||||
|
## `settings.llvmArguments`
|
||||||
|
|
||||||
|
Allows to specify arbitrary command line arguments to LLVM initialization. Used mainly for development and debugging purposes.
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Tooling integration
|
||||||
|
|
||||||
|
`resolc` achieved successful integration with a variety of third party developer tools.
|
||||||
|
|
||||||
|
## Solidity toolkits
|
||||||
|
|
||||||
|
Support for `resolc` is available in forks of the [hardhat](https://hardhat.org) and [foundry](https://getfoundry.sh) Solidity toolkits:
|
||||||
|
|
||||||
|
- [The Parity Hardhat fork](https://github.com/paritytech/hardhat-polkadot)
|
||||||
|
- [The Parity Foundry fork](https://github.com/paritytech/foundry-polkadot?tab=readme-ov-file#2-resolc-compiler-integration)
|
||||||
|
|
||||||
|
## Compiler explorer
|
||||||
|
|
||||||
|
`resolc` is available on [godbolt.org](https://godbolt.org/z/6GM6n4Ka3) for the Solidity and Yul input languages. See also the announcement post on the [forum](https://forum.polkadot.network/t/resolc-is-live-on-compiler-explorer).
|
||||||
|
|
||||||
|
## Remix IDE
|
||||||
|
|
||||||
|
There is remix IDE fork with `resolc` support at [remix.polkadot.io](https://remix.polkadot.io). Unfortunately this is no longer actively maintained (there might be bugs and outdated `resolc` versions).
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Welcome
|
||||||
|
|
||||||
|
Hello and a warm welcome to the `revive` Solidity compiler book!
|
||||||
|
|
||||||
|
## Target audience
|
||||||
|
|
||||||
|
- **Solidity dApp developers** should read the [user guide](./user_guide.md). Solidity on PolkaVM introduces important differences to EVM which should be well understood.
|
||||||
|
- **Contributors** will find the [developer guide](./developer_guide.md) helpful for getting up to speed.
|
||||||
|
|
||||||
|
## Other Polkadot contracts resources
|
||||||
|
|
||||||
|
Head to [contracts.polkadot.io](https://docs.polkadot.com/develop/smart-contracts/) for more general information about contracts on Polkadot.
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This [mdBook](https://github.com/rust-lang/mdBook) documents the revive Solidity compiler project. The content is found under `book/`. Run `make book` to observe changes.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- [Benchmark Results](#benchmark-results)
|
- [Benchmark Results](#benchmark-results)
|
||||||
- [Baseline](#baseline)
|
- [Baseline](#baseline)
|
||||||
- [OddPorduct](#oddporduct)
|
- [OddProduct](#oddproduct)
|
||||||
- [TriangleNumber](#trianglenumber)
|
- [TriangleNumber](#trianglenumber)
|
||||||
- [FibonacciRecursive](#fibonaccirecursive)
|
- [FibonacciRecursive](#fibonaccirecursive)
|
||||||
- [FibonacciIterative](#fibonacciiterative)
|
- [FibonacciIterative](#fibonacciiterative)
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|:--------|:-------------------------|:-------------------------------- |
|
|:--------|:-------------------------|:-------------------------------- |
|
||||||
| **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (✅ **1.02x slower**) |
|
| **`0`** | `10.08 us` (✅ **1.00x**) | `10.32 us` (✅ **1.02x slower**) |
|
||||||
|
|
||||||
### OddPorduct
|
### OddProduct
|
||||||
|
|
||||||
| | `EVM` | `PVMInterpreter` |
|
| | `EVM` | `PVMInterpreter` |
|
||||||
|:-------------|:--------------------------|:-------------------------------- |
|
|:-------------|:--------------------------|:-------------------------------- |
|
||||||
@@ -70,4 +70,3 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
|
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fn bench_baseline(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_odd_product(c: &mut Criterion) {
|
fn bench_odd_product(c: &mut Criterion) {
|
||||||
let group = group(c, "OddPorduct");
|
let group = group(c, "OddProduct");
|
||||||
let parameters = &[10_000, 100_000, 300000];
|
let parameters = &[10_000, 100_000, 300000];
|
||||||
|
|
||||||
bench(group, parameters, parameters, Contract::odd_product);
|
bench(group, parameters, parameters, Contract::odd_product);
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ pub enum EVMVersion {
|
|||||||
/// The corresponding EVM version.
|
/// The corresponding EVM version.
|
||||||
#[serde(rename = "cancun")]
|
#[serde(rename = "cancun")]
|
||||||
Cancun,
|
Cancun,
|
||||||
|
/// The corresponding EVM version.
|
||||||
|
#[serde(rename = "prague")]
|
||||||
|
Prague,
|
||||||
|
/// The corresponding EVM version.
|
||||||
|
#[serde(rename = "osaka")]
|
||||||
|
Osaka,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for EVMVersion {
|
impl TryFrom<&str> for EVMVersion {
|
||||||
@@ -62,6 +68,8 @@ impl TryFrom<&str> for EVMVersion {
|
|||||||
"paris" => Self::Paris,
|
"paris" => Self::Paris,
|
||||||
"shanghai" => Self::Shanghai,
|
"shanghai" => Self::Shanghai,
|
||||||
"cancun" => Self::Cancun,
|
"cancun" => Self::Cancun,
|
||||||
|
"prague" => Self::Prague,
|
||||||
|
"osaka" => Self::Osaka,
|
||||||
_ => anyhow::bail!("Invalid EVM version: {}", value),
|
_ => anyhow::bail!("Invalid EVM version: {}", value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -82,6 +90,8 @@ impl std::fmt::Display for EVMVersion {
|
|||||||
Self::Paris => write!(f, "paris"),
|
Self::Paris => write!(f, "paris"),
|
||||||
Self::Shanghai => write!(f, "shanghai"),
|
Self::Shanghai => write!(f, "shanghai"),
|
||||||
Self::Cancun => write!(f, "cancun"),
|
Self::Cancun => write!(f, "cancun"),
|
||||||
|
Self::Prague => write!(f, "prague"),
|
||||||
|
Self::Osaka => write!(f, "osaka"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"grayGlacierBlock": 0,
|
"grayGlacierBlock": 0,
|
||||||
"shanghaiTime": 0,
|
"shanghaiTime": 0,
|
||||||
"cancunTime": 0,
|
"cancunTime": 0,
|
||||||
|
"pragueTime": 0,
|
||||||
|
"osakaTime": 0,
|
||||||
"terminalTotalDifficulty": 0,
|
"terminalTotalDifficulty": 0,
|
||||||
"terminalTotalDifficultyPassed": true,
|
"terminalTotalDifficultyPassed": true,
|
||||||
"blobSchedule": {
|
"blobSchedule": {
|
||||||
@@ -22,6 +24,16 @@
|
|||||||
"target": 3,
|
"target": 3,
|
||||||
"max": 6,
|
"max": 6,
|
||||||
"baseFeeUpdateFraction": 3338477
|
"baseFeeUpdateFraction": 3338477
|
||||||
|
},
|
||||||
|
"prague": {
|
||||||
|
"target": 6,
|
||||||
|
"max": 9,
|
||||||
|
"baseFeeUpdateFraction": 5007716
|
||||||
|
},
|
||||||
|
"osaka": {
|
||||||
|
"target": 6,
|
||||||
|
"max": 9,
|
||||||
|
"baseFeeUpdateFraction": 5007716
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -44,4 +56,4 @@
|
|||||||
"balance": "1000000000"
|
"balance": "1000000000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"Baseline": 914,
|
"Baseline": 911,
|
||||||
"Computation": 2295,
|
"Computation": 2337,
|
||||||
"DivisionArithmetics": 14496,
|
"DivisionArithmetics": 14488,
|
||||||
"ERC20": 17482,
|
"ERC20": 17041,
|
||||||
"Events": 1674,
|
"Events": 1672,
|
||||||
"FibonacciIterative": 1490,
|
"FibonacciIterative": 1454,
|
||||||
"Flipper": 2086,
|
"Flipper": 2106,
|
||||||
"SHA1": 8158
|
"SHA1": 7814
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
|
|||||||
|
|
||||||
contract BaseFee {
|
contract BaseFee {
|
||||||
constructor() payable {
|
constructor() payable {
|
||||||
assert(block.basefee == 0);
|
assert(block.basefee > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8;
|
||||||
|
|
||||||
|
// Use a non-zero call gas that works with call gas clipping but not with a truncate.
|
||||||
|
|
||||||
|
/* runner.json
|
||||||
|
{
|
||||||
|
"differential": true,
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"Upload": {
|
||||||
|
"code": {
|
||||||
|
"Solidity": {
|
||||||
|
"contract": "Other"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Instantiate": {
|
||||||
|
"code": {
|
||||||
|
"Solidity": {
|
||||||
|
"contract": "CallGas"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": "1000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
contract Other {
|
||||||
|
address public last;
|
||||||
|
uint public foo;
|
||||||
|
|
||||||
|
fallback() external {
|
||||||
|
last = msg.sender;
|
||||||
|
foo += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract CallGas {
|
||||||
|
constructor(uint _gas) payable {
|
||||||
|
Other other = new Other();
|
||||||
|
address(other).call{ gas: _gas }(hex"");
|
||||||
|
assert(other.last() == address(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.8.31;
|
||||||
|
|
||||||
|
/* runner.json
|
||||||
|
{
|
||||||
|
"differential": true,
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"Instantiate": {
|
||||||
|
"code": {
|
||||||
|
"Solidity": {
|
||||||
|
"contract": "CountLeadingZeros"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// The EIP-7939 test vectors:
|
||||||
|
/// https://eips.ethereum.org/EIPS/eip-7939#test-cases
|
||||||
|
contract CountLeadingZeros {
|
||||||
|
function clz(uint256 x) internal pure returns (uint256 r) {
|
||||||
|
assembly {
|
||||||
|
r := clz(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() payable {
|
||||||
|
assert(
|
||||||
|
clz(0x000000000000000000000000000000000000000000000000000000000000000)
|
||||||
|
== 0x0000000000000000000000000000000000000000000000000000000000000100
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
clz(0x8000000000000000000000000000000000000000000000000000000000000000)
|
||||||
|
== 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
clz(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
|
||||||
|
== 0x0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
clz(0x4000000000000000000000000000000000000000000000000000000000000000)
|
||||||
|
== 0x0000000000000000000000000000000000000000000000000000000000000001
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
clz(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
|
||||||
|
== 0x0000000000000000000000000000000000000000000000000000000000000001
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
clz(0x0000000000000000000000000000000000000000000000000000000000000001)
|
||||||
|
== 0x00000000000000000000000000000000000000000000000000000000000000ff
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,6 @@ pragma solidity ^0.8;
|
|||||||
|
|
||||||
contract GasLeft {
|
contract GasLeft {
|
||||||
constructor() payable {
|
constructor() payable {
|
||||||
assert(gasleft() > gasleft());
|
|
||||||
assert(gasleft() > 0 && gasleft() < 0xffffffffffffffff);
|
assert(gasleft() > 0 && gasleft() < 0xffffffffffffffff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
|
|||||||
|
|
||||||
contract GasLimit {
|
contract GasLimit {
|
||||||
constructor() payable {
|
constructor() payable {
|
||||||
assert(block.gaslimit == 2000000000000);
|
assert(block.gaslimit > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ pragma solidity ^0.8;
|
|||||||
|
|
||||||
contract GasPrice {
|
contract GasPrice {
|
||||||
constructor() payable {
|
constructor() payable {
|
||||||
assert(tx.gasprice == 1000);
|
assert(tx.gasprice > 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pragma solidity ^0.8.28;
|
|||||||
"dest": {
|
"dest": {
|
||||||
"Instantiated": 0
|
"Instantiated": 0
|
||||||
},
|
},
|
||||||
"data": "e2179b8e"
|
"data": "0be0e4a60000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -29,12 +29,12 @@ pragma solidity ^0.8.28;
|
|||||||
|
|
||||||
contract MLoad {
|
contract MLoad {
|
||||||
constructor() payable {
|
constructor() payable {
|
||||||
assert(g() == 0);
|
assert(loadAt(0) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function g() public payable returns (uint m) {
|
function loadAt(uint _offset) public payable returns (uint m) {
|
||||||
assembly {
|
assembly {
|
||||||
m := mload(0)
|
m := mload(_offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ pragma solidity ^0.8;
|
|||||||
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
|
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"VerifyCall": {
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Call": {
|
"Call": {
|
||||||
"dest": {
|
"dest": {
|
||||||
@@ -36,11 +31,6 @@ pragma solidity ^0.8;
|
|||||||
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
|
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"VerifyCall": {
|
|
||||||
"success": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Call": {
|
"Call": {
|
||||||
"dest": {
|
"dest": {
|
||||||
@@ -48,11 +38,6 @@ pragma solidity ^0.8;
|
|||||||
},
|
},
|
||||||
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
|
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"VerifyCall": {
|
|
||||||
"success": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ pragma solidity ^0.8;
|
|||||||
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
|
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"VerifyCall": {
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Call": {
|
"Call": {
|
||||||
"dest": {
|
"dest": {
|
||||||
@@ -36,11 +31,6 @@ pragma solidity ^0.8;
|
|||||||
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
|
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"VerifyCall": {
|
|
||||||
"success": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Call": {
|
"Call": {
|
||||||
"dest": {
|
"dest": {
|
||||||
@@ -48,11 +38,6 @@ pragma solidity ^0.8;
|
|||||||
},
|
},
|
||||||
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
|
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"VerifyCall": {
|
|
||||||
"success": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub struct Contract {
|
|||||||
pub evm_runtime: Vec<u8>,
|
pub evm_runtime: Vec<u8>,
|
||||||
pub pvm_runtime: Vec<u8>,
|
pub pvm_runtime: Vec<u8>,
|
||||||
pub calldata: Vec<u8>,
|
pub calldata: Vec<u8>,
|
||||||
|
pub yul: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Contract {
|
impl Contract {
|
||||||
@@ -19,6 +20,7 @@ impl Contract {
|
|||||||
evm_runtime: compile_evm_bin_runtime(name, code),
|
evm_runtime: compile_evm_bin_runtime(name, code),
|
||||||
pvm_runtime: compile_blob(name, code),
|
pvm_runtime: compile_blob(name, code),
|
||||||
calldata,
|
calldata,
|
||||||
|
yul: compile_to_yul(name, code, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ impl Contract {
|
|||||||
evm_runtime: compile_evm_bin_runtime(name, code),
|
evm_runtime: compile_evm_bin_runtime(name, code),
|
||||||
pvm_runtime: compile_blob_with_options(name, code, true, OptimizerSettings::size()),
|
pvm_runtime: compile_blob_with_options(name, code, true, OptimizerSettings::size()),
|
||||||
calldata,
|
calldata,
|
||||||
|
yul: compile_to_yul(name, code, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +234,15 @@ sol!(
|
|||||||
);
|
);
|
||||||
case!("MCopy.sol", MCopy, memcpyCall, memcpy, payload: Bytes);
|
case!("MCopy.sol", MCopy, memcpyCall, memcpy, payload: Bytes);
|
||||||
|
|
||||||
|
sol!(
|
||||||
|
contract MLoad {
|
||||||
|
constructor() payable;
|
||||||
|
|
||||||
|
function loadAt(uint _offset) public payable returns (uint m);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
case!("MLoad.sol", MLoad, loadAtCall, load_at, _offset: U256);
|
||||||
|
|
||||||
sol!(
|
sol!(
|
||||||
contract Call {
|
contract Call {
|
||||||
function value_transfer(address payable destination) public payable;
|
function value_transfer(address payable destination) public payable;
|
||||||
|
|||||||
+233
-44
@@ -1,6 +1,7 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use alloy_primitives::*;
|
use alloy_primitives::*;
|
||||||
|
use resolc::test_utils::build_yul;
|
||||||
use revive_runner::*;
|
use revive_runner::*;
|
||||||
use SpecsAction::*;
|
use SpecsAction::*;
|
||||||
|
|
||||||
@@ -64,6 +65,8 @@ test_spec!(shift_arithmetic_right, "SAR", "SAR.sol");
|
|||||||
test_spec!(add_mod_mul_mod, "AddModMulModTester", "AddModMulMod.sol");
|
test_spec!(add_mod_mul_mod, "AddModMulModTester", "AddModMulMod.sol");
|
||||||
test_spec!(memory_bounds, "MemoryBounds", "MemoryBounds.sol");
|
test_spec!(memory_bounds, "MemoryBounds", "MemoryBounds.sol");
|
||||||
test_spec!(selfdestruct, "Selfdestruct", "Selfdestruct.sol");
|
test_spec!(selfdestruct, "Selfdestruct", "Selfdestruct.sol");
|
||||||
|
test_spec!(clz, "CountLeadingZeros", "CountLeadingZeros.sol");
|
||||||
|
test_spec!(call_gas, "CallGas", "CallGas.sol");
|
||||||
|
|
||||||
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
|
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
|
||||||
vec![Instantiate {
|
vec![Instantiate {
|
||||||
@@ -449,50 +452,6 @@ fn ext_code_size() {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected = "ReentranceDenied")]
|
|
||||||
fn send_denies_reentrancy() {
|
|
||||||
let value = 1000;
|
|
||||||
Specs {
|
|
||||||
actions: vec![
|
|
||||||
instantiate("contracts/Send.sol", "Send").remove(0),
|
|
||||||
Call {
|
|
||||||
origin: TestAddress::Alice,
|
|
||||||
dest: TestAddress::Instantiated(0),
|
|
||||||
value,
|
|
||||||
gas_limit: None,
|
|
||||||
storage_deposit_limit: None,
|
|
||||||
data: Contract::send_self(U256::from(value)).calldata,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
differential: false,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic(expected = "ReentranceDenied")]
|
|
||||||
fn transfer_denies_reentrancy() {
|
|
||||||
let value = 1000;
|
|
||||||
Specs {
|
|
||||||
actions: vec![
|
|
||||||
instantiate("contracts/Transfer.sol", "Transfer").remove(0),
|
|
||||||
Call {
|
|
||||||
origin: TestAddress::Alice,
|
|
||||||
dest: TestAddress::Instantiated(0),
|
|
||||||
value,
|
|
||||||
gas_limit: None,
|
|
||||||
storage_deposit_limit: None,
|
|
||||||
data: Contract::transfer_self(U256::from(value)).calldata,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
differential: false,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create2_salt() {
|
fn create2_salt() {
|
||||||
let salt = U256::from(777);
|
let salt = U256::from(777);
|
||||||
@@ -520,3 +479,233 @@ fn create2_salt() {
|
|||||||
}
|
}
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn code_block_stops() {
|
||||||
|
let code = &build_yul(&[(
|
||||||
|
"poc.yul",
|
||||||
|
r#"object "Test"{
|
||||||
|
code {
|
||||||
|
tstore(0x7fd9d641,0x7b1e022)
|
||||||
|
returndatacopy(0x0,0x0,returndatasize())
|
||||||
|
}
|
||||||
|
object "Test_deployed" { code{} }
|
||||||
|
}"#,
|
||||||
|
)])
|
||||||
|
.unwrap()["poc.yul:Test"];
|
||||||
|
|
||||||
|
Specs {
|
||||||
|
actions: vec![
|
||||||
|
Instantiate {
|
||||||
|
origin: TestAddress::Alice,
|
||||||
|
value: 0,
|
||||||
|
gas_limit: Some(GAS_LIMIT),
|
||||||
|
storage_deposit_limit: None,
|
||||||
|
code: Code::Bytes(code.to_vec()),
|
||||||
|
data: Default::default(),
|
||||||
|
salt: OptionalHex::default(),
|
||||||
|
},
|
||||||
|
Call {
|
||||||
|
origin: TestAddress::Alice,
|
||||||
|
dest: TestAddress::Instantiated(0),
|
||||||
|
value: Default::default(),
|
||||||
|
gas_limit: None,
|
||||||
|
storage_deposit_limit: None,
|
||||||
|
data: Default::default(),
|
||||||
|
},
|
||||||
|
VerifyCall(Default::default()),
|
||||||
|
],
|
||||||
|
differential: false,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn code_block_with_nested_object_stops() {
|
||||||
|
let code = &build_yul(&[(
|
||||||
|
"poc.yul",
|
||||||
|
r#"object "Test" {
|
||||||
|
code {
|
||||||
|
function allocate(size) -> ptr {
|
||||||
|
ptr := mload(0x40)
|
||||||
|
if iszero(ptr) { ptr := 0x60 }
|
||||||
|
mstore(0x40, add(ptr, size))
|
||||||
|
}
|
||||||
|
let size := datasize("Test_deployed")
|
||||||
|
let offset := allocate(size)
|
||||||
|
datacopy(offset, dataoffset("Test_deployed"), size)
|
||||||
|
return(offset, size)
|
||||||
|
}
|
||||||
|
object "Test_deployed" {
|
||||||
|
code {
|
||||||
|
sstore(0, 100)
|
||||||
|
}
|
||||||
|
object "Test" {
|
||||||
|
code {
|
||||||
|
revert(0,0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)])
|
||||||
|
.unwrap()["poc.yul:Test"];
|
||||||
|
|
||||||
|
Specs {
|
||||||
|
actions: vec![
|
||||||
|
Instantiate {
|
||||||
|
origin: TestAddress::Alice,
|
||||||
|
value: 0,
|
||||||
|
gas_limit: Some(GAS_LIMIT),
|
||||||
|
storage_deposit_limit: None,
|
||||||
|
code: Code::Bytes(code.to_vec()),
|
||||||
|
data: Default::default(),
|
||||||
|
salt: OptionalHex::default(),
|
||||||
|
},
|
||||||
|
Call {
|
||||||
|
origin: TestAddress::Alice,
|
||||||
|
dest: TestAddress::Instantiated(0),
|
||||||
|
value: Default::default(),
|
||||||
|
gas_limit: None,
|
||||||
|
storage_deposit_limit: None,
|
||||||
|
data: Default::default(),
|
||||||
|
},
|
||||||
|
VerifyCall(Default::default()),
|
||||||
|
],
|
||||||
|
differential: false,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sbrk_bounds_checks() {
|
||||||
|
let code = &build_yul(&[(
|
||||||
|
"poc.yul",
|
||||||
|
r#"object "Test" {
|
||||||
|
code {
|
||||||
|
return(0x4, 0xffffffff)
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
object "Test_deployed" {
|
||||||
|
code {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)])
|
||||||
|
.unwrap()["poc.yul:Test"];
|
||||||
|
|
||||||
|
let results = Specs {
|
||||||
|
actions: vec![
|
||||||
|
Instantiate {
|
||||||
|
origin: TestAddress::Alice,
|
||||||
|
value: 0,
|
||||||
|
gas_limit: Some(GAS_LIMIT),
|
||||||
|
storage_deposit_limit: None,
|
||||||
|
code: Code::Bytes(code.to_vec()),
|
||||||
|
data: Default::default(),
|
||||||
|
salt: OptionalHex::default(),
|
||||||
|
},
|
||||||
|
VerifyCall(VerifyCallExpectation {
|
||||||
|
success: false,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
differential: false,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let CallResult::Instantiate { result, .. } = results.last().unwrap() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
format!("{result:?}").contains("ContractTrapped"),
|
||||||
|
"not seeing a trap means the contract did not catch the OOB"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_opcode_works() {
|
||||||
|
let code = &build_yul(&[(
|
||||||
|
"invalid.yul",
|
||||||
|
r#"object "Test" {
|
||||||
|
code {
|
||||||
|
invalid()
|
||||||
|
}
|
||||||
|
object "Test_deployed" {
|
||||||
|
code {
|
||||||
|
invalid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
)])
|
||||||
|
.unwrap()["invalid.yul:Test"];
|
||||||
|
|
||||||
|
let results = Specs {
|
||||||
|
actions: vec![
|
||||||
|
Instantiate {
|
||||||
|
origin: TestAddress::Alice,
|
||||||
|
value: 0,
|
||||||
|
gas_limit: Some(GAS_LIMIT),
|
||||||
|
storage_deposit_limit: None,
|
||||||
|
code: Code::Bytes(code.to_vec()),
|
||||||
|
data: Default::default(),
|
||||||
|
salt: OptionalHex::default(),
|
||||||
|
},
|
||||||
|
VerifyCall(VerifyCallExpectation {
|
||||||
|
success: false,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
differential: false,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let CallResult::Instantiate { result, .. } = results.last().unwrap() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result.weight_consumed, GAS_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load from heap memory using an out of bounds offset and expect the
|
||||||
|
/// contract to hit the `invalid` syscall to use all gas (like on EVM).
|
||||||
|
///
|
||||||
|
/// The offset is picked such that a regular truncate would be in bounds.
|
||||||
|
#[test]
|
||||||
|
fn safe_truncate_int_to_xlen_works() {
|
||||||
|
let offset = 0x10000000_00000000u64;
|
||||||
|
let data = Contract::load_at(Uint::from(offset)).calldata;
|
||||||
|
let mut actions = instantiate("contracts/MLoad.sol", "MLoad");
|
||||||
|
actions.append(&mut vec![
|
||||||
|
Call {
|
||||||
|
origin: TestAddress::Alice,
|
||||||
|
dest: TestAddress::Instantiated(0),
|
||||||
|
value: 0,
|
||||||
|
gas_limit: None,
|
||||||
|
storage_deposit_limit: None,
|
||||||
|
data,
|
||||||
|
},
|
||||||
|
VerifyCall(VerifyCallExpectation {
|
||||||
|
success: false,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let results = Specs {
|
||||||
|
actions,
|
||||||
|
differential: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.run();
|
||||||
|
|
||||||
|
let CallResult::Exec { result, .. } = results.last().unwrap() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(result.weight_consumed, GAS_LIMIT);
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ SECTIONS {
|
|||||||
"--error-limit=0",
|
"--error-limit=0",
|
||||||
"--relocatable",
|
"--relocatable",
|
||||||
"--emit-relocs",
|
"--emit-relocs",
|
||||||
"--no-relax",
|
"--relax",
|
||||||
"--unique",
|
"--unique",
|
||||||
"--gc-sections",
|
"--gc-sections",
|
||||||
self.linker_script_path.to_str().expect("should be utf8"),
|
self.linker_script_path.to_str().expect("should be utf8"),
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ pub fn polkavm_linker<T: AsRef<[u8]>>(code: T, strip_binary: bool) -> anyhow::Re
|
|||||||
config.set_strip(strip_binary);
|
config.set_strip(strip_binary);
|
||||||
config.set_optimize(true);
|
config.set_optimize(true);
|
||||||
|
|
||||||
polkavm_linker::program_from_elf(config, code.as_ref())
|
polkavm_linker::program_from_elf(
|
||||||
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
|
config,
|
||||||
|
polkavm_linker::TargetInstructionSet::ReviveV1,
|
||||||
|
code.as_ref(),
|
||||||
|
)
|
||||||
|
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ doctest = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace = true, features = ["help", "std", "derive"] }
|
clap = { workspace = true, features = ["help", "std", "derive"] }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
serde = { workspace = true, features = [ "derive" ] }
|
|
||||||
toml = { workspace = true }
|
|
||||||
num_cpus = { workspace = true }
|
num_cpus = { workspace = true }
|
||||||
fs_extra = { workspace = true }
|
fs_extra = { workspace = true }
|
||||||
path-slash = { workspace = true }
|
path-slash = { workspace = true }
|
||||||
|
|||||||
@@ -67,12 +67,6 @@ Obtain a compatible build for your host platform from the release section of thi
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>4. (Optional) Create the `LLVM.lock` file.</summary>
|
|
||||||
|
|
||||||
* The `LLVM.lock` dictates the LLVM source tree being used.
|
|
||||||
A default `./LLVM.lock` pointing to the release used for development is already provided.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>5. Build LLVM.</summary>
|
<summary>5. Build LLVM.</summary>
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ use crate::utils::path_windows_to_unix as to_unix;
|
|||||||
use std::{env::consts::EXE_EXTENSION, process::Command};
|
use std::{env::consts::EXE_EXTENSION, process::Command};
|
||||||
|
|
||||||
/// Static CFLAGS variable passed to the compiler building the compiler-rt builtins.
|
/// Static CFLAGS variable passed to the compiler building the compiler-rt builtins.
|
||||||
const C_FLAGS: [&str; 6] = [
|
const C_FLAGS: [&str; 7] = [
|
||||||
"--target=riscv64",
|
"--target=riscv64",
|
||||||
"-march=rv64emac",
|
"-march=rv64emac",
|
||||||
"-mabi=lp64e",
|
"-mabi=lp64e",
|
||||||
"-mcpu=generic-rv64",
|
"-mcpu=generic-rv64",
|
||||||
"-nostdlib",
|
"-nostdlib",
|
||||||
"-nodefaultlibs",
|
"-nodefaultlibs",
|
||||||
|
"-fuse-ld=lld",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Static CMAKE arguments for building the compiler-rt builtins.
|
/// Static CMAKE arguments for building the compiler-rt builtins.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ pub mod builtins;
|
|||||||
pub mod ccache_variant;
|
pub mod ccache_variant;
|
||||||
pub mod llvm_path;
|
pub mod llvm_path;
|
||||||
pub mod llvm_project;
|
pub mod llvm_project;
|
||||||
pub mod lock;
|
|
||||||
pub mod platforms;
|
pub mod platforms;
|
||||||
pub mod sanitizer;
|
pub mod sanitizer;
|
||||||
pub mod target_env;
|
pub mod target_env;
|
||||||
@@ -14,7 +13,6 @@ pub mod utils;
|
|||||||
|
|
||||||
pub use self::build_type::BuildType;
|
pub use self::build_type::BuildType;
|
||||||
pub use self::llvm_path::LLVMPath;
|
pub use self::llvm_path::LLVMPath;
|
||||||
pub use self::lock::Lock;
|
|
||||||
pub use self::platforms::Platform;
|
pub use self::platforms::Platform;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -23,87 +21,25 @@ use std::process::Command;
|
|||||||
pub use target_env::TargetEnv;
|
pub use target_env::TargetEnv;
|
||||||
pub use target_triple::TargetTriple;
|
pub use target_triple::TargetTriple;
|
||||||
|
|
||||||
/// Executes the LLVM repository cloning.
|
/// Initializes the LLVM submodule if not already done.
|
||||||
pub fn clone(lock: Lock, deep: bool, target_env: TargetEnv) -> anyhow::Result<()> {
|
pub fn init(init_emscripten: bool) -> anyhow::Result<()> {
|
||||||
utils::check_presence("git")?;
|
utils::check_presence("git")?;
|
||||||
|
|
||||||
if target_env == TargetEnv::Emscripten {
|
if init_emscripten {
|
||||||
utils::install_emsdk()?;
|
utils::install_emsdk()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let destination_path = PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE);
|
let destination_path = PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE);
|
||||||
if destination_path.exists() {
|
if destination_path.join(".git").exists() {
|
||||||
log::warn!(
|
log::info!("LLVM submodule already initialized");
|
||||||
"LLVM repository directory {} already exists, falling back to checkout",
|
return Ok(());
|
||||||
destination_path.display()
|
|
||||||
);
|
|
||||||
return checkout(lock, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut clone_args = vec!["clone", "--branch", lock.branch.as_str()];
|
|
||||||
if !deep {
|
|
||||||
clone_args.push("--depth");
|
|
||||||
clone_args.push("1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::command(
|
utils::command(
|
||||||
Command::new("git")
|
Command::new("git").args(["submodule", "update", "--init", "--recursive"]),
|
||||||
.args(clone_args)
|
"LLVM submodule initialization",
|
||||||
.arg(lock.url.as_str())
|
|
||||||
.arg(destination_path.to_string_lossy().as_ref()),
|
|
||||||
"LLVM repository cloning",
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(r#ref) = lock.r#ref {
|
|
||||||
utils::command(
|
|
||||||
Command::new("git")
|
|
||||||
.args(["checkout", r#ref.as_str()])
|
|
||||||
.current_dir(destination_path.to_string_lossy().as_ref()),
|
|
||||||
"LLVM repository commit checking out",
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes the checkout of the specified branch.
|
|
||||||
pub fn checkout(lock: Lock, force: bool) -> anyhow::Result<()> {
|
|
||||||
let destination_path = PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE);
|
|
||||||
|
|
||||||
utils::command(
|
|
||||||
Command::new("git")
|
|
||||||
.current_dir(destination_path.as_path())
|
|
||||||
.args(["fetch", "--all", "--tags"]),
|
|
||||||
"LLVM repository data fetching",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if force {
|
|
||||||
utils::command(
|
|
||||||
Command::new("git")
|
|
||||||
.current_dir(destination_path.as_path())
|
|
||||||
.args(["clean", "-d", "-x", "--force"]),
|
|
||||||
"LLVM repository cleaning",
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::command(
|
|
||||||
Command::new("git")
|
|
||||||
.current_dir(destination_path.as_path())
|
|
||||||
.args(["checkout", "--force", lock.branch.as_str()]),
|
|
||||||
"LLVM repository data pulling",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let Some(r#ref) = lock.r#ref {
|
|
||||||
let mut checkout_command = Command::new("git");
|
|
||||||
checkout_command.current_dir(destination_path.as_path());
|
|
||||||
checkout_command.arg("checkout");
|
|
||||||
if force {
|
|
||||||
checkout_command.arg("--force");
|
|
||||||
}
|
|
||||||
checkout_command.arg(r#ref);
|
|
||||||
utils::command(&mut checkout_command, "LLVM repository checking out")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,8 +268,6 @@ pub fn clean() -> anyhow::Result<()> {
|
|||||||
.expect("target_env parent directory is target-llvm"),
|
.expect("target_env parent directory is target-llvm"),
|
||||||
)?;
|
)?;
|
||||||
remove_if_exists(&PathBuf::from(LLVMPath::DIRECTORY_EMSDK_SOURCE))?;
|
remove_if_exists(&PathBuf::from(LLVMPath::DIRECTORY_EMSDK_SOURCE))?;
|
||||||
remove_if_exists(&PathBuf::from(LLVMPath::DIRECTORY_LLVM_SOURCE))?;
|
|
||||||
remove_if_exists(&PathBuf::from(LLVMPath::DIRECTORY_LLVM_HOST_SOURCE))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
//! The revive LLVM builder lock file.
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
/// The default lock file location.
|
|
||||||
pub const LLVM_LOCK_DEFAULT_PATH: &str = "LLVM.lock";
|
|
||||||
|
|
||||||
/// The lock file data.
|
|
||||||
///
|
|
||||||
/// This file describes the exact reference of the LLVM framework.
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct Lock {
|
|
||||||
/// The LLVM repository URL.
|
|
||||||
pub url: String,
|
|
||||||
/// The LLVM repository branch.
|
|
||||||
pub branch: String,
|
|
||||||
/// The LLVM repository commit reference.
|
|
||||||
pub r#ref: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&PathBuf> for Lock {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(path: &PathBuf) -> Result<Self, Self::Error> {
|
|
||||||
let mut config_str = String::new();
|
|
||||||
let mut config_file =
|
|
||||||
File::open(path).with_context(|| format!("Error opening {path:?} file"))?;
|
|
||||||
config_file.read_to_string(&mut config_str)?;
|
|
||||||
Ok(toml::from_str(&config_str)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,13 +18,6 @@ pub struct Arguments {
|
|||||||
/// The revive LLVM builder arguments.
|
/// The revive LLVM builder arguments.
|
||||||
#[derive(Debug, clap::Subcommand)]
|
#[derive(Debug, clap::Subcommand)]
|
||||||
pub enum Subcommand {
|
pub enum Subcommand {
|
||||||
/// Clone the branch specified in `LLVM.lock`.
|
|
||||||
Clone {
|
|
||||||
/// Clone with full commits history.
|
|
||||||
#[arg(long)]
|
|
||||||
deep: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Build the LLVM framework.
|
/// Build the LLVM framework.
|
||||||
Build {
|
Build {
|
||||||
/// LLVM build type (`Debug`, `Release`, `RelWithDebInfo`, or `MinSizeRel`).
|
/// LLVM build type (`Debug`, `Release`, `RelWithDebInfo`, or `MinSizeRel`).
|
||||||
@@ -77,12 +70,8 @@ pub enum Subcommand {
|
|||||||
enable_valgrind: bool,
|
enable_valgrind: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Checkout the branch specified in `LLVM.lock`.
|
/// Install emsdk
|
||||||
Checkout {
|
Emsdk,
|
||||||
/// Remove all artifacts preventing the checkout (removes all local changes!).
|
|
||||||
#[arg(long)]
|
|
||||||
force: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Clean the build artifacts.
|
/// Clean the build artifacts.
|
||||||
Clean,
|
Clean,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
pub(crate) mod arguments;
|
pub(crate) mod arguments;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
@@ -29,13 +28,6 @@ fn main_inner() -> anyhow::Result<()> {
|
|||||||
revive_llvm_builder::utils::directory_target_llvm(arguments.target_env);
|
revive_llvm_builder::utils::directory_target_llvm(arguments.target_env);
|
||||||
|
|
||||||
match arguments.subcommand {
|
match arguments.subcommand {
|
||||||
Subcommand::Clone { deep } => {
|
|
||||||
let lock = revive_llvm_builder::Lock::try_from(&PathBuf::from(
|
|
||||||
revive_llvm_builder::lock::LLVM_LOCK_DEFAULT_PATH,
|
|
||||||
))?;
|
|
||||||
revive_llvm_builder::clone(lock, deep, arguments.target_env)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Subcommand::Build {
|
Subcommand::Build {
|
||||||
build_type,
|
build_type,
|
||||||
targets,
|
targets,
|
||||||
@@ -50,6 +42,8 @@ fn main_inner() -> anyhow::Result<()> {
|
|||||||
sanitizer,
|
sanitizer,
|
||||||
enable_valgrind,
|
enable_valgrind,
|
||||||
} => {
|
} => {
|
||||||
|
revive_llvm_builder::init(false)?;
|
||||||
|
|
||||||
let mut targets = targets
|
let mut targets = targets
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|target| revive_llvm_builder::Platform::from_str(target.as_str()))
|
.map(|target| revive_llvm_builder::Platform::from_str(target.as_str()))
|
||||||
@@ -107,11 +101,8 @@ fn main_inner() -> anyhow::Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Subcommand::Checkout { force } => {
|
Subcommand::Emsdk => {
|
||||||
let lock = revive_llvm_builder::Lock::try_from(&PathBuf::from(
|
revive_llvm_builder::init(true)?;
|
||||||
revive_llvm_builder::lock::LLVM_LOCK_DEFAULT_PATH,
|
|
||||||
))?;
|
|
||||||
revive_llvm_builder::checkout(lock, force)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Subcommand::Clean => {
|
Subcommand::Clean => {
|
||||||
|
|||||||
@@ -4,16 +4,10 @@ use std::process::Command;
|
|||||||
|
|
||||||
use assert_cmd::{cargo, prelude::*};
|
use assert_cmd::{cargo, prelude::*};
|
||||||
|
|
||||||
/// This test verifies that the LLVM repository can be successfully cloned, built, and cleaned.
|
/// This test verifies that the LLVM repository can be successfully built and cleaned.
|
||||||
#[test]
|
#[test]
|
||||||
fn clone_build_and_clean() -> anyhow::Result<()> {
|
fn build_and_clean() -> anyhow::Result<()> {
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
let test_dir = common::TestDir::new()?;
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("clone")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
Command::new(cargo::cargo_bin!("revive-llvm"))
|
||||||
.current_dir(test_dir.path())
|
.current_dir(test_dir.path())
|
||||||
@@ -40,18 +34,12 @@ fn clone_build_and_clean() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This test verifies that the LLVM repository can be successfully cloned, built, and cleaned
|
/// This test verifies that the LLVM repository can be successfully built and cleaned
|
||||||
/// with 2-staged build using MUSL as sysroot.
|
/// with 2-staged build using MUSL as sysroot.
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn clone_build_and_clean_musl() -> anyhow::Result<()> {
|
fn build_and_clean_musl() -> anyhow::Result<()> {
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
let test_dir = common::TestDir::new()?;
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.arg("clone")
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
Command::new(cargo::cargo_bin!("revive-llvm"))
|
||||||
.current_dir(test_dir.path())
|
.current_dir(test_dir.path())
|
||||||
@@ -84,18 +72,12 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This test verifies that the LLVM repository can be successfully cloned and built in debug mode
|
/// This test verifies that the LLVM repository can be successfully built in debug mode
|
||||||
/// with tests and coverage enabled.
|
/// with tests and coverage enabled.
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
|
fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
let test_dir = common::TestDir::new()?;
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("clone")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
Command::new(cargo::cargo_bin!("revive-llvm"))
|
||||||
.current_dir(test_dir.path())
|
.current_dir(test_dir.path())
|
||||||
@@ -118,13 +100,7 @@ fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn build_with_sanitizers() -> anyhow::Result<()> {
|
fn build_with_sanitizers() -> anyhow::Result<()> {
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
let test_dir = common::TestDir::new()?;
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("clone")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
Command::new(cargo::cargo_bin!("revive-llvm"))
|
||||||
.current_dir(test_dir.path())
|
.current_dir(test_dir.path())
|
||||||
@@ -141,27 +117,28 @@ fn build_with_sanitizers() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests the clone, build, and clean process of the LLVM repository for the emscripten target.
|
/// Tests the build and clean process of the LLVM repository for the emscripten target.
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn clone_build_and_clean_emscripten() -> anyhow::Result<()> {
|
fn build_and_clean_emscripten() -> anyhow::Result<()> {
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
let test_dir = common::TestDir::new()?;
|
||||||
let command = Command::new(cargo::cargo_bin!("revive-llvm"));
|
let command = Command::new(cargo::cargo_bin!("revive-llvm"));
|
||||||
let program = command.get_program().to_string_lossy();
|
let program = command.get_program().to_string_lossy();
|
||||||
|
let path = test_dir.path();
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
Command::new(cargo::cargo_bin!("revive-llvm"))
|
||||||
.current_dir(test_dir.path())
|
.current_dir(path)
|
||||||
.arg("clone")
|
.arg("build")
|
||||||
|
.arg("--llvm-projects")
|
||||||
|
.arg("clang")
|
||||||
|
.arg("--llvm-projects")
|
||||||
|
.arg("lld")
|
||||||
.assert()
|
.assert()
|
||||||
.success();
|
.success();
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
Command::new(cargo::cargo_bin!("revive-llvm"))
|
||||||
.current_dir(test_dir.path())
|
.current_dir(path)
|
||||||
.arg("build")
|
.arg("emsdk")
|
||||||
.arg("--llvm-projects")
|
|
||||||
.arg("lld")
|
|
||||||
.arg("--llvm-projects")
|
|
||||||
.arg("clang")
|
|
||||||
.assert()
|
.assert()
|
||||||
.success();
|
.success();
|
||||||
|
|
||||||
@@ -170,22 +147,20 @@ fn clone_build_and_clean_emscripten() -> anyhow::Result<()> {
|
|||||||
// `cd {} && . ./emsdk_env.sh && cd ..` helps the script to locate `emsdk.py`
|
// `cd {} && . ./emsdk_env.sh && cd ..` helps the script to locate `emsdk.py`
|
||||||
// @see https://github.com/emscripten-core/emsdk/blob/9dbdc4b3437750b85d16931c7c801bb71a782122/emsdk_env.sh#L61-L69
|
// @see https://github.com/emscripten-core/emsdk/blob/9dbdc4b3437750b85d16931c7c801bb71a782122/emsdk_env.sh#L61-L69
|
||||||
let emsdk_wrapped_build_command = format!(
|
let emsdk_wrapped_build_command = format!(
|
||||||
"{program} --target-env emscripten clone && \
|
"cd {} && . ./emsdk_env.sh && cd .. && \
|
||||||
cd {} && . ./emsdk_env.sh && cd .. && \
|
|
||||||
{program} --target-env emscripten build --llvm-projects lld",
|
{program} --target-env emscripten build --llvm-projects lld",
|
||||||
revive_llvm_builder::LLVMPath::DIRECTORY_EMSDK_SOURCE,
|
revive_llvm_builder::LLVMPath::DIRECTORY_EMSDK_SOURCE,
|
||||||
);
|
);
|
||||||
|
|
||||||
Command::new("sh")
|
Command::new("sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(emsdk_wrapped_build_command)
|
.arg(emsdk_wrapped_build_command)
|
||||||
.current_dir(test_dir.path())
|
.current_dir(path)
|
||||||
.assert()
|
.assert()
|
||||||
.success();
|
.success();
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
Command::new(cargo::cargo_bin!("revive-llvm"))
|
||||||
.arg("clean")
|
.arg("clean")
|
||||||
.current_dir(test_dir.path())
|
.current_dir(path)
|
||||||
.assert()
|
.assert()
|
||||||
.success();
|
.success();
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
pub mod common;
|
|
||||||
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use assert_cmd::{cargo, prelude::*};
|
|
||||||
|
|
||||||
/// This test verifies that after cloning the LLVM repository, checking out a specific branch
|
|
||||||
/// or reference works as expected.
|
|
||||||
#[test]
|
|
||||||
fn checkout_after_clone() -> anyhow::Result<()> {
|
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("clone")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("checkout")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This test verifies that after cloning the LLVM repository, checking out a specific branch
|
|
||||||
/// or reference with the `--force` option works as expected.
|
|
||||||
#[test]
|
|
||||||
fn force_checkout() -> anyhow::Result<()> {
|
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("clone")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("checkout")
|
|
||||||
.arg("--force")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
pub mod common;
|
|
||||||
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use assert_cmd::{cargo, prelude::*};
|
|
||||||
|
|
||||||
/// This test verifies that the LLVM repository can be successfully cloned using a specific branch
|
|
||||||
/// and reference.
|
|
||||||
#[test]
|
|
||||||
fn clone() -> anyhow::Result<()> {
|
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("clone")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This test verifies that the LLVM repository can be successfully cloned using a specific branch
|
|
||||||
/// and reference with --deep option.
|
|
||||||
#[test]
|
|
||||||
fn clone_deep() -> anyhow::Result<()> {
|
|
||||||
let test_dir = common::TestDir::with_lockfile(None)?;
|
|
||||||
|
|
||||||
Command::new(cargo::cargo_bin!("revive-llvm"))
|
|
||||||
.current_dir(test_dir.path())
|
|
||||||
.arg("clone")
|
|
||||||
.arg("--deep")
|
|
||||||
.assert()
|
|
||||||
.success();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,51 @@
|
|||||||
use assert_fs::fixture::FileWriteStr;
|
use assert_fs::TempDir;
|
||||||
|
|
||||||
pub const REVIVE_LLVM: &str = "revive-llvm";
|
pub const REVIVE_LLVM: &str = "revive-llvm";
|
||||||
pub const REVIVE_LLVM_REPO_URL: &str = "https://github.com/llvm/llvm-project";
|
|
||||||
pub const REVIVE_LLVM_REPO_TEST_BRANCH: &str = "release/18.x";
|
|
||||||
|
|
||||||
pub struct TestDir {
|
pub struct TestDir {
|
||||||
_lockfile: assert_fs::NamedTempFile,
|
_tempdir: TempDir,
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a temporary lock file for testing.
|
/// Creates a temporary directory for testing with submodule setup.
|
||||||
impl TestDir {
|
impl TestDir {
|
||||||
pub fn with_lockfile(reference: Option<String>) -> anyhow::Result<Self> {
|
pub fn new() -> anyhow::Result<Self> {
|
||||||
let file =
|
let tempdir = TempDir::new()?;
|
||||||
assert_fs::NamedTempFile::new(revive_llvm_builder::lock::LLVM_LOCK_DEFAULT_PATH)?;
|
let tmppath = tempdir.path();
|
||||||
let lock = revive_llvm_builder::Lock {
|
|
||||||
url: REVIVE_LLVM_REPO_URL.to_string(),
|
// Initialize a git repo and add the LLVM submodule
|
||||||
branch: REVIVE_LLVM_REPO_TEST_BRANCH.to_string(),
|
std::process::Command::new("git")
|
||||||
r#ref: reference,
|
.args(["init"])
|
||||||
};
|
.current_dir(tmppath)
|
||||||
file.write_str(toml::to_string(&lock)?.as_str())?;
|
.output()?;
|
||||||
|
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args([
|
||||||
|
"submodule",
|
||||||
|
"add",
|
||||||
|
"-b",
|
||||||
|
"release/18.x",
|
||||||
|
"https://github.com/llvm/llvm-project.git",
|
||||||
|
"llvm",
|
||||||
|
])
|
||||||
|
.current_dir(tmppath)
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args([
|
||||||
|
"submodule",
|
||||||
|
"update",
|
||||||
|
"--init",
|
||||||
|
"--recursive",
|
||||||
|
"--force",
|
||||||
|
"--depth 1",
|
||||||
|
])
|
||||||
|
.current_dir(tmppath)
|
||||||
|
.output()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
path: file
|
path: tmppath.to_path_buf(),
|
||||||
.parent()
|
_tempdir: tempdir,
|
||||||
.expect("lockfile parent dir always exists")
|
|
||||||
.into(),
|
|
||||||
_lockfile: file,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ pub struct Intrinsics<'ctx> {
|
|||||||
pub byte_swap_word: FunctionDeclaration<'ctx>,
|
pub byte_swap_word: FunctionDeclaration<'ctx>,
|
||||||
/// Performs endianness swaps on i160 values
|
/// Performs endianness swaps on i160 values
|
||||||
pub byte_swap_eth_address: FunctionDeclaration<'ctx>,
|
pub byte_swap_eth_address: FunctionDeclaration<'ctx>,
|
||||||
|
/// Counts leading zeroes.
|
||||||
|
pub count_leading_zeros: FunctionDeclaration<'ctx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> Intrinsics<'ctx> {
|
impl<'ctx> Intrinsics<'ctx> {
|
||||||
@@ -26,6 +28,9 @@ impl<'ctx> Intrinsics<'ctx> {
|
|||||||
/// The corresponding intrinsic function name.
|
/// The corresponding intrinsic function name.
|
||||||
pub const FUNCTION_BYTE_SWAP_ETH_ADDRESS: &'static str = "llvm.bswap.i160";
|
pub const FUNCTION_BYTE_SWAP_ETH_ADDRESS: &'static str = "llvm.bswap.i160";
|
||||||
|
|
||||||
|
/// The corresponding intrinsic function name.
|
||||||
|
pub const FUNCTION_COUNT_LEADING_ZEROS: &'static str = "llvm.ctlz.i256";
|
||||||
|
|
||||||
/// A shortcut constructor.
|
/// A shortcut constructor.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
llvm: &'ctx inkwell::context::Context,
|
llvm: &'ctx inkwell::context::Context,
|
||||||
@@ -53,11 +58,18 @@ impl<'ctx> Intrinsics<'ctx> {
|
|||||||
Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS,
|
Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS,
|
||||||
address_type.fn_type(&[address_type.as_basic_type_enum().into()], false),
|
address_type.fn_type(&[address_type.as_basic_type_enum().into()], false),
|
||||||
);
|
);
|
||||||
|
let count_leading_zeros = Self::declare(
|
||||||
|
llvm,
|
||||||
|
module,
|
||||||
|
Self::FUNCTION_COUNT_LEADING_ZEROS,
|
||||||
|
word_type.fn_type(&[word_type.into(), llvm.bool_type().into()], false),
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
trap,
|
trap,
|
||||||
byte_swap_word,
|
byte_swap_word,
|
||||||
byte_swap_eth_address,
|
byte_swap_eth_address,
|
||||||
|
count_leading_zeros,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,12 +97,15 @@ impl<'ctx> Intrinsics<'ctx> {
|
|||||||
let word_type = llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32);
|
let word_type = llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32);
|
||||||
|
|
||||||
match name {
|
match name {
|
||||||
name if name == Self::FUNCTION_BYTE_SWAP_WORD => vec![word_type.as_basic_type_enum()],
|
_ if name == Self::FUNCTION_BYTE_SWAP_WORD => vec![word_type.as_basic_type_enum()],
|
||||||
name if name == Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS => {
|
_ if name == Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS => {
|
||||||
vec![llvm
|
vec![llvm
|
||||||
.custom_width_int_type(revive_common::BIT_LENGTH_ETH_ADDRESS as u32)
|
.custom_width_int_type(revive_common::BIT_LENGTH_ETH_ADDRESS as u32)
|
||||||
.as_basic_type_enum()]
|
.as_basic_type_enum()]
|
||||||
}
|
}
|
||||||
|
_ if name == Self::FUNCTION_COUNT_LEADING_ZEROS => {
|
||||||
|
vec![word_type.as_basic_type_enum()]
|
||||||
|
}
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ impl RuntimeFunction for WordToPointer {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let block_continue = context.append_basic_block("offset_pointer_ok");
|
let block_continue = context.append_basic_block("offset_pointer_ok");
|
||||||
let block_trap = context.append_basic_block("offset_pointer_overflow");
|
let block_invalid = context.append_basic_block("offset_pointer_overflow");
|
||||||
context.build_conditional_branch(is_overflow, block_trap, block_continue)?;
|
context.build_conditional_branch(is_overflow, block_invalid, block_continue)?;
|
||||||
|
|
||||||
context.set_basic_block(block_trap);
|
context.set_basic_block(block_invalid);
|
||||||
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
|
context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]);
|
||||||
context.build_unreachable();
|
context.build_unreachable();
|
||||||
|
|
||||||
context.set_basic_block(block_continue);
|
context.set_basic_block(block_continue);
|
||||||
|
|||||||
@@ -77,6 +77,20 @@ impl RuntimeFunction for Sbrk {
|
|||||||
context.build_unreachable();
|
context.build_unreachable();
|
||||||
|
|
||||||
context.set_basic_block(offset_in_bounds_block);
|
context.set_basic_block(offset_in_bounds_block);
|
||||||
|
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,
|
||||||
|
size,
|
||||||
|
context.heap_size(),
|
||||||
|
"size_in_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 mask = context
|
let mask = context
|
||||||
.xlen_type()
|
.xlen_type()
|
||||||
.const_int(BYTE_LENGTH_WORD as u64 - 1, false);
|
.const_int(BYTE_LENGTH_WORD as u64 - 1, false);
|
||||||
@@ -88,20 +102,20 @@ impl RuntimeFunction for Sbrk {
|
|||||||
context.builder().build_not(mask, "mask_not")?,
|
context.builder().build_not(mask, "mask_not")?,
|
||||||
"memory_size",
|
"memory_size",
|
||||||
)?;
|
)?;
|
||||||
let size_in_bounds_block = context.append_basic_block("size_in_bounds");
|
let total_size_in_bounds_block = context.append_basic_block("total_size_in_bounds");
|
||||||
let is_size_out_of_bounds = context.builder().build_int_compare(
|
let is_total_size_out_of_bounds = context.builder().build_int_compare(
|
||||||
inkwell::IntPredicate::UGT,
|
inkwell::IntPredicate::UGT,
|
||||||
memory_size,
|
memory_size,
|
||||||
context.heap_size(),
|
context.heap_size(),
|
||||||
"size_out_of_bounds",
|
"size_out_of_bounds",
|
||||||
)?;
|
)?;
|
||||||
context.build_conditional_branch(
|
context.build_conditional_branch(
|
||||||
is_size_out_of_bounds,
|
is_total_size_out_of_bounds,
|
||||||
trap_block,
|
trap_block,
|
||||||
size_in_bounds_block,
|
total_size_in_bounds_block,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
context.set_basic_block(size_in_bounds_block);
|
context.set_basic_block(total_size_in_bounds_block);
|
||||||
let new_size_block = context.append_basic_block("new_size");
|
let new_size_block = context.append_basic_block("new_size");
|
||||||
let is_new_size = context.builder().build_int_compare(
|
let is_new_size = context.builder().build_int_compare(
|
||||||
inkwell::IntPredicate::UGT,
|
inkwell::IntPredicate::UGT,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl<'ctx> Global<'ctx> {
|
|||||||
.add_global(r#type, Some(address_space.into()), name);
|
.add_global(r#type, Some(address_space.into()), name);
|
||||||
let global = Self { r#type, value };
|
let global = Self { r#type, value };
|
||||||
|
|
||||||
global.value.set_linkage(inkwell::module::Linkage::External);
|
global.value.set_linkage(inkwell::module::Linkage::Internal);
|
||||||
global
|
global
|
||||||
.value
|
.value
|
||||||
.set_visibility(inkwell::GlobalVisibility::Default);
|
.set_visibility(inkwell::GlobalVisibility::Default);
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl<'ctx> Context<'ctx> {
|
|||||||
module
|
module
|
||||||
.get_function(import)
|
.get_function(import)
|
||||||
.unwrap_or_else(|| panic!("{import} import should be declared"))
|
.unwrap_or_else(|| panic!("{import} import should be declared"))
|
||||||
.set_linkage(inkwell::module::Linkage::External);
|
.set_linkage(inkwell::module::Linkage::Internal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,6 +256,22 @@ impl<'ctx> Context<'ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes a new dummy LLVM context.
|
||||||
|
///
|
||||||
|
/// Omits the LLVM module initialization; use this only in tests and benchmarks.
|
||||||
|
pub fn new_dummy(
|
||||||
|
llvm: &'ctx inkwell::context::Context,
|
||||||
|
optimizer_settings: OptimizerSettings,
|
||||||
|
) -> Self {
|
||||||
|
Self::new(
|
||||||
|
llvm,
|
||||||
|
llvm.create_module("dummy"),
|
||||||
|
Optimizer::new(optimizer_settings),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds the LLVM IR module, returning the build artifacts.
|
/// Builds the LLVM IR module, returning the build artifacts.
|
||||||
pub fn build(
|
pub fn build(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,33 +1,21 @@
|
|||||||
//! The LLVM IR generator context tests.
|
//! The LLVM IR generator context tests.
|
||||||
|
|
||||||
use crate::optimizer::settings::Settings as OptimizerSettings;
|
use crate::optimizer::settings::Settings as OptimizerSettings;
|
||||||
use crate::optimizer::Optimizer;
|
|
||||||
use crate::polkavm::context::attribute::Attribute;
|
use crate::polkavm::context::attribute::Attribute;
|
||||||
use crate::polkavm::context::Context;
|
use crate::polkavm::context::Context;
|
||||||
use crate::PolkaVMTarget;
|
use crate::PolkaVMTarget;
|
||||||
|
|
||||||
pub fn create_context(
|
/// Initializes the LLVM compiler backend.
|
||||||
llvm: &inkwell::context::Context,
|
fn initialize_llvm() {
|
||||||
optimizer_settings: OptimizerSettings,
|
|
||||||
) -> Context<'_> {
|
|
||||||
crate::initialize_llvm(PolkaVMTarget::PVM, "resolc", Default::default());
|
crate::initialize_llvm(PolkaVMTarget::PVM, "resolc", Default::default());
|
||||||
|
|
||||||
let module = llvm.create_module("test");
|
|
||||||
let optimizer = Optimizer::new(optimizer_settings);
|
|
||||||
|
|
||||||
Context::new(
|
|
||||||
llvm,
|
|
||||||
module,
|
|
||||||
optimizer,
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn check_attribute_null_pointer_is_invalid() {
|
pub fn check_attribute_null_pointer_is_invalid() {
|
||||||
|
initialize_llvm();
|
||||||
|
|
||||||
let llvm = inkwell::context::Context::create();
|
let llvm = inkwell::context::Context::create();
|
||||||
let mut context = create_context(&llvm, OptimizerSettings::cycles());
|
let mut context = Context::new_dummy(&llvm, OptimizerSettings::cycles());
|
||||||
|
|
||||||
let function = context
|
let function = context
|
||||||
.add_function(
|
.add_function(
|
||||||
@@ -51,8 +39,10 @@ pub fn check_attribute_null_pointer_is_invalid() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn check_attribute_optimize_for_size_mode_3() {
|
pub fn check_attribute_optimize_for_size_mode_3() {
|
||||||
|
initialize_llvm();
|
||||||
|
|
||||||
let llvm = inkwell::context::Context::create();
|
let llvm = inkwell::context::Context::create();
|
||||||
let mut context = create_context(&llvm, OptimizerSettings::cycles());
|
let mut context = Context::new_dummy(&llvm, OptimizerSettings::cycles());
|
||||||
|
|
||||||
let function = context
|
let function = context
|
||||||
.add_function(
|
.add_function(
|
||||||
@@ -76,8 +66,10 @@ pub fn check_attribute_optimize_for_size_mode_3() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn check_attribute_optimize_for_size_mode_z() {
|
pub fn check_attribute_optimize_for_size_mode_z() {
|
||||||
|
initialize_llvm();
|
||||||
|
|
||||||
let llvm = inkwell::context::Context::create();
|
let llvm = inkwell::context::Context::create();
|
||||||
let mut context = create_context(&llvm, OptimizerSettings::size());
|
let mut context = Context::new_dummy(&llvm, OptimizerSettings::size());
|
||||||
|
|
||||||
let function = context
|
let function = context
|
||||||
.add_function(
|
.add_function(
|
||||||
@@ -101,8 +93,10 @@ pub fn check_attribute_optimize_for_size_mode_z() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn check_attribute_min_size_mode_3() {
|
pub fn check_attribute_min_size_mode_3() {
|
||||||
|
initialize_llvm();
|
||||||
|
|
||||||
let llvm = inkwell::context::Context::create();
|
let llvm = inkwell::context::Context::create();
|
||||||
let mut context = create_context(&llvm, OptimizerSettings::cycles());
|
let mut context = Context::new_dummy(&llvm, OptimizerSettings::cycles());
|
||||||
|
|
||||||
let function = context
|
let function = context
|
||||||
.add_function(
|
.add_function(
|
||||||
@@ -126,8 +120,10 @@ pub fn check_attribute_min_size_mode_3() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn check_attribute_min_size_mode_z() {
|
pub fn check_attribute_min_size_mode_z() {
|
||||||
|
initialize_llvm();
|
||||||
|
|
||||||
let llvm = inkwell::context::Context::create();
|
let llvm = inkwell::context::Context::create();
|
||||||
let mut context = create_context(&llvm, OptimizerSettings::size());
|
let mut context = Context::new_dummy(&llvm, OptimizerSettings::size());
|
||||||
|
|
||||||
let function = context
|
let function = context
|
||||||
.add_function(
|
.add_function(
|
||||||
|
|||||||
@@ -261,3 +261,20 @@ pub fn byte<'ctx>(
|
|||||||
|
|
||||||
Ok(byte.as_basic_value_enum())
|
Ok(byte.as_basic_value_enum())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Translates the CLZ instruction.
|
||||||
|
pub fn count_leading_zeros<'ctx>(
|
||||||
|
context: &mut Context<'ctx>,
|
||||||
|
value: inkwell::values::IntValue<'ctx>,
|
||||||
|
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
|
||||||
|
Ok(context
|
||||||
|
.builder()
|
||||||
|
.build_call(
|
||||||
|
context.intrinsics().count_leading_zeros.function_value(),
|
||||||
|
&[value.into(), context.bool_const(false).into()],
|
||||||
|
"clz",
|
||||||
|
)?
|
||||||
|
.try_as_basic_value()
|
||||||
|
.left()
|
||||||
|
.expect("the llvm.ctlz should return a value"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ use inkwell::values::BasicValue;
|
|||||||
|
|
||||||
use crate::polkavm::context::Context;
|
use crate::polkavm::context::Context;
|
||||||
|
|
||||||
const STATIC_CALL_FLAG: u32 = 0b0001_0000;
|
const STATIC_CALL_FLAG: u64 = 0b0001_0000;
|
||||||
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
|
const REENTRANT_CALL_FLAG: u64 = 0b0000_1000;
|
||||||
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
|
|
||||||
|
|
||||||
/// Translates a contract call.
|
/// Translates a contract call.
|
||||||
pub fn call<'ctx>(
|
pub fn call<'ctx>(
|
||||||
@@ -38,33 +37,12 @@ pub fn call<'ctx>(
|
|||||||
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
|
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
|
||||||
context.build_store(output_length_pointer, output_length)?;
|
context.build_store(output_length_pointer, output_length)?;
|
||||||
|
|
||||||
let (flags, deposit_limit_value) = if static_call {
|
let flags = if static_call {
|
||||||
let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
|
REENTRANT_CALL_FLAG | STATIC_CALL_FLAG
|
||||||
(
|
|
||||||
context.xlen_type().const_int(flags as u64, false),
|
|
||||||
context.word_type().const_zero(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
call_reentrancy_heuristic(context, gas, input_length, output_length)?
|
REENTRANT_CALL_FLAG
|
||||||
};
|
};
|
||||||
|
|
||||||
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
|
|
||||||
context.build_store(deposit_pointer, deposit_limit_value)?;
|
|
||||||
|
|
||||||
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
|
||||||
context.builder(),
|
|
||||||
context.llvm(),
|
|
||||||
flags,
|
|
||||||
address_pointer.to_int(context),
|
|
||||||
"address_and_callee",
|
|
||||||
)?;
|
|
||||||
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
|
||||||
context.builder(),
|
|
||||||
context.llvm(),
|
|
||||||
deposit_pointer.to_int(context),
|
|
||||||
value_pointer.to_int(context),
|
|
||||||
"deposit_and_value",
|
|
||||||
)?;
|
|
||||||
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
||||||
context.builder(),
|
context.builder(),
|
||||||
context.llvm(),
|
context.llvm(),
|
||||||
@@ -85,10 +63,10 @@ pub fn call<'ctx>(
|
|||||||
.build_runtime_call(
|
.build_runtime_call(
|
||||||
name,
|
name,
|
||||||
&[
|
&[
|
||||||
flags_and_callee.into(),
|
context.xlen_type().const_int(flags, false).into(),
|
||||||
context.register_type().const_all_ones().into(),
|
address_pointer.to_int(context).into(),
|
||||||
context.register_type().const_all_ones().into(),
|
value_pointer.to_int(context).into(),
|
||||||
deposit_and_value.into(),
|
clip_call_gas(context, gas)?,
|
||||||
input_data.into(),
|
input_data.into(),
|
||||||
output_data.into(),
|
output_data.into(),
|
||||||
],
|
],
|
||||||
@@ -111,7 +89,7 @@ pub fn call<'ctx>(
|
|||||||
|
|
||||||
pub fn delegate_call<'ctx>(
|
pub fn delegate_call<'ctx>(
|
||||||
context: &mut Context<'ctx>,
|
context: &mut Context<'ctx>,
|
||||||
_gas: inkwell::values::IntValue<'ctx>,
|
gas: inkwell::values::IntValue<'ctx>,
|
||||||
address: inkwell::values::IntValue<'ctx>,
|
address: inkwell::values::IntValue<'ctx>,
|
||||||
input_offset: inkwell::values::IntValue<'ctx>,
|
input_offset: inkwell::values::IntValue<'ctx>,
|
||||||
input_length: inkwell::values::IntValue<'ctx>,
|
input_length: inkwell::values::IntValue<'ctx>,
|
||||||
@@ -132,18 +110,6 @@ pub fn delegate_call<'ctx>(
|
|||||||
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
|
let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
|
||||||
context.build_store(output_length_pointer, output_length)?;
|
context.build_store(output_length_pointer, output_length)?;
|
||||||
|
|
||||||
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
|
|
||||||
context.build_store(deposit_pointer, context.word_type().const_all_ones())?;
|
|
||||||
|
|
||||||
let flags = context.xlen_type().const_int(0u64, false);
|
|
||||||
|
|
||||||
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
|
||||||
context.builder(),
|
|
||||||
context.llvm(),
|
|
||||||
flags,
|
|
||||||
address_pointer.to_int(context),
|
|
||||||
"address_and_callee",
|
|
||||||
)?;
|
|
||||||
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
|
||||||
context.builder(),
|
context.builder(),
|
||||||
context.llvm(),
|
context.llvm(),
|
||||||
@@ -164,10 +130,9 @@ pub fn delegate_call<'ctx>(
|
|||||||
.build_runtime_call(
|
.build_runtime_call(
|
||||||
name,
|
name,
|
||||||
&[
|
&[
|
||||||
flags_and_callee.into(),
|
context.xlen_type().const_int(0u64, false).into(),
|
||||||
context.register_type().const_all_ones().into(),
|
address_pointer.to_int(context).into(),
|
||||||
context.register_type().const_all_ones().into(),
|
clip_call_gas(context, gas)?,
|
||||||
deposit_pointer.to_int(context).into(),
|
|
||||||
input_data.into(),
|
input_data.into(),
|
||||||
output_data.into(),
|
output_data.into(),
|
||||||
],
|
],
|
||||||
@@ -201,82 +166,22 @@ pub fn linker_symbol<'ctx>(
|
|||||||
context.build_load_address(context.get_global(path)?.into())
|
context.build_load_address(context.get_global(path)?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Solidity `address.transfer` and `address.send` call detection heuristic.
|
/// The runtime implements gas as `u64` so we clip the stipend to `u64::MAX`.
|
||||||
///
|
fn clip_call_gas<'ctx>(
|
||||||
/// # Why
|
context: &Context<'ctx>,
|
||||||
/// This heuristic is an additional security feature to guard against re-entrancy attacks
|
|
||||||
/// in case contract authors violate Solidity best practices and use `address.transfer` or
|
|
||||||
/// `address.send`.
|
|
||||||
/// While contract authors are supposed to never use `address.transfer` or `address.send`,
|
|
||||||
/// for a small cost we can be extra defensive about it.
|
|
||||||
///
|
|
||||||
/// # How
|
|
||||||
/// The gas stipend emitted by solc for `transfer` and `send` is not static, thus:
|
|
||||||
/// - Dynamically allow re-entrancy only for calls considered not transfer or send.
|
|
||||||
/// - Detected balance transfers will supply 0 deposit limit instead of `u256::MAX`.
|
|
||||||
///
|
|
||||||
/// Calls are considered transfer or send if:
|
|
||||||
/// - (Input length | Output lenght) == 0;
|
|
||||||
/// - Gas <= 2300;
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// The call flags xlen `IntValue` and the deposit limit word `IntValue`.
|
|
||||||
fn call_reentrancy_heuristic<'ctx>(
|
|
||||||
context: &mut Context<'ctx>,
|
|
||||||
gas: inkwell::values::IntValue<'ctx>,
|
gas: inkwell::values::IntValue<'ctx>,
|
||||||
input_length: inkwell::values::IntValue<'ctx>,
|
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> {
|
||||||
output_length: inkwell::values::IntValue<'ctx>,
|
let builder = context.builder();
|
||||||
) -> anyhow::Result<(
|
|
||||||
inkwell::values::IntValue<'ctx>,
|
let clipped = context.register_type().const_all_ones();
|
||||||
inkwell::values::IntValue<'ctx>,
|
let is_overflow = builder.build_int_compare(
|
||||||
)> {
|
inkwell::IntPredicate::UGT,
|
||||||
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
|
|
||||||
let input_length_or_output_length =
|
|
||||||
context
|
|
||||||
.builder()
|
|
||||||
.build_or(input_length, output_length, "input_length_or_output_length")?;
|
|
||||||
let is_no_input_no_output = context.builder().build_int_compare(
|
|
||||||
inkwell::IntPredicate::EQ,
|
|
||||||
context.xlen_type().const_zero(),
|
|
||||||
input_length_or_output_length,
|
|
||||||
"is_no_input_no_output",
|
|
||||||
)?;
|
|
||||||
let gas_stipend = context
|
|
||||||
.word_type()
|
|
||||||
.const_int(SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD, false);
|
|
||||||
let is_gas_stipend_for_transfer_or_send = context.builder().build_int_compare(
|
|
||||||
inkwell::IntPredicate::ULE,
|
|
||||||
gas,
|
gas,
|
||||||
gas_stipend,
|
builder.build_int_z_extend(clipped, context.word_type(), "gas_clipped")?,
|
||||||
"is_gas_stipend_for_transfer_or_send",
|
"is_gas_overflow",
|
||||||
)?;
|
)?;
|
||||||
let is_balance_transfer = context.builder().build_and(
|
let truncated = builder.build_int_truncate(gas, context.register_type(), "gas_truncated")?;
|
||||||
is_no_input_no_output,
|
let call_gas = builder.build_select(is_overflow, clipped, truncated, "call_gas")?;
|
||||||
is_gas_stipend_for_transfer_or_send,
|
|
||||||
"is_balance_transfer",
|
|
||||||
)?;
|
|
||||||
let is_regular_call = context
|
|
||||||
.builder()
|
|
||||||
.build_not(is_balance_transfer, "is_balance_transfer_inverted")?;
|
|
||||||
|
|
||||||
// Call flag: Left shift the heuristic boolean value.
|
Ok(call_gas)
|
||||||
let is_regular_call_xlen = context.builder().build_int_z_extend(
|
|
||||||
is_regular_call,
|
|
||||||
context.xlen_type(),
|
|
||||||
"is_balance_transfer_xlen",
|
|
||||||
)?;
|
|
||||||
let call_flags = context.builder().build_left_shift(
|
|
||||||
is_regular_call_xlen,
|
|
||||||
context.xlen_type().const_int(3, false),
|
|
||||||
"flags",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Deposit limit value: Sign-extended the heuristic boolean value.
|
|
||||||
let deposit_limit_value = context.builder().build_int_s_extend(
|
|
||||||
is_regular_call,
|
|
||||||
context.word_type(),
|
|
||||||
"deposit_limit_value",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok((call_flags, deposit_limit_value))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,12 +52,14 @@ pub fn stop(context: &mut Context) -> anyhow::Result<()> {
|
|||||||
/// Translates the `invalid` instruction.
|
/// Translates the `invalid` instruction.
|
||||||
/// Burns all gas using an out-of-bounds memory store, causing a panic.
|
/// Burns all gas using an out-of-bounds memory store, causing a panic.
|
||||||
pub fn invalid(context: &mut Context) -> anyhow::Result<()> {
|
pub fn invalid(context: &mut Context) -> anyhow::Result<()> {
|
||||||
crate::polkavm::evm::memory::store(
|
let invalid_block = context.append_basic_block("explicit_invalid");
|
||||||
context,
|
context.build_unconditional_branch(invalid_block);
|
||||||
context.word_type().const_all_ones(),
|
context.set_basic_block(invalid_block);
|
||||||
context.word_const(0),
|
context.build_runtime_call(revive_runtime_api::polkavm_imports::INVALID, &[]);
|
||||||
)?;
|
context.build_unreachable();
|
||||||
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
|
|
||||||
|
context.set_basic_block(context.append_basic_block("dead_code"));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ impl TargetMachine {
|
|||||||
|
|
||||||
/// LLVM target features.
|
/// LLVM target features.
|
||||||
pub const VM_FEATURES: &'static str =
|
pub const VM_FEATURES: &'static str =
|
||||||
"+e,+m,+a,+c,+zbb,+auipc-addi-fusion,+ld-add-fusion,+lui-addi-fusion,+xtheadcondmov";
|
"+e,+m,+a,+c,+zbb,+auipc-addi-fusion,+ld-add-fusion,+lui-addi-fusion,+xtheadcondmov,+relax";
|
||||||
|
|
||||||
/// A shortcut constructor.
|
/// A shortcut constructor.
|
||||||
/// A separate instance for every optimization level is created.
|
/// A separate instance for every optimization level is created.
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Benchmarks
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Benchmark Results](#benchmark-results)
|
||||||
|
- [Empty](#empty)
|
||||||
|
- [Dependency](#dependency)
|
||||||
|
- [LargeDivRem](#largedivrem)
|
||||||
|
- [Memset (`--yul`)](#memset-(`--yul`))
|
||||||
|
- [Return (`--yul`)](#return-(`--yul`))
|
||||||
|
- [Multiple Contracts (`--standard-json`)](#multiple-contracts-(`--standard-json`))
|
||||||
|
|
||||||
|
## Benchmark Results
|
||||||
|
|
||||||
|
### Empty
|
||||||
|
|
||||||
|
| | `resolc` | `solc` |
|
||||||
|
|:-------|:-------------------------|:------------------------------- |
|
||||||
|
| | `62.77 ms` (✅ **1.00x**) | `9.63 ms` (🚀 **6.52x faster**) |
|
||||||
|
|
||||||
|
### Dependency
|
||||||
|
|
||||||
|
| | `resolc` | `solc` |
|
||||||
|
|:-------|:--------------------------|:-------------------------------- |
|
||||||
|
| | `142.28 ms` (✅ **1.00x**) | `57.57 ms` (🚀 **2.47x faster**) |
|
||||||
|
|
||||||
|
### LargeDivRem
|
||||||
|
|
||||||
|
| | `resolc` | `solc` |
|
||||||
|
|:-------|:--------------------------|:-------------------------------- |
|
||||||
|
| | `110.80 ms` (✅ **1.00x**) | `20.96 ms` (🚀 **5.29x faster**) |
|
||||||
|
|
||||||
|
### Memset (`--yul`)
|
||||||
|
|
||||||
|
| | `resolc` | `solc` |
|
||||||
|
|:-------|:-------------------------|:------------------------------- |
|
||||||
|
| | `58.39 ms` (✅ **1.00x**) | `8.84 ms` (🚀 **6.61x faster**) |
|
||||||
|
|
||||||
|
### Return (`--yul`)
|
||||||
|
|
||||||
|
| | `resolc` | `solc` |
|
||||||
|
|:-------|:-------------------------|:------------------------------- |
|
||||||
|
| | `52.83 ms` (✅ **1.00x**) | `8.04 ms` (🚀 **6.57x faster**) |
|
||||||
|
|
||||||
|
### Multiple Contracts (`--standard-json`)
|
||||||
|
|
||||||
|
| | `resolc` | `solc` |
|
||||||
|
|:-------|:-----------------------|:--------------------------------- |
|
||||||
|
| | `1.52 s` (✅ **1.00x**) | `623.91 ms` (🚀 **2.44x faster**) |
|
||||||
|
|
||||||
|
---
|
||||||
|
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ anyhow = { workspace = true }
|
|||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
hex = { workspace = true }
|
hex = { workspace = true }
|
||||||
inkwell = { workspace = true }
|
inkwell = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
path-slash = { workspace = true }
|
path-slash = { workspace = true }
|
||||||
rayon = { workspace = true, optional = true }
|
rayon = { workspace = true, optional = true }
|
||||||
@@ -47,9 +48,13 @@ inkwell = { workspace = true, features = ["target-riscv", "llvm18-1-no-llvm-link
|
|||||||
git2 = { workspace = true, default-features = false }
|
git2 = { workspace = true, default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
criterion = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
parallel = ["rayon", "revive-solc-json-interface/parallel"]
|
parallel = ["rayon", "revive-solc-json-interface/parallel"]
|
||||||
default = ["parallel"]
|
default = ["parallel"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "compile"
|
||||||
|
harness = false
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
//! The `resolc` compilation benchmarks.
|
||||||
|
//! The tests mimicking the commands run by these benchmarks exist in `src/tests/cli/bin.rs`.
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use criterion::{
|
||||||
|
criterion_group, criterion_main,
|
||||||
|
measurement::{Measurement, WallTime},
|
||||||
|
BenchmarkGroup, Criterion,
|
||||||
|
};
|
||||||
|
use resolc::{
|
||||||
|
self,
|
||||||
|
cli_utils::{
|
||||||
|
absolute_path, execute_command, ResolcOptSettings, SolcOptSettings, SOLIDITY_CONTRACT_PATH,
|
||||||
|
SOLIDITY_DEPENDENCY_CONTRACT_PATH, SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH,
|
||||||
|
STANDARD_JSON_CONTRACTS_PATH, YUL_MEMSET_CONTRACT_PATH, YUL_RETURN_CONTRACT_PATH,
|
||||||
|
},
|
||||||
|
SolcCompiler,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The function under test executes the `resolc` executable.
|
||||||
|
fn execute_resolc(arguments: &[&str], stdin_file_path: Option<&str>) {
|
||||||
|
execute_command(resolc::DEFAULT_EXECUTABLE_NAME, arguments, stdin_file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The function under test executes the `solc` executable.
|
||||||
|
fn execute_solc(arguments: &[&str], stdin_file_path: Option<&str>) {
|
||||||
|
execute_command(
|
||||||
|
SolcCompiler::DEFAULT_EXECUTABLE_NAME,
|
||||||
|
arguments,
|
||||||
|
stdin_file_path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group<'error, M>(c: &'error mut Criterion<M>, group_name: &str) -> BenchmarkGroup<'error, M>
|
||||||
|
where
|
||||||
|
M: Measurement,
|
||||||
|
{
|
||||||
|
c.benchmark_group(group_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(
|
||||||
|
mut group: BenchmarkGroup<'_, WallTime>,
|
||||||
|
resolc_arguments: &[&str],
|
||||||
|
solc_arguments: &[&str],
|
||||||
|
stdin_file_path: Option<&str>,
|
||||||
|
) {
|
||||||
|
group.bench_function("resolc", |b| {
|
||||||
|
b.iter(|| execute_resolc(resolc_arguments, stdin_file_path));
|
||||||
|
});
|
||||||
|
|
||||||
|
group.bench_function("solc", |b| {
|
||||||
|
b.iter(|| execute_solc(solc_arguments, stdin_file_path));
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_empty(c: &mut Criterion) {
|
||||||
|
let mut group = group(c, "Empty");
|
||||||
|
group
|
||||||
|
.sample_size(100)
|
||||||
|
.measurement_time(Duration::from_secs(8));
|
||||||
|
let path = absolute_path(SOLIDITY_CONTRACT_PATH);
|
||||||
|
let resolc_arguments = &[&path, "--bin", ResolcOptSettings::PERFORMANCE];
|
||||||
|
let solc_arguments = &[
|
||||||
|
&path,
|
||||||
|
"--bin",
|
||||||
|
"--via-ir",
|
||||||
|
"--optimize",
|
||||||
|
"--optimize-runs",
|
||||||
|
SolcOptSettings::PERFORMANCE,
|
||||||
|
];
|
||||||
|
|
||||||
|
bench(group, resolc_arguments, solc_arguments, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_dependency(c: &mut Criterion) {
|
||||||
|
let mut group = group(c, "Dependency");
|
||||||
|
group
|
||||||
|
.sample_size(50)
|
||||||
|
.measurement_time(Duration::from_secs(9));
|
||||||
|
let path = absolute_path(SOLIDITY_DEPENDENCY_CONTRACT_PATH);
|
||||||
|
let resolc_arguments = &[&path, "--bin", ResolcOptSettings::PERFORMANCE];
|
||||||
|
let solc_arguments = &[
|
||||||
|
&path,
|
||||||
|
"--bin",
|
||||||
|
"--via-ir",
|
||||||
|
"--optimize",
|
||||||
|
"--optimize-runs",
|
||||||
|
SolcOptSettings::PERFORMANCE,
|
||||||
|
];
|
||||||
|
|
||||||
|
bench(group, resolc_arguments, solc_arguments, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_large_div_rem(c: &mut Criterion) {
|
||||||
|
let mut group = group(c, "LargeDivRem");
|
||||||
|
group
|
||||||
|
.sample_size(45)
|
||||||
|
.measurement_time(Duration::from_secs(9));
|
||||||
|
let path = absolute_path(SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH);
|
||||||
|
let resolc_arguments = &[&path, "--bin", ResolcOptSettings::PERFORMANCE];
|
||||||
|
let solc_arguments = &[
|
||||||
|
&path,
|
||||||
|
"--bin",
|
||||||
|
"--via-ir",
|
||||||
|
"--optimize",
|
||||||
|
"--optimize-runs",
|
||||||
|
SolcOptSettings::PERFORMANCE,
|
||||||
|
];
|
||||||
|
|
||||||
|
bench(group, resolc_arguments, solc_arguments, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_memset(c: &mut Criterion) {
|
||||||
|
let mut group = group(c, "Memset (`--yul`)");
|
||||||
|
group
|
||||||
|
.sample_size(100)
|
||||||
|
.measurement_time(Duration::from_secs(7));
|
||||||
|
let path = absolute_path(YUL_MEMSET_CONTRACT_PATH);
|
||||||
|
let resolc_arguments = &[&path, "--yul", "--bin", ResolcOptSettings::PERFORMANCE];
|
||||||
|
let solc_arguments = &[
|
||||||
|
&path,
|
||||||
|
"--strict-assembly",
|
||||||
|
"--bin",
|
||||||
|
"--optimize",
|
||||||
|
"--optimize-runs",
|
||||||
|
SolcOptSettings::PERFORMANCE,
|
||||||
|
];
|
||||||
|
|
||||||
|
bench(group, resolc_arguments, solc_arguments, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_return(c: &mut Criterion) {
|
||||||
|
let mut group = group(c, "Return (`--yul`)");
|
||||||
|
group
|
||||||
|
.sample_size(100)
|
||||||
|
.measurement_time(Duration::from_secs(6));
|
||||||
|
let path = absolute_path(YUL_RETURN_CONTRACT_PATH);
|
||||||
|
let resolc_arguments = &[&path, "--yul", "--bin", ResolcOptSettings::PERFORMANCE];
|
||||||
|
let solc_arguments = &[
|
||||||
|
&path,
|
||||||
|
"--strict-assembly",
|
||||||
|
"--bin",
|
||||||
|
"--optimize",
|
||||||
|
"--optimize-runs",
|
||||||
|
SolcOptSettings::PERFORMANCE,
|
||||||
|
];
|
||||||
|
|
||||||
|
bench(group, resolc_arguments, solc_arguments, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench_standard_json_contracts(c: &mut Criterion) {
|
||||||
|
let mut group = group(c, "Multiple Contracts (`--standard-json`)");
|
||||||
|
group
|
||||||
|
.sample_size(20)
|
||||||
|
.measurement_time(Duration::from_secs(35));
|
||||||
|
let path = absolute_path(STANDARD_JSON_CONTRACTS_PATH);
|
||||||
|
let resolc_arguments = &["--standard-json"];
|
||||||
|
let solc_arguments = &["--standard-json"];
|
||||||
|
|
||||||
|
bench(group, resolc_arguments, solc_arguments, Some(&path));
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
name = benches;
|
||||||
|
config = Criterion::default();
|
||||||
|
targets =
|
||||||
|
bench_empty,
|
||||||
|
bench_dependency,
|
||||||
|
bench_large_div_rem,
|
||||||
|
bench_memset,
|
||||||
|
bench_return,
|
||||||
|
bench_standard_json_contracts,
|
||||||
|
);
|
||||||
|
criterion_main!(benches);
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10,28 +11,53 @@ use crate::SolcCompiler;
|
|||||||
/// The simple Solidity contract test fixture path.
|
/// The simple Solidity contract test fixture path.
|
||||||
pub const SOLIDITY_CONTRACT_PATH: &str = "src/tests/data/solidity/contract.sol";
|
pub const SOLIDITY_CONTRACT_PATH: &str = "src/tests/data/solidity/contract.sol";
|
||||||
/// The dependency Solidity contract test fixture path.
|
/// The dependency Solidity contract test fixture path.
|
||||||
pub const DEPENDENCY_CONTRACT_PATH: &str = "src/tests/data/solidity/dependency.sol";
|
pub const SOLIDITY_DEPENDENCY_CONTRACT_PATH: &str = "src/tests/data/solidity/dependency.sol";
|
||||||
|
/// The simple Solidity contract containing i256 divisions and remains
|
||||||
|
/// that should be compiled correctly.
|
||||||
|
pub const SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH: &str = "src/tests/data/solidity/large_div_rem.sol";
|
||||||
|
|
||||||
/// The simple YUL contract test fixture path.
|
/// The simple YUL contract test fixture path.
|
||||||
pub const YUL_CONTRACT_PATH: &str = "src/tests/data/yul/contract.yul";
|
pub const YUL_CONTRACT_PATH: &str = "src/tests/data/yul/contract.yul";
|
||||||
|
|
||||||
/// The memeset YUL contract test fixture path.
|
/// The memeset YUL contract test fixture path.
|
||||||
pub const YUL_MEMSET_CONTRACT_PATH: &str = "src/tests/data/yul/memset.yul";
|
pub const YUL_MEMSET_CONTRACT_PATH: &str = "src/tests/data/yul/memset.yul";
|
||||||
|
/// The return YUL contract test fixture path.
|
||||||
|
pub const YUL_RETURN_CONTRACT_PATH: &str = "src/tests/data/yul/return.yul";
|
||||||
|
|
||||||
/// The standard JSON contracts test fixture path.
|
/// The standard JSON contracts test fixture path.
|
||||||
///
|
|
||||||
pub const STANDARD_JSON_CONTRACTS_PATH: &str =
|
pub const STANDARD_JSON_CONTRACTS_PATH: &str =
|
||||||
"src/tests/data/standard_json/solidity_contracts.json";
|
"src/tests/data/standard_json/solidity_contracts.json";
|
||||||
|
/// The standard JSON no EVM codegen test fixture path.
|
||||||
/// The simple Solidity contract containing i256 divisions and remains that should be compiled
|
///
|
||||||
/// correctly
|
/// This contains EVM bytecode selection flags with provided code
|
||||||
pub const SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH: &str = "src/tests/data/solidity/large_div_rem.sol";
|
/// that doesn't compile without `viaIr`. Because we remove those
|
||||||
|
/// selection flags, it should compile fine regardless.
|
||||||
|
pub const STANDARD_JSON_NO_EVM_CODEGEN_PATH: &str =
|
||||||
|
"src/tests/data/standard_json/no_evm_codegen.json";
|
||||||
|
|
||||||
/// The `resolc` YUL mode flag.
|
/// The `resolc` YUL mode flag.
|
||||||
pub const RESOLC_YUL_FLAG: &str = "--yul";
|
pub const RESOLC_YUL_FLAG: &str = "--yul";
|
||||||
/// The `--yul` option was deprecated in Solidity 0.8.27 in favor of `--strict-assembly`.
|
/// The `--yul` option was deprecated in Solidity 0.8.27 in favor of `--strict-assembly`.
|
||||||
/// See section `--strict-assembly vs. --yul` in https://soliditylang.org/blog/2024/09/04/solidity-0.8.27-release-announcement/
|
/// See section `--strict-assembly vs. --yul` in the [release announcement](https://soliditylang.org/blog/2024/09/04/solidity-0.8.27-release-announcement/).
|
||||||
pub const SOLC_YUL_FLAG: &str = "--strict-assembly";
|
pub const SOLC_YUL_FLAG: &str = "--strict-assembly";
|
||||||
|
|
||||||
|
/// Common `resolc` CLI optimization settings.
|
||||||
|
pub struct ResolcOptSettings;
|
||||||
|
|
||||||
|
impl ResolcOptSettings {
|
||||||
|
pub const NONE: &'static str = "-O0";
|
||||||
|
pub const PERFORMANCE: &'static str = "-O3";
|
||||||
|
pub const SIZE: &'static str = "-Oz";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Common `solc` CLI optimization settings for `--optimize-runs`.
|
||||||
|
pub struct SolcOptSettings;
|
||||||
|
|
||||||
|
impl SolcOptSettings {
|
||||||
|
pub const NONE: &'static str = "0";
|
||||||
|
pub const PERFORMANCE: &'static str = "20000";
|
||||||
|
pub const SIZE: &'static str = "1";
|
||||||
|
}
|
||||||
|
|
||||||
/// The result of executing a command.
|
/// The result of executing a command.
|
||||||
pub struct CommandResult {
|
pub struct CommandResult {
|
||||||
/// The data written to `stdout`.
|
/// The data written to `stdout`.
|
||||||
@@ -44,18 +70,22 @@ pub struct CommandResult {
|
|||||||
pub code: i32,
|
pub code: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the `resolc` command with the given `arguments`.
|
||||||
pub fn execute_resolc(arguments: &[&str]) -> CommandResult {
|
pub fn execute_resolc(arguments: &[&str]) -> CommandResult {
|
||||||
execute_command("resolc", arguments, None)
|
execute_command("resolc", arguments, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the `resolc` command with the given `arguments` and file path passed to `stdin`.
|
||||||
pub fn execute_resolc_with_stdin_input(arguments: &[&str], stdin_file_path: &str) -> CommandResult {
|
pub fn execute_resolc_with_stdin_input(arguments: &[&str], stdin_file_path: &str) -> CommandResult {
|
||||||
execute_command("resolc", arguments, Some(stdin_file_path))
|
execute_command("resolc", arguments, Some(stdin_file_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the `solc` command with the given `arguments`.
|
||||||
pub fn execute_solc(arguments: &[&str]) -> CommandResult {
|
pub fn execute_solc(arguments: &[&str]) -> CommandResult {
|
||||||
execute_command(SolcCompiler::DEFAULT_EXECUTABLE_NAME, arguments, None)
|
execute_command(SolcCompiler::DEFAULT_EXECUTABLE_NAME, arguments, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes the `solc` command with the given `arguments` and file path passed to `stdin`.
|
||||||
pub fn execute_solc_with_stdin_input(arguments: &[&str], stdin_file_path: &str) -> CommandResult {
|
pub fn execute_solc_with_stdin_input(arguments: &[&str], stdin_file_path: &str) -> CommandResult {
|
||||||
execute_command(
|
execute_command(
|
||||||
SolcCompiler::DEFAULT_EXECUTABLE_NAME,
|
SolcCompiler::DEFAULT_EXECUTABLE_NAME,
|
||||||
@@ -64,12 +94,13 @@ pub fn execute_solc_with_stdin_input(arguments: &[&str], stdin_file_path: &str)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_command(
|
/// Executes the `command` with the given `arguments` and optional file path passed to `stdin`.
|
||||||
|
pub fn execute_command(
|
||||||
command: &str,
|
command: &str,
|
||||||
arguments: &[&str],
|
arguments: &[&str],
|
||||||
stdin_file_path: Option<&str>,
|
stdin_file_path: Option<&str>,
|
||||||
) -> CommandResult {
|
) -> CommandResult {
|
||||||
println!(
|
log::trace!(
|
||||||
"executing command: '{command} {}{}'",
|
"executing command: '{command} {}{}'",
|
||||||
arguments.join(" "),
|
arguments.join(" "),
|
||||||
stdin_file_path
|
stdin_file_path
|
||||||
@@ -97,10 +128,12 @@ fn execute_command(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Asserts that the exit codes of executing `solc` and `resolc` are equal.
|
||||||
pub fn assert_equal_exit_codes(solc_result: &CommandResult, resolc_result: &CommandResult) {
|
pub fn assert_equal_exit_codes(solc_result: &CommandResult, resolc_result: &CommandResult) {
|
||||||
assert_eq!(solc_result.code, resolc_result.code,);
|
assert_eq!(solc_result.code, resolc_result.code,);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Asserts that the command terminated successfully with a `0` exit code.
|
||||||
pub fn assert_command_success(result: &CommandResult, error_message_prefix: &str) {
|
pub fn assert_command_success(result: &CommandResult, error_message_prefix: &str) {
|
||||||
assert!(
|
assert!(
|
||||||
result.success,
|
result.success,
|
||||||
@@ -111,6 +144,7 @@ pub fn assert_command_success(result: &CommandResult, error_message_prefix: &str
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Asserts that the command terminated with an error and a non-`0` exit code.
|
||||||
pub fn assert_command_failure(result: &CommandResult, error_message_prefix: &str) {
|
pub fn assert_command_failure(result: &CommandResult, error_message_prefix: &str) {
|
||||||
assert!(
|
assert!(
|
||||||
!result.success,
|
!result.success,
|
||||||
@@ -119,3 +153,15 @@ pub fn assert_command_failure(result: &CommandResult, error_message_prefix: &str
|
|||||||
result.code
|
result.code
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the absolute path of a file. The `relative_path` must
|
||||||
|
/// be relative to the `resolc` crate.
|
||||||
|
/// Panics if the path does not exist or is not an accessible file.
|
||||||
|
pub fn absolute_path(relative_path: &str) -> String {
|
||||||
|
let absolute_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(relative_path);
|
||||||
|
if !absolute_path.is_file() {
|
||||||
|
panic!("expected a file at `{}`", absolute_path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
absolute_path.to_string_lossy().into_owned()
|
||||||
|
}
|
||||||
@@ -55,6 +55,8 @@ pub use self::solc::LAST_SUPPORTED_VERSION as SolcLastSupportedVersion;
|
|||||||
pub use self::version::Version as ResolcVersion;
|
pub use self::version::Version as ResolcVersion;
|
||||||
|
|
||||||
pub(crate) mod build;
|
pub(crate) mod build;
|
||||||
|
#[cfg(not(target_os = "emscripten"))]
|
||||||
|
pub mod cli_utils;
|
||||||
pub(crate) mod r#const;
|
pub(crate) mod r#const;
|
||||||
pub(crate) mod linker;
|
pub(crate) mod linker;
|
||||||
pub(crate) mod missing_libraries;
|
pub(crate) mod missing_libraries;
|
||||||
@@ -209,6 +211,7 @@ pub fn standard_json<T: Compiler>(
|
|||||||
.debug_information
|
.debug_information
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
solc_input.extend_selection(SolcStandardJsonInputSettingsSelection::new_required());
|
solc_input.extend_selection(SolcStandardJsonInputSettingsSelection::new_required());
|
||||||
|
solc_input.retain_output_selection();
|
||||||
let mut solc_output = solc.standard_json(
|
let mut solc_output = solc.standard_json(
|
||||||
&mut solc_input,
|
&mut solc_input,
|
||||||
messages,
|
messages,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use serde::Serialize;
|
|||||||
|
|
||||||
use revive_yul::parser::statement::object::Object;
|
use revive_yul::parser::statement::object::Object;
|
||||||
|
|
||||||
/// he contract Yul source code.
|
/// The contract Yul source code.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Yul {
|
pub struct Yul {
|
||||||
/// The Yul AST object.
|
/// The Yul AST object.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub mod version;
|
|||||||
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
|
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
|
||||||
|
|
||||||
/// The last supported version of `solc`.
|
/// The last supported version of `solc`.
|
||||||
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 30);
|
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 31);
|
||||||
|
|
||||||
/// The Solidity compiler.
|
/// The Solidity compiler.
|
||||||
pub trait Compiler {
|
pub trait Compiler {
|
||||||
|
|||||||
+131
-28
@@ -33,6 +33,7 @@ static PVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Def
|
|||||||
static EVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default);
|
static EVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default);
|
||||||
static EVM_RUNTIME_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> =
|
static EVM_RUNTIME_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> =
|
||||||
Lazy::new(Default::default);
|
Lazy::new(Default::default);
|
||||||
|
static YUL_IR_CACHE: Lazy<Mutex<HashMap<CachedBlob, String>>> = Lazy::new(Default::default);
|
||||||
|
|
||||||
const DEBUG_CONFIG: revive_llvm_context::DebugConfig = DebugConfig::new(None, true);
|
const DEBUG_CONFIG: revive_llvm_context::DebugConfig = DebugConfig::new(None, true);
|
||||||
|
|
||||||
@@ -241,47 +242,52 @@ pub fn build_solidity_and_detect_missing_libraries<T: ToString>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the Yul project can be built without errors.
|
/// Checks if the Yul project can be built without errors.
|
||||||
pub fn build_yul<T: ToString + Display>(sources: &[(T, T)]) -> anyhow::Result<()> {
|
pub fn build_yul<T: ToString + Display>(
|
||||||
|
sources: &[(T, T)],
|
||||||
|
) -> anyhow::Result<BTreeMap<String, Vec<u8>>> {
|
||||||
check_dependencies();
|
check_dependencies();
|
||||||
|
|
||||||
inkwell::support::enable_llvm_pretty_stack_trace();
|
inkwell::support::enable_llvm_pretty_stack_trace();
|
||||||
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
|
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
|
||||||
let optimizer_settings = OptimizerSettings::none();
|
|
||||||
|
|
||||||
let sources = sources
|
let _ = crate::process::native_process::EXECUTABLE
|
||||||
.iter()
|
.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
|
||||||
.map(|(path, source)| {
|
|
||||||
(
|
|
||||||
path.to_string(),
|
|
||||||
SolcStandardJsonInputSource::from(source.to_string()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let mut output = SolcStandardJsonOutput::new(&sources, &mut vec![]);
|
|
||||||
|
|
||||||
let project = Project::try_from_yul_sources(
|
let mut build = Project::try_from_yul_sources(
|
||||||
sources,
|
sources
|
||||||
|
.iter()
|
||||||
|
.map(|(path, source)| {
|
||||||
|
(
|
||||||
|
path.to_string(),
|
||||||
|
SolcStandardJsonInputSource::from(source.to_string()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Some(&mut output),
|
None,
|
||||||
&Default::default(),
|
&DEBUG_CONFIG,
|
||||||
)?;
|
)?
|
||||||
let build = project.compile(
|
.compile(
|
||||||
&mut vec![],
|
&mut vec![],
|
||||||
optimizer_settings,
|
OptimizerSettings::size(),
|
||||||
MetadataHash::None,
|
MetadataHash::Keccak256,
|
||||||
&DEBUG_CONFIG,
|
&DEBUG_CONFIG,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)?;
|
)?;
|
||||||
|
build.take_and_write_warnings();
|
||||||
build.check_errors()?;
|
build.check_errors()?;
|
||||||
|
|
||||||
let build = build.link(BTreeMap::new(), &DEBUG_CONFIG);
|
let mut build = build.link(Default::default(), &DEBUG_CONFIG);
|
||||||
|
build.take_and_write_warnings();
|
||||||
build.check_errors()?;
|
build.check_errors()?;
|
||||||
|
|
||||||
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
|
Ok(build
|
||||||
build.write_to_standard_json(&mut output, &solc.version()?)?;
|
.results
|
||||||
output.check_errors()?;
|
.into_iter()
|
||||||
Ok(())
|
.fold(BTreeMap::new(), |mut init, (path, result)| {
|
||||||
|
init.insert(path.to_string(), result.unwrap().build.bytecode);
|
||||||
|
init
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the Yul standard JSON and returns the standard JSON output.
|
/// Builds the Yul standard JSON and returns the standard JSON output.
|
||||||
@@ -292,13 +298,19 @@ pub fn build_yul_standard_json(
|
|||||||
inkwell::support::enable_llvm_pretty_stack_trace();
|
inkwell::support::enable_llvm_pretty_stack_trace();
|
||||||
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
|
initialize_llvm(PolkaVMTarget::PVM, crate::DEFAULT_EXECUTABLE_NAME, &[]);
|
||||||
|
|
||||||
|
let _ = crate::process::native_process::EXECUTABLE
|
||||||
|
.set(PathBuf::from(crate::r#const::DEFAULT_EXECUTABLE_NAME));
|
||||||
|
|
||||||
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
|
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned())?;
|
||||||
let mut output = solc.validate_yul_standard_json(&mut solc_input, &mut vec![])?;
|
let mut output = solc.validate_yul_standard_json(&mut solc_input, &mut vec![])?;
|
||||||
|
if output.has_errors() {
|
||||||
|
return Ok(output);
|
||||||
|
}
|
||||||
let build = Project::try_from_yul_sources(
|
let build = Project::try_from_yul_sources(
|
||||||
solc_input.sources,
|
solc_input.sources,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
Some(&mut output),
|
Some(&mut output),
|
||||||
&Default::default(),
|
&DEBUG_CONFIG,
|
||||||
)?
|
)?
|
||||||
.compile(
|
.compile(
|
||||||
&mut vec![],
|
&mut vec![],
|
||||||
@@ -314,7 +326,6 @@ pub fn build_yul_standard_json(
|
|||||||
build.check_errors()?;
|
build.check_errors()?;
|
||||||
build.write_to_standard_json(&mut output, &solc.version()?)?;
|
build.write_to_standard_json(&mut output, &solc.version()?)?;
|
||||||
|
|
||||||
output.check_errors()?;
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,3 +476,95 @@ fn compile_evm(
|
|||||||
|
|
||||||
blob
|
blob
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compiles the Solidity source code into Yul IR and returns
|
||||||
|
/// the Yul IR code of the contract named `contract_name`.
|
||||||
|
pub fn compile_to_yul(
|
||||||
|
contract_name: &str,
|
||||||
|
source_code: &str,
|
||||||
|
solc_optimizer_enabled: bool,
|
||||||
|
) -> String {
|
||||||
|
check_dependencies();
|
||||||
|
|
||||||
|
let optimizer = SolcStandardJsonInputSettingsOptimizer::new(
|
||||||
|
solc_optimizer_enabled,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
let id = CachedBlob {
|
||||||
|
contract_name: contract_name.to_owned(),
|
||||||
|
solc_optimizer_enabled,
|
||||||
|
solidity: source_code.to_owned(),
|
||||||
|
opt: optimizer.mode.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(yul) = YUL_IR_CACHE.lock().unwrap().get(&id) {
|
||||||
|
return yul.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let file_name = "contract.sol";
|
||||||
|
let sources = BTreeMap::from([(
|
||||||
|
file_name.to_owned(),
|
||||||
|
SolcStandardJsonInputSource::from(source_code.to_owned()),
|
||||||
|
)]);
|
||||||
|
let mut input = SolcStandardJsonInput::try_from_solidity_sources(
|
||||||
|
None,
|
||||||
|
sources.clone(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
SolcStandardJsonInputSettingsSelection::new_required_for_tests(),
|
||||||
|
optimizer,
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
Default::default(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let solc = SolcCompiler::new(SolcCompiler::DEFAULT_EXECUTABLE_NAME.to_owned()).unwrap();
|
||||||
|
let output = solc
|
||||||
|
.standard_json(&mut input, &mut vec![], None, vec![], None)
|
||||||
|
.unwrap();
|
||||||
|
output.check_errors().unwrap();
|
||||||
|
|
||||||
|
let yul = output
|
||||||
|
.contracts
|
||||||
|
.get(file_name)
|
||||||
|
.unwrap_or_else(|| panic!("file `{file_name}` not found in solc output"))
|
||||||
|
.get(contract_name)
|
||||||
|
.unwrap_or_else(|| panic!("contract `{contract_name}` not found in solc output"))
|
||||||
|
.ir_optimized
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
YUL_IR_CACHE.lock().unwrap().insert(id, yul.clone());
|
||||||
|
|
||||||
|
yul
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
use super::compile_to_yul;
|
||||||
|
use crate::cli_utils::SOLIDITY_DEPENDENCY_CONTRACT_PATH;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compiles_to_yul() {
|
||||||
|
let contract_name = "Dependency";
|
||||||
|
let source_code = read_to_string(SOLIDITY_DEPENDENCY_CONTRACT_PATH).unwrap();
|
||||||
|
let yul = compile_to_yul(contract_name, &source_code, true);
|
||||||
|
assert!(
|
||||||
|
yul.contains(&format!("object \"{contract_name}")),
|
||||||
|
"the `{contract_name}` contract IR code should contain a Yul object"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "contract `Nonexistent` not found in solc output")]
|
||||||
|
fn error_nonexistent_contract_in_yul() {
|
||||||
|
let contract_name = "Nonexistent";
|
||||||
|
let source_code = read_to_string(SOLIDITY_DEPENDENCY_CONTRACT_PATH).unwrap();
|
||||||
|
compile_to_yul(contract_name, &source_code, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
//! The tests for running resolc with asm option.
|
//! The tests for running resolc with asm option.
|
||||||
|
|
||||||
use crate::tests::cli::utils;
|
use crate::cli_utils::{
|
||||||
|
assert_command_failure, assert_command_success, assert_equal_exit_codes, execute_resolc,
|
||||||
|
execute_solc, SOLIDITY_CONTRACT_PATH,
|
||||||
|
};
|
||||||
|
|
||||||
const ASM_OPTION: &str = "--asm";
|
const ASM_OPTION: &str = "--asm";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runs_with_valid_input_file() {
|
fn runs_with_valid_input_file() {
|
||||||
let arguments = &[utils::SOLIDITY_CONTRACT_PATH, ASM_OPTION];
|
let arguments = &[SOLIDITY_CONTRACT_PATH, ASM_OPTION];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
utils::assert_command_success(&resolc_result, "Providing a valid input file");
|
assert_command_success(&resolc_result, "Providing a valid input file");
|
||||||
|
|
||||||
for pattern in &["deploy", "call", "seal_return"] {
|
for pattern in &["deploy", "call", "seal_return"] {
|
||||||
assert!(
|
assert!(
|
||||||
@@ -17,19 +20,19 @@ fn runs_with_valid_input_file() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_without_input_file() {
|
fn fails_without_input_file() {
|
||||||
let arguments = &[ASM_OPTION];
|
let arguments = &[ASM_OPTION];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
utils::assert_command_failure(&resolc_result, "Omitting an input file");
|
assert_command_failure(&resolc_result, "Omitting an input file");
|
||||||
|
|
||||||
let output = resolc_result.stderr.to_lowercase();
|
let output = resolc_result.stderr.to_lowercase();
|
||||||
assert!(output.contains("no input sources specified"));
|
assert!(output.contains("no input sources specified"));
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
//! The tests for running `resolc` with bin option.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cli_utils::{
|
||||||
|
absolute_path, assert_command_success, execute_command, CommandResult, ResolcOptSettings,
|
||||||
|
SolcOptSettings, SOLIDITY_CONTRACT_PATH, STANDARD_JSON_CONTRACTS_PATH,
|
||||||
|
YUL_MEMSET_CONTRACT_PATH,
|
||||||
|
},
|
||||||
|
SolcCompiler,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The starting hex value of a PVM blob (encoding of `"PVM"`).
|
||||||
|
const PVM_BLOB_START: &str = "50564d";
|
||||||
|
|
||||||
|
/// The starting hex value of an EVM blob compiled from Solidity.
|
||||||
|
const EVM_BLOB_START_FROM_SOLIDITY: &str = "6080";
|
||||||
|
|
||||||
|
/// The starting hex value of an EVM blob compiled from Yul.
|
||||||
|
/// (Blobs compiled from Yul do not have consistent starting hex values.)
|
||||||
|
const EVM_BLOB_START_FROM_YUL: &str = "";
|
||||||
|
|
||||||
|
/// Asserts that the `resolc` output contains a PVM blob.
|
||||||
|
fn assert_pvm_blob(result: &CommandResult) {
|
||||||
|
assert_binary_blob(result, "Binary:\n", PVM_BLOB_START);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the `solc` output from compiling Solidity contains an EVM blob.
|
||||||
|
fn assert_evm_blob_from_solidity(result: &CommandResult) {
|
||||||
|
assert_binary_blob(result, "Binary:\n", EVM_BLOB_START_FROM_SOLIDITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the `solc` output from compiling Yul contains an EVM blob.
|
||||||
|
fn assert_evm_blob_from_yul(result: &CommandResult) {
|
||||||
|
assert_binary_blob(result, "Binary representation:\n", EVM_BLOB_START_FROM_YUL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the `resolc` output of compiling Solidity from JSON input contains a PVM blob.
|
||||||
|
/// - `result`: The result of running the command.
|
||||||
|
/// - `file_name`: The file name of the contract to verify the existence of a PVM blob for (corresponds to the name specified as a `source` in the JSON input).
|
||||||
|
/// - `contract_name`: The name of the contract to verify the existence of a PVM blob for.
|
||||||
|
fn assert_pvm_blob_from_json(result: &CommandResult, file_name: &str, contract_name: &str) {
|
||||||
|
assert_binary_blob_from_json(result, file_name, contract_name, PVM_BLOB_START);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the `solc` output of compiling Solidity from JSON input contains an EVM blob.
|
||||||
|
/// - `result`: The result of running the command.
|
||||||
|
/// - `file_name`: The file name of the contract to verify the existence of an EVM blob for (corresponds to the name specified as a `source` in the JSON input).
|
||||||
|
/// - `contract_name`: The name of the contract to verify the existence of an EVM blob for.
|
||||||
|
fn assert_evm_blob_from_json(result: &CommandResult, file_name: &str, contract_name: &str) {
|
||||||
|
assert_binary_blob_from_json(
|
||||||
|
result,
|
||||||
|
file_name,
|
||||||
|
contract_name,
|
||||||
|
EVM_BLOB_START_FROM_SOLIDITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the output of a compilation contains a binary blob.
|
||||||
|
/// - `result`: The result of running the command.
|
||||||
|
/// - `blob_prefix`: The `stdout` message immediately preceding the binary blob representation.
|
||||||
|
/// - `blob_start`: The starting hex value of the binary blob.
|
||||||
|
fn assert_binary_blob(result: &CommandResult, blob_prefix: &str, blob_start: &str) {
|
||||||
|
assert_command_success(result, "Executing the command");
|
||||||
|
|
||||||
|
let is_blob = result
|
||||||
|
.stdout
|
||||||
|
.split(blob_prefix)
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.get(1)
|
||||||
|
.is_some_and(|blob| blob.starts_with(blob_start));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
is_blob,
|
||||||
|
"expected a binary blob starting with `{blob_start}`",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the output of compiling Solidity from JSON input contains a binary blob.
|
||||||
|
/// - `result`: The result of running the command.
|
||||||
|
/// - `file_name`: The file name of the contract to verify the existence of a binary blob for (corresponds to the name specified as a `source` in the JSON input).
|
||||||
|
/// - `contract_name`: The name of the contract to verify the existence of a binary blob for.
|
||||||
|
/// - `blob_start`: The starting hex value of the binary blob.
|
||||||
|
///
|
||||||
|
/// See [output description](https://docs.soliditylang.org/en/latest/using-the-compiler.html#output-description)
|
||||||
|
/// for more details on the JSON output format.
|
||||||
|
fn assert_binary_blob_from_json(
|
||||||
|
result: &CommandResult,
|
||||||
|
file_name: &str,
|
||||||
|
contract_name: &str,
|
||||||
|
blob_start: &str,
|
||||||
|
) {
|
||||||
|
assert_command_success(result, "Executing the command with stdin JSON input");
|
||||||
|
|
||||||
|
let parsed_output: serde_json::Value =
|
||||||
|
serde_json::from_str(&result.stdout).expect("expected valid JSON output");
|
||||||
|
let contract = &parsed_output["contracts"][file_name][contract_name];
|
||||||
|
|
||||||
|
let errors = contract["errors"].as_array();
|
||||||
|
assert!(
|
||||||
|
errors.is_none(),
|
||||||
|
"errors found for JSON-provided contract `{contract_name}` in `{file_name}`: {}",
|
||||||
|
get_first_json_error(errors.unwrap()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let blob = contract["evm"]["bytecode"]["object"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"expected a binary blob for JSON-provided contract `{contract_name}` in `{file_name}`",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
blob.starts_with(blob_start),
|
||||||
|
"expected a binary blob starting with `{blob_start}`",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the first error message reported when compiling from JSON input.
|
||||||
|
///
|
||||||
|
/// See [output description](https://docs.soliditylang.org/en/latest/using-the-compiler.html#output-description)
|
||||||
|
/// for more details on the JSON output format.
|
||||||
|
fn get_first_json_error(errors: &[serde_json::Value]) -> &str {
|
||||||
|
errors.first().unwrap()["message"].as_str().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This test mimics the command used in the `resolc` benchmarks when compiling Solidity.
|
||||||
|
#[test]
|
||||||
|
fn compiles_solidity_to_binary_blob() {
|
||||||
|
let path = absolute_path(SOLIDITY_CONTRACT_PATH);
|
||||||
|
let resolc_arguments = &[&path, "--bin", ResolcOptSettings::PERFORMANCE];
|
||||||
|
let solc_arguments = &[
|
||||||
|
&path,
|
||||||
|
"--bin",
|
||||||
|
"--via-ir",
|
||||||
|
"--optimize",
|
||||||
|
"--optimize-runs",
|
||||||
|
SolcOptSettings::PERFORMANCE,
|
||||||
|
];
|
||||||
|
|
||||||
|
let resolc_result = execute_command(crate::DEFAULT_EXECUTABLE_NAME, resolc_arguments, None);
|
||||||
|
assert_pvm_blob(&resolc_result);
|
||||||
|
|
||||||
|
let solc_result = execute_command(SolcCompiler::DEFAULT_EXECUTABLE_NAME, solc_arguments, None);
|
||||||
|
assert_evm_blob_from_solidity(&solc_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This test mimics the command used in the `resolc` benchmarks when compiling Yul.
|
||||||
|
#[test]
|
||||||
|
fn compiles_yul_to_binary_blob() {
|
||||||
|
let path = absolute_path(YUL_MEMSET_CONTRACT_PATH);
|
||||||
|
let resolc_arguments = &[&path, "--yul", "--bin", ResolcOptSettings::PERFORMANCE];
|
||||||
|
let solc_arguments = &[
|
||||||
|
&path,
|
||||||
|
"--strict-assembly",
|
||||||
|
"--bin",
|
||||||
|
"--optimize",
|
||||||
|
"--optimize-runs",
|
||||||
|
SolcOptSettings::PERFORMANCE,
|
||||||
|
];
|
||||||
|
|
||||||
|
let resolc_result = execute_command(crate::DEFAULT_EXECUTABLE_NAME, resolc_arguments, None);
|
||||||
|
assert_pvm_blob(&resolc_result);
|
||||||
|
|
||||||
|
let solc_result = execute_command(SolcCompiler::DEFAULT_EXECUTABLE_NAME, solc_arguments, None);
|
||||||
|
assert_evm_blob_from_yul(&solc_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This test mimics the command used in the `resolc` benchmarks when compiling Solidity via standard JSON input.
|
||||||
|
#[test]
|
||||||
|
fn compiles_json_to_binary_blob() {
|
||||||
|
let path = absolute_path(STANDARD_JSON_CONTRACTS_PATH);
|
||||||
|
let resolc_arguments = &["--standard-json"];
|
||||||
|
let solc_arguments = &["--standard-json"];
|
||||||
|
|
||||||
|
let resolc_result = execute_command(
|
||||||
|
crate::DEFAULT_EXECUTABLE_NAME,
|
||||||
|
resolc_arguments,
|
||||||
|
Some(&path),
|
||||||
|
);
|
||||||
|
assert_pvm_blob_from_json(&resolc_result, "src/Counter.sol", "Counter");
|
||||||
|
|
||||||
|
let solc_result = execute_command(
|
||||||
|
SolcCompiler::DEFAULT_EXECUTABLE_NAME,
|
||||||
|
solc_arguments,
|
||||||
|
Some(&path),
|
||||||
|
);
|
||||||
|
assert_evm_blob_from_json(&solc_result, "src/Counter.sol", "Counter");
|
||||||
|
}
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
use revive_solc_json_interface::CombinedJsonInvalidSelectorMessage;
|
use revive_solc_json_interface::CombinedJsonInvalidSelectorMessage;
|
||||||
|
|
||||||
use crate::tests::cli::utils;
|
use crate::cli_utils::{
|
||||||
|
assert_command_failure, assert_equal_exit_codes, execute_resolc, execute_solc,
|
||||||
|
SOLIDITY_CONTRACT_PATH, YUL_CONTRACT_PATH,
|
||||||
|
};
|
||||||
|
|
||||||
const JSON_OPTION: &str = "--combined-json";
|
const JSON_OPTION: &str = "--combined-json";
|
||||||
const JSON_ARGUMENTS: &[&str] = &[
|
const JSON_ARGUMENTS: &[&str] = &[
|
||||||
@@ -21,8 +24,8 @@ const JSON_ARGUMENTS: &[&str] = &[
|
|||||||
#[test]
|
#[test]
|
||||||
fn runs_with_valid_json_argument() {
|
fn runs_with_valid_json_argument() {
|
||||||
for json_argument in JSON_ARGUMENTS {
|
for json_argument in JSON_ARGUMENTS {
|
||||||
let arguments = &[utils::SOLIDITY_CONTRACT_PATH, JSON_OPTION, json_argument];
|
let arguments = &[SOLIDITY_CONTRACT_PATH, JSON_OPTION, json_argument];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
assert!(
|
assert!(
|
||||||
resolc_result.success,
|
resolc_result.success,
|
||||||
"Providing the `{json_argument}` argument should succeed with exit code {}, got {}.\nDetails: {}",
|
"Providing the `{json_argument}` argument should succeed with exit code {}, got {}.\nDetails: {}",
|
||||||
@@ -36,86 +39,82 @@ fn runs_with_valid_json_argument() {
|
|||||||
"Expected the output to contain a `contracts` field when using the `{json_argument}` argument."
|
"Expected the output to contain a `contracts` field when using the `{json_argument}` argument."
|
||||||
);
|
);
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_with_invalid_json_argument() {
|
fn fails_with_invalid_json_argument() {
|
||||||
let arguments = &[
|
let arguments = &[SOLIDITY_CONTRACT_PATH, JSON_OPTION, "invalid-argument"];
|
||||||
utils::SOLIDITY_CONTRACT_PATH,
|
let resolc_result = execute_resolc(arguments);
|
||||||
JSON_OPTION,
|
assert_command_failure(&resolc_result, "Providing an invalid json argument");
|
||||||
"invalid-argument",
|
|
||||||
];
|
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
|
||||||
utils::assert_command_failure(&resolc_result, "Providing an invalid json argument");
|
|
||||||
|
|
||||||
assert!(resolc_result
|
assert!(resolc_result
|
||||||
.stderr
|
.stderr
|
||||||
.contains(CombinedJsonInvalidSelectorMessage));
|
.contains(CombinedJsonInvalidSelectorMessage));
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_with_multiple_json_arguments() {
|
fn fails_with_multiple_json_arguments() {
|
||||||
let arguments = &[
|
let arguments = &[
|
||||||
utils::SOLIDITY_CONTRACT_PATH,
|
SOLIDITY_CONTRACT_PATH,
|
||||||
JSON_OPTION,
|
JSON_OPTION,
|
||||||
JSON_ARGUMENTS[0],
|
JSON_ARGUMENTS[0],
|
||||||
JSON_ARGUMENTS[1],
|
JSON_ARGUMENTS[1],
|
||||||
];
|
];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
utils::assert_command_failure(&resolc_result, "Providing multiple json arguments");
|
assert_command_failure(&resolc_result, "Providing multiple json arguments");
|
||||||
|
|
||||||
assert!(resolc_result
|
assert!(resolc_result
|
||||||
.stderr
|
.stderr
|
||||||
.contains(&format!("Error: \"{}\" is not found.", JSON_ARGUMENTS[1])),);
|
.contains(&format!("Error: \"{}\" is not found.", JSON_ARGUMENTS[1])),);
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_without_json_argument() {
|
fn fails_without_json_argument() {
|
||||||
let arguments = &[utils::SOLIDITY_CONTRACT_PATH, JSON_OPTION];
|
let arguments = &[SOLIDITY_CONTRACT_PATH, JSON_OPTION];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
utils::assert_command_failure(&resolc_result, "Omitting a JSON argument");
|
assert_command_failure(&resolc_result, "Omitting a JSON argument");
|
||||||
|
|
||||||
assert!(resolc_result.stderr.contains(
|
assert!(resolc_result.stderr.contains(
|
||||||
"a value is required for '--combined-json <COMBINED_JSON>' but none was supplied"
|
"a value is required for '--combined-json <COMBINED_JSON>' but none was supplied"
|
||||||
));
|
));
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_without_solidity_input_file() {
|
fn fails_without_solidity_input_file() {
|
||||||
let arguments = &[JSON_OPTION, JSON_ARGUMENTS[0]];
|
let arguments = &[JSON_OPTION, JSON_ARGUMENTS[0]];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
utils::assert_command_failure(&resolc_result, "Omitting a Solidity input file");
|
assert_command_failure(&resolc_result, "Omitting a Solidity input file");
|
||||||
|
|
||||||
assert!(resolc_result.stderr.contains("Error: No input files given"),);
|
assert!(resolc_result.stderr.contains("Error: No input files given"),);
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_with_yul_input_file() {
|
fn fails_with_yul_input_file() {
|
||||||
for json_argument in JSON_ARGUMENTS {
|
for json_argument in JSON_ARGUMENTS {
|
||||||
let arguments = &[utils::YUL_CONTRACT_PATH, JSON_OPTION, json_argument];
|
let arguments = &[YUL_CONTRACT_PATH, JSON_OPTION, json_argument];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
utils::assert_command_failure(&resolc_result, "Providing a Yul input file");
|
assert_command_failure(&resolc_result, "Providing a Yul input file");
|
||||||
|
|
||||||
assert!(resolc_result
|
assert!(resolc_result
|
||||||
.stderr
|
.stderr
|
||||||
.contains("Error: Expected identifier but got 'StringLiteral'"));
|
.contains("Error: Expected identifier but got 'StringLiteral'"));
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::tests::cli::utils::{assert_command_success, execute_resolc, DEPENDENCY_CONTRACT_PATH};
|
use crate::cli_utils::{assert_command_success, execute_resolc, SOLIDITY_DEPENDENCY_CONTRACT_PATH};
|
||||||
|
|
||||||
/// Test deploy time linking a contract with unresolved factory dependencies.
|
/// Test deploy time linking a contract with unresolved factory dependencies.
|
||||||
#[test]
|
#[test]
|
||||||
@@ -6,7 +6,7 @@ fn deploy_time_linking_works() {
|
|||||||
let temp_dir = tempfile::TempDir::new().unwrap();
|
let temp_dir = tempfile::TempDir::new().unwrap();
|
||||||
let output_directory = temp_dir.path().to_path_buf();
|
let output_directory = temp_dir.path().to_path_buf();
|
||||||
let source_path = temp_dir.path().to_path_buf().join("dependency.sol");
|
let source_path = temp_dir.path().to_path_buf().join("dependency.sol");
|
||||||
std::fs::copy(DEPENDENCY_CONTRACT_PATH, &source_path).unwrap();
|
std::fs::copy(SOLIDITY_DEPENDENCY_CONTRACT_PATH, &source_path).unwrap();
|
||||||
|
|
||||||
assert_command_success(
|
assert_command_success(
|
||||||
&execute_resolc(&[
|
&execute_resolc(&[
|
||||||
@@ -58,7 +58,7 @@ fn deploy_time_linking_works() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn emits_unlinked_binary_warning() {
|
fn emits_unlinked_binary_warning() {
|
||||||
let output = execute_resolc(&[DEPENDENCY_CONTRACT_PATH, "--bin"]);
|
let output = execute_resolc(&[SOLIDITY_DEPENDENCY_CONTRACT_PATH, "--bin"]);
|
||||||
assert_command_success(&output, "Missing libraries should compile fine");
|
assert_command_success(&output, "Missing libraries should compile fine");
|
||||||
assert!(output.stderr.contains("is unlinked"));
|
assert!(output.stderr.contains("is unlinked"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::tests::cli::utils::{
|
use crate::cli_utils::{
|
||||||
assert_command_success, execute_resolc, RESOLC_YUL_FLAG, YUL_CONTRACT_PATH,
|
assert_command_success, execute_resolc, RESOLC_YUL_FLAG, YUL_CONTRACT_PATH,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! The `resolc` CLI tests.
|
//! The `resolc` CLI tests.
|
||||||
|
|
||||||
mod asm;
|
mod asm;
|
||||||
|
mod bin;
|
||||||
mod combined_json;
|
mod combined_json;
|
||||||
mod linker;
|
mod linker;
|
||||||
mod llvm_arguments;
|
mod llvm_arguments;
|
||||||
@@ -8,5 +9,4 @@ mod optimization;
|
|||||||
mod output_dir;
|
mod output_dir;
|
||||||
mod standard_json;
|
mod standard_json;
|
||||||
mod usage;
|
mod usage;
|
||||||
mod utils;
|
|
||||||
mod yul;
|
mod yul;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! The tests for running resolc with explicit optimization.
|
//! The tests for running resolc with explicit optimization.
|
||||||
|
|
||||||
use crate::tests::cli::utils::{
|
use crate::cli_utils::{
|
||||||
self, assert_command_failure, assert_command_success, assert_equal_exit_codes, execute_resolc,
|
assert_command_failure, assert_command_success, assert_equal_exit_codes, execute_resolc,
|
||||||
execute_solc, RESOLC_YUL_FLAG, SOLIDITY_CONTRACT_PATH, SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH,
|
execute_solc, RESOLC_YUL_FLAG, SOLIDITY_CONTRACT_PATH, SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH,
|
||||||
YUL_MEMSET_CONTRACT_PATH,
|
YUL_MEMSET_CONTRACT_PATH,
|
||||||
};
|
};
|
||||||
@@ -13,7 +13,7 @@ fn runs_with_valid_level() {
|
|||||||
for level in LEVELS {
|
for level in LEVELS {
|
||||||
let optimization_argument = format!("-O{level}");
|
let optimization_argument = format!("-O{level}");
|
||||||
let arguments = &[YUL_MEMSET_CONTRACT_PATH, "--yul", &optimization_argument];
|
let arguments = &[YUL_MEMSET_CONTRACT_PATH, "--yul", &optimization_argument];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
assert!(
|
assert!(
|
||||||
resolc_result.success,
|
resolc_result.success,
|
||||||
"Providing the level `{optimization_argument}` should succeed with exit code {}, got {}.\nDetails: {}",
|
"Providing the level `{optimization_argument}` should succeed with exit code {}, got {}.\nDetails: {}",
|
||||||
@@ -63,7 +63,7 @@ fn test_large_div_rem_expansion() {
|
|||||||
for level in LEVELS {
|
for level in LEVELS {
|
||||||
let optimization_argument = format!("-O{level}");
|
let optimization_argument = format!("-O{level}");
|
||||||
let arguments = &[SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH, &optimization_argument];
|
let arguments = &[SOLIDITY_LARGE_DIV_REM_CONTRACT_PATH, &optimization_argument];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
assert!(
|
assert!(
|
||||||
resolc_result.success,
|
resolc_result.success,
|
||||||
"Providing the level `{optimization_argument}` should succeed with exit code {}, got {}.\nDetails: {}",
|
"Providing the level `{optimization_argument}` should succeed with exit code {}, got {}.\nDetails: {}",
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ use std::path::Path;
|
|||||||
|
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use crate::tests::cli::utils;
|
use crate::cli_utils::{
|
||||||
|
assert_command_success, execute_resolc, CommandResult, SOLIDITY_CONTRACT_PATH,
|
||||||
|
};
|
||||||
|
|
||||||
const OUTPUT_BIN_FILE_PATH: &str = "contract.sol:C.pvm";
|
const OUTPUT_BIN_FILE_PATH: &str = "contract.sol:C.pvm";
|
||||||
const OUTPUT_ASM_FILE_PATH: &str = "contract.sol:C.pvmasm";
|
const OUTPUT_ASM_FILE_PATH: &str = "contract.sol:C.pvmasm";
|
||||||
@@ -13,11 +15,11 @@ const OUTPUT_LLVM_UNOPTIMIZED_FILE_PATH: &str =
|
|||||||
"src_tests_data_solidity_contract.sol.C.unoptimized.ll";
|
"src_tests_data_solidity_contract.sol.C.unoptimized.ll";
|
||||||
|
|
||||||
fn assert_valid_output_file(
|
fn assert_valid_output_file(
|
||||||
result: &utils::CommandResult,
|
result: &CommandResult,
|
||||||
debug_output_directory: &Path,
|
debug_output_directory: &Path,
|
||||||
output_file_name: &str,
|
output_file_name: &str,
|
||||||
) {
|
) {
|
||||||
utils::assert_command_success(result, "Providing an output directory");
|
assert_command_success(result, "Providing an output directory");
|
||||||
|
|
||||||
assert!(result.stderr.contains("Compiler run successful"),);
|
assert!(result.stderr.contains("Compiler run successful"),);
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ fn assert_valid_output_file(
|
|||||||
fn writes_to_file() {
|
fn writes_to_file() {
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let arguments = &[
|
let arguments = &[
|
||||||
utils::SOLIDITY_CONTRACT_PATH,
|
SOLIDITY_CONTRACT_PATH,
|
||||||
"--overwrite",
|
"--overwrite",
|
||||||
"-O3",
|
"-O3",
|
||||||
"--bin",
|
"--bin",
|
||||||
@@ -45,7 +47,7 @@ fn writes_to_file() {
|
|||||||
"--output-dir",
|
"--output-dir",
|
||||||
temp_dir.path().to_str().unwrap(),
|
temp_dir.path().to_str().unwrap(),
|
||||||
];
|
];
|
||||||
let result = utils::execute_resolc(arguments);
|
let result = execute_resolc(arguments);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_BIN_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_BIN_FILE_PATH);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_ASM_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_ASM_FILE_PATH);
|
||||||
}
|
}
|
||||||
@@ -54,7 +56,7 @@ fn writes_to_file() {
|
|||||||
fn writes_debug_info_to_file_unoptimized() {
|
fn writes_debug_info_to_file_unoptimized() {
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let arguments = &[
|
let arguments = &[
|
||||||
utils::SOLIDITY_CONTRACT_PATH,
|
SOLIDITY_CONTRACT_PATH,
|
||||||
"-g",
|
"-g",
|
||||||
"--disable-solc-optimizer",
|
"--disable-solc-optimizer",
|
||||||
"--overwrite",
|
"--overwrite",
|
||||||
@@ -63,7 +65,7 @@ fn writes_debug_info_to_file_unoptimized() {
|
|||||||
"--output-dir",
|
"--output-dir",
|
||||||
temp_dir.path().to_str().unwrap(),
|
temp_dir.path().to_str().unwrap(),
|
||||||
];
|
];
|
||||||
let result = utils::execute_resolc(arguments);
|
let result = execute_resolc(arguments);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_BIN_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_BIN_FILE_PATH);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_ASM_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_ASM_FILE_PATH);
|
||||||
}
|
}
|
||||||
@@ -72,7 +74,7 @@ fn writes_debug_info_to_file_unoptimized() {
|
|||||||
fn writes_debug_info_to_file_optimized() {
|
fn writes_debug_info_to_file_optimized() {
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let arguments = &[
|
let arguments = &[
|
||||||
utils::SOLIDITY_CONTRACT_PATH,
|
SOLIDITY_CONTRACT_PATH,
|
||||||
"-g",
|
"-g",
|
||||||
"--overwrite",
|
"--overwrite",
|
||||||
"--bin",
|
"--bin",
|
||||||
@@ -80,7 +82,7 @@ fn writes_debug_info_to_file_optimized() {
|
|||||||
"--output-dir",
|
"--output-dir",
|
||||||
temp_dir.path().to_str().unwrap(),
|
temp_dir.path().to_str().unwrap(),
|
||||||
];
|
];
|
||||||
let result = utils::execute_resolc(arguments);
|
let result = execute_resolc(arguments);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_BIN_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_BIN_FILE_PATH);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_ASM_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_ASM_FILE_PATH);
|
||||||
}
|
}
|
||||||
@@ -89,14 +91,14 @@ fn writes_debug_info_to_file_optimized() {
|
|||||||
fn writes_llvm_debug_info_to_file_unoptimized() {
|
fn writes_llvm_debug_info_to_file_unoptimized() {
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let arguments = &[
|
let arguments = &[
|
||||||
utils::SOLIDITY_CONTRACT_PATH,
|
SOLIDITY_CONTRACT_PATH,
|
||||||
"-g",
|
"-g",
|
||||||
"--disable-solc-optimizer",
|
"--disable-solc-optimizer",
|
||||||
"--overwrite",
|
"--overwrite",
|
||||||
"--debug-output-dir",
|
"--debug-output-dir",
|
||||||
temp_dir.path().to_str().unwrap(),
|
temp_dir.path().to_str().unwrap(),
|
||||||
];
|
];
|
||||||
let result = utils::execute_resolc(arguments);
|
let result = execute_resolc(arguments);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_LLVM_UNOPTIMIZED_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_LLVM_UNOPTIMIZED_FILE_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,12 +106,12 @@ fn writes_llvm_debug_info_to_file_unoptimized() {
|
|||||||
fn writes_llvm_debug_info_to_file_optimized() {
|
fn writes_llvm_debug_info_to_file_optimized() {
|
||||||
let temp_dir = tempdir().unwrap();
|
let temp_dir = tempdir().unwrap();
|
||||||
let arguments = &[
|
let arguments = &[
|
||||||
utils::SOLIDITY_CONTRACT_PATH,
|
SOLIDITY_CONTRACT_PATH,
|
||||||
"-g",
|
"-g",
|
||||||
"--overwrite",
|
"--overwrite",
|
||||||
"--debug-output-dir",
|
"--debug-output-dir",
|
||||||
temp_dir.path().to_str().unwrap(),
|
temp_dir.path().to_str().unwrap(),
|
||||||
];
|
];
|
||||||
let result = utils::execute_resolc(arguments);
|
let result = execute_resolc(arguments);
|
||||||
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_LLVM_OPTIMIZED_FILE_PATH);
|
assert_valid_output_file(&result, temp_dir.path(), OUTPUT_LLVM_OPTIMIZED_FILE_PATH);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
//! The tests for running resolc with standard JSON option.
|
//! The tests for running resolc with standard JSON option.
|
||||||
|
|
||||||
use crate::tests::cli::utils::{
|
use revive_solc_json_interface::SolcStandardJsonOutput;
|
||||||
|
|
||||||
|
use crate::cli_utils::{
|
||||||
assert_command_success, assert_equal_exit_codes, execute_resolc_with_stdin_input,
|
assert_command_success, assert_equal_exit_codes, execute_resolc_with_stdin_input,
|
||||||
execute_solc_with_stdin_input, STANDARD_JSON_CONTRACTS_PATH,
|
execute_solc_with_stdin_input, STANDARD_JSON_CONTRACTS_PATH, STANDARD_JSON_NO_EVM_CODEGEN_PATH,
|
||||||
};
|
};
|
||||||
|
|
||||||
const JSON_OPTION: &str = "--standard-json";
|
const JSON_OPTION: &str = "--standard-json";
|
||||||
@@ -21,3 +23,12 @@ fn runs_with_valid_input_file() {
|
|||||||
let solc_result = execute_solc_with_stdin_input(arguments, STANDARD_JSON_CONTRACTS_PATH);
|
let solc_result = execute_solc_with_stdin_input(arguments, STANDARD_JSON_CONTRACTS_PATH);
|
||||||
assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_evm_codegen_requested() {
|
||||||
|
let result = execute_resolc_with_stdin_input(&[JSON_OPTION], STANDARD_JSON_NO_EVM_CODEGEN_PATH);
|
||||||
|
assert_command_success(&result, "EVM codegen std json input fixture should build");
|
||||||
|
|
||||||
|
let output: SolcStandardJsonOutput = serde_json::from_str(&result.stdout).unwrap();
|
||||||
|
assert!(!output.errors.iter().any(|msg| msg.severity == "error"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
//! The tests for running resolc when expecting usage output.
|
//! The tests for running resolc when expecting usage output.
|
||||||
|
|
||||||
use crate::tests::cli::utils;
|
use crate::cli_utils::{
|
||||||
|
assert_command_failure, assert_command_success, assert_equal_exit_codes, execute_resolc,
|
||||||
|
execute_solc,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shows_usage_with_help() {
|
fn shows_usage_with_help() {
|
||||||
let arguments = &["--help"];
|
let arguments = &["--help"];
|
||||||
let resolc_result = utils::execute_resolc(arguments);
|
let resolc_result = execute_resolc(arguments);
|
||||||
utils::assert_command_success(&resolc_result, "Providing the `--help` option");
|
assert_command_success(&resolc_result, "Providing the `--help` option");
|
||||||
|
|
||||||
assert!(resolc_result.stdout.contains("Usage: resolc"));
|
assert!(resolc_result.stdout.contains("Usage: resolc"));
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(arguments);
|
let solc_result = execute_solc(arguments);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_without_options() {
|
fn fails_without_options() {
|
||||||
let resolc_result = utils::execute_resolc(&[]);
|
let resolc_result = execute_resolc(&[]);
|
||||||
utils::assert_command_failure(&resolc_result, "Omitting options");
|
assert_command_failure(&resolc_result, "Omitting options");
|
||||||
|
|
||||||
assert!(resolc_result.stderr.contains("Usage: resolc"));
|
assert!(resolc_result.stderr.contains("Usage: resolc"));
|
||||||
|
|
||||||
let solc_result = utils::execute_solc(&[]);
|
let solc_result = execute_solc(&[]);
|
||||||
utils::assert_equal_exit_codes(&solc_result, &resolc_result);
|
assert_equal_exit_codes(&solc_result, &resolc_result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! The tests for running resolc with yul option.
|
//! The tests for running resolc with yul option.
|
||||||
|
|
||||||
use crate::tests::cli::utils::{
|
use crate::cli_utils::{
|
||||||
assert_command_success, assert_equal_exit_codes, execute_resolc, execute_solc, RESOLC_YUL_FLAG,
|
assert_command_success, assert_equal_exit_codes, execute_resolc, execute_solc, RESOLC_YUL_FLAG,
|
||||||
SOLC_YUL_FLAG, YUL_CONTRACT_PATH,
|
SOLC_YUL_FLAG, YUL_CONTRACT_PATH,
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user