Compare commits

..

1 Commits

Author SHA1 Message Date
xermicus ea78e03348 experimentally switch to rv64
Signed-off-by: xermicus <cyrill@parity.io>
2024-04-27 12:46:05 +02:00
391 changed files with 16314 additions and 26385 deletions
-17
View File
@@ -1,17 +0,0 @@
[target.wasm32-unknown-emscripten]
rustflags = [
"-Clink-arg=-sEXPORTED_FUNCTIONS=_main,_free,_malloc",
"-Clink-arg=-sNO_INVOKE_RUN=1",
"-Clink-arg=-sEXIT_RUNTIME=1",
"-Clink-arg=-sALLOW_MEMORY_GROWTH=1",
"-Clink-arg=-sEXPORTED_RUNTIME_METHODS=FS,callMain,stringToNewUTF8",
"-Clink-arg=-sMODULARIZE=1",
"-Clink-arg=-sEXPORT_NAME=createRevive",
"-Clink-arg=-sWASM_ASYNC_COMPILATION=0",
"-Clink-arg=-sDYNAMIC_EXECUTION=0",
"-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/embed/pre.js",
"-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0"
]
-7
View File
@@ -1,7 +0,0 @@
emsdk
llvm*/
target-llvm/
target/
node_modules/
utils/
build/
-143
View File
@@ -1,143 +0,0 @@
name: Build revive-wasm
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
REVIVE_WASM_INSTALL_DIR: ${{ github.workspace }}/target/wasm32-unknown-emscripten/release
BUN_VERSION: 1.1.43
jobs:
build-revive-wasm:
runs-on: parity-large
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Cache LLVM build
id: cache-llvm
uses: actions/cache@v3
with:
path: |
target-llvm/emscripten/target-final
target-llvm/gnu/target-final
# Use a unique key based on LLVM version or configuration files to avoid cache invalidation
key: llvm-build-${{ runner.os }}-${{ hashFiles('LLVM.lock', 'Cargo.toml', 'Cargo.lock', 'crates/llvm-builder/**', '.github/workflows/build-revive-wasm.yml') }}
- name: Install system dependencies
run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld
- name: Install Rust stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rust-src
target: wasm32-unknown-emscripten
- name: Install LLVM build dependencies
run: |
make install-llvm-builder
revive-llvm --target-env emscripten clone
- name: Setup revive environment variables
run: |
echo "LLVM_SYS_181_PREFIX=$(pwd)/target-llvm/gnu/target-final" >> $GITHUB_ENV
echo "REVIVE_LLVM_TARGET_PREFIX=$(pwd)/target-llvm/emscripten/target-final" >> $GITHUB_ENV
- name: Build host LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
revive-llvm build --llvm-projects lld --llvm-projects clang
- name: Build target LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
source emsdk/emsdk_env.sh
revive-llvm --target-env emscripten build --llvm-projects lld
- run: |
rustup show
cargo --version
rustup +nightly show
cargo +nightly --version
cmake --version
bash --version
- name: Use Cached LLVM
if: steps.cache-llvm.outputs.cache-hit == 'true'
run: |
echo "Using cached LLVM"
- name: Build revive
run: |
source emsdk/emsdk_env.sh
make install-wasm
- uses: actions/upload-artifact@v4
with:
name: revive-wasm
path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_web.js
retention-days: 1
test-revive-wasm:
needs: build-revive-wasm
strategy:
matrix:
os: ["ubuntu-24.04", "macos-14", "windows-2022"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Create Target Directory
run: mkdir -p ${{ env.REVIVE_WASM_INSTALL_DIR }}
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: revive-wasm
path: ${{ env.REVIVE_WASM_INSTALL_DIR }}
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: Install Bun on Windows
if: runner.os == 'Windows'
run: |
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
scoop install bun@${{ env.BUN_VERSION }}
scoop install wget
Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH
- name: Install Bun on macOS and Linux
if: runner.os != 'Windows'
run: |
curl -fsSL https://bun.sh/install | bash -s bun-v${{ env.BUN_VERSION }}
echo "$HOME/.bun/bin" >> $GITHUB_PATH
- name: Install packages
run: npm install
- name: Run Playwright tests
run: |
cd js
npx playwright install --with-deps
npx playwright test
- name: Test revive
run: |
echo "Running tests for ${{ matrix.os }}"
npm run test:wasm
-59
View File
@@ -1,59 +0,0 @@
name: Build
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
env:
CARGO_TERM_COLOR: always
jobs:
build-ubuntu-x86:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install solc
run: |
mkdir -p solc
curl -sSL --output solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.28/solc-static-linux
chmod +x solc/solc
echo "$(pwd)/solc/" >> $GITHUB_PATH
- name: Install LLVM
run: |
curl -sSL --output llvm.tar.xz https://github.com/paritytech/revive/releases/download/v0.1.0-dev.7/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-24.04.tar.xz
mkdir llvm18
tar Jxf llvm.tar.xz -C llvm18/
echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm18" >> $GITHUB_ENV
- name: Install geth
run: |
git clone https://github.com/xermicus/go-ethereum --branch=cl/fix-runner-state-dump --depth=1
cd go-ethereum
make all
echo "$(pwd)/build/bin/" >> $GITHUB_PATH
- name: Machete
uses: bnjbvr/cargo-machete@main
- name: Format
run: make format
- name: Clippy
run: make clippy
- name: Test cargo workspace
run: make test-workspace
- name: Test CLI
run: make test-cli
- uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-resolc
path: ./target/release/resolc
retention-days: 1
+3 -10
View File
@@ -1,5 +1,4 @@
/target
target-llvm
*.dot
.vscode/
.DS_Store
@@ -7,15 +6,9 @@ target-llvm
/*.yul
/*.ll
/*.s
/llvm*
/llvm-project
/llvm18.0
node_modules
artifacts
tmp
package-lock.json
/*.html
/build
soljson.js
test-results
playwright-report
.cache
emsdk
package-lock.json
-120
View File
@@ -1,120 +0,0 @@
# Changelog
## Unreleased
This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added
- Support for the `coinbase` opcode.
### Changed
- Missing the `--overwrite` flag emits an error instead of a warning.
- The `resolc` executable prints the help by default.
- Removed support for legacy EVM assembly (EVMLA) translation.
- integration: identify cached code blobs on source code to fix potential confusions.
- Setting base, include or allow paths in emscripten is now a hard error.
- Employ a heuristic to detect `address.transfer` and `address.send` calls.
If detected, the re-entrant call flag is not set and 0 deposit limit is endowed.
### Fixed
- Solidity: Add the solc `--libraries` files to sources.
- A data race in tests.
- Fix `broken pipe` errors.
- llvm-builder: Allow warnings.
- solidity: Fix the custom compiler warning messages.
## v0.1.0-dev.9
This is a development pre-release.
### Added
### Changed
- Syscalls with more than 6 arguments now pack them into registers.
### Fixed
- Remove reloading of the resolc.js file (fix issue with relative path in web worker)
## v0.1.0-dev.8
This is a development pre-release.
### Added
- The `revive-llvm-builder` crate with the `revive-llvm` helper utility for streamlined management of the LLVM framework dependency.
- Initial support for running `resolc` in the browser.
### Changed
- Suported contracts runtime is polkadot-sdk git version `d62a90c8c729acd98c7e9a5cab9803b8b211ffc5`.
- The minimum supported Rust version is `1.81.0`.
- Error out early instead of invoking `solc` with invalid base or include path flags.
### Fixed
- Decouple the LLVM target dependency from the LLVM host dependency.
- Do not error out if no files and no errors were produced. This aligns resolc closer to solc.
- Fixes input normalization in the Wasm version.
## v0.1.0-dev.7
This is a development pre-release.
### Added
- Implement the `GASPRICE` opcode.
- Implement the `BASEFEE` opcode.
- Implement the `GASLIMIT` opcode.
### Changed
- The `GAS` opcode now returns the remaining `ref_time`.
- Contracts can now be supplied call data input of arbitrary size.
- Some syscalls now return the value in a register, slightly improving emitted contract code.
- Calls forward maximum weight limits instead of 0, anticipating a change in polkadot-sdk where weight limits of 0 no longer interprets as uncapped limit.
### Fixed
- A linker bug which was preventing certain contracts from linking with the PVM linker.
- JS: Fix encoding conversion from JS string (UTF-16) to UTF-8.
- The git commit hash slug is always displayed in the version string.
## v0.1.0-dev.6
This is a development pre-release.
# Added
- Implement the `BLOCKHASH` opcode.
- Implement delegate calls.
- Implement the `GASPRICE` opcode. Currently hard-coded to return `1`.
- The ELF shared object contract artifact is dumped into the debug output directory.
- Initial support for emitting debug info (opt in via the `-g` flag)
# Changed
- resolc now emits 64bit PolkaVM blobs, reducing contract code size and execution time.
- The RISC-V bit-manipulation target feature (`zbb`) is enabled.
# Fixed
- Compilation to Wasm (for usage in node and web browsers)
## v0.1.0-dev.5
This is development pre-release.
# Added
- Implement the `CODESIZE` and `EXTCODESIZE` opcodes.
# Changed
- Include the full revive version in the contract metadata.
# Fixed
## v0.1.0-dev-4
This is development pre-release.
# Added
- Support the `ORIGIN` opcode.
# Changed
- Update polkavm to `v0.14.0`.
- Enable the `a`, `fast-unaligned-access` and `xtheadcondmov` LLVM target features, decreasing the code size for some contracts.
# Fixed
Generated
+480 -10302
View File
File diff suppressed because it is too large Load Diff
+28 -64
View File
@@ -2,86 +2,50 @@
resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.1.0-dev.9"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>",
]
license = "MIT/Apache-2.0"
edition = "2021"
repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0"
[workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.9", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.9", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.9", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.9", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.9", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.9", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.9", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.9", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.9", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.9", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.9", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.9", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.9", path = "crates/build-utils" }
hex = "0.4.3"
hex = "0.4"
petgraph = "0.6"
cc = "1.0"
libc = "0.2.169"
libc = "0.2"
tempfile = "3.8"
anyhow = "1.0"
semver = { version = "1.0", features = ["serde"] }
itertools = "0.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
semver = { version = "1.0", features = [ "serde" ] }
itertools = "0.12"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = { version = "1.0", features = [ "arbitrary_precision" ] }
regex = "1.10"
once_cell = "1.19"
num = "0.4.3"
num = "0.4"
sha1 = "0.10"
sha2 = "0.10"
sha3 = "0.10"
thiserror = "2.0"
which = "7.0"
md5 = "0.7"
colored = "2.1"
thiserror = "1.0"
which = "5.0"
path-slash = "0.2"
rayon = "1.8"
clap = { version = "4", default-features = false, features = ["derive"] }
polkavm-common = "0.19.0"
polkavm-linker = "0.19.0"
polkavm-disassembler = "0.19.0"
polkavm = "0.19.0"
alloy-primitives = { version = "0.8.19", features = ["serde"] }
alloy-sol-types = "0.8.19"
alloy-genesis = "0.11.0"
alloy-serde = "0.11.0"
env_logger = { version = "0.11.6", default-features = false }
serde_stacker = "0.1.11"
criterion = { version = "0.5.1", features = ["html_reports"] }
log = { version = "0.4.25" }
git2 = { version = "0.20.0", default-features = false }
downloader = "0.2.8"
flate2 = "1.0.35"
fs_extra = "1.3.0"
num_cpus = "1"
tar = "0.4.43"
toml = "0.8.19"
assert_cmd = "2.0.16"
assert_fs = "1.1.2"
structopt = { version = "0.3", default-features = false }
rand = "0.8"
polkavm-common = { branch = "master_byteswap", git = "https://github.com/xermicus/polkavm.git" }
polkavm-linker = { branch = "master_byteswap", git = "https://github.com/xermicus/polkavm.git" }
polkavm = { branch = "master_byteswap", git = "https://github.com/xermicus/polkavm.git" }
alloy-primitives = "0.6"
alloy-sol-types = "0.6"
env_logger = { version = "0.10.0", default-features = false }
serde_stacker = "0.1"
criterion = { version = "0.5", features = ["html_reports"] }
# polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "274a781e8ca1a9432c7ec87593bd93214abbff50" }
# Benchmarking against EVM
primitive-types = { version = "0.12", features = ["codec"] }
evm-interpreter = { git = "https://github.com/xermicus/evm.git", branch = "separate-compilation" }
# llvm
[workspace.dependencies.inkwell]
git = "https://github.com/TheDan64/inkwell.git"
rev = "7b410298b6a93450adaa90b1841d5805a3038f12"
commit = "d916c66"
default-features = false
features = ["serde", "llvm18-0", "no-libffi-linking", "target-riscv"]
[profile.bench]
[profile.benchmark]
inherits = "release"
lto = true
codegen-units = 1
-32
View File
@@ -1,32 +0,0 @@
FROM rust:1.84.0 AS llvm-builder
WORKDIR /opt/revive
RUN apt update && \
apt upgrade -y && \
apt install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
COPY . .
RUN make install-llvm-builder
RUN revive-llvm --target-env musl clone
RUN revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang
FROM messense/rust-musl-cross:x86_64-musl AS resolc-builder
WORKDIR /opt/revive
RUN apt update && \
apt upgrade -y && \
apt install -y pkg-config
COPY . .
COPY --from=llvm-builder /opt/revive/target-llvm /opt/revive/target-llvm
ENV LLVM_SYS_181_PREFIX=/opt/revive/target-llvm/musl/target-final
RUN make install-bin
FROM alpine:latest
ADD https://github.com/ethereum/solidity/releases/download/v0.8.28/solc-static-linux /usr/bin/solc
COPY --from=resolc-builder /root/.cargo/bin/resolc /usr/bin/resolc
RUN apk add --no-cache libc6-compat
RUN chmod +x /usr/bin/solc
-3
View File
@@ -1,3 +0,0 @@
url = "https://github.com/llvm/llvm-project.git"
branch = "release/18.x"
ref = "3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff"
+18 -63
View File
@@ -1,24 +1,4 @@
.PHONY: \
install \
install-bin \
install-npm \
install-wasm \
install-llvm-builder \
install-llvm \
format \
clippy \
machete \
test \
test-integration \
test-solidity \
test-workspace \
test-cli \
test-wasm \
test-llvm-builder
bench \
bench-pvm \
bench-evm \
clean
.PHONY: install test test-solidity test-cli test-integration clean
install: install-bin install-npm
@@ -28,28 +8,7 @@ install-bin:
install-npm:
npm install && npm fund
install-wasm: install-npm
cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features
npm run build:package
install-llvm-builder:
cargo install --path crates/llvm-builder
install-llvm: install-llvm-builder
revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang
format:
cargo fmt --all --check
clippy:
cargo clippy --all-features --workspace --tests --benches -- --deny warnings
machete:
cargo install cargo-machete
cargo machete
test: format clippy machete test-cli test-workspace
test: install test-integration test-cli test-solidity
test-integration: install-bin
cargo test --package revive-integration
@@ -57,38 +16,34 @@ test-integration: install-bin
test-solidity: install
cargo test --package revive-solidity
test-workspace: install
cargo test --workspace --exclude revive-llvm-builder
test-cli: install
npm run test:cli
test-wasm: install-wasm
npm run test:wasm
bench-prepare: install-bin
cargo criterion --bench prepare --features bench-evm,bench-pvm --message-format=json \
| criterion-table > crates/benchmarks/PREPARE.md
test-llvm-builder:
@echo "warning: the llvm-builder tests will take many hours"
cargo test --package revive-llvm-builder -- --test-threads=1
bench-execute: install-bin
cargo criterion --bench execute --features bench-evm,bench-pvm --message-format=json \
| criterion-table > crates/benchmarks/EXECUTE.md
bench: install-bin
bench-extensive: install-bin
cargo criterion --all --all-features --message-format=json \
| criterion-table > crates/benchmarks/BENCHMARKS.md
bench-pvm: install-bin
cargo criterion --bench execute --features bench-pvm-interpreter --message-format=json \
| criterion-table > crates/benchmarks/PVM.md
bench-quick: install-bin
cargo criterion --all --features bench-evm
bench-evm: install-bin
cargo criterion --bench execute --features bench-evm --message-format=json \
| criterion-table > crates/benchmarks/EVM.md
bench: install-bin
cargo criterion --all --features bench-evm,bench-pvm --message-format=json \
| criterion-table > crates/benchmarks/BENCHMARKS.md
clippy:
cargo clippy --all-features --workspace --tests --benches
clean:
cargo clean ; \
revive-llvm clean ; \
rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
cargo uninstall revive-solidity ; \
cargo uninstall revive-llvm-builder ; \
rm -f package-lock.json ; \
rm -rf js/dist ; \
rm -f js/src/resolc.{wasm,js}
rm -f package-lock.json
+30 -77
View File
@@ -1,85 +1,38 @@
![CI](https://github.com/paritytech/revive/actions/workflows/rust.yml/badge.svg)
[![Docs](https://img.shields.io/badge/Docs-contracts.polkadot.io-brightgreen.svg)](https://contracts.polkadot.io)
# revive
YUL recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
YUL and EVM bytecode recompiler to LLVM, targetting RISC-V on PolkaVM.
Visit [contracts.polkadot.io](https://contracts.polkadot.io) to learn more about contracts on Polkadot!
Code bases of [frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are forked and adapted from ZKSync `zksolc`.
## Status
# Status
This is experimental software in active development and not ready just yet for production usage. Please do report any compiler related issues or missing features that are [not yet known to us](https://contracts.polkadot.io/known_issues/) here.
Currently, primary goal of this codebase is to allow for benchmarks comparing performance against ink! and solang artifacts as well as EVM interpreters.
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).
# TODO
## Installation
The project is in a very early PoC phase. Don't yet expect the produced code to be working nor to be correct for anything more than a basic flipper contract at the current stage.
`resolc` depends on the [solc](https://github.com/ethereum/solidity) binary installed on your system.
Download and install the `resolc` frontend executable for your platform from our [releases](https://github.com/paritytech/revive/releases).
## Building from source
Building revive requires a [stable Rust installation](https://rustup.rs/) and a C++ toolchain for building [LLVM](https://github.com/llvm/llvm-project) on your system.
### LLVM
`revive` depends on a custom build of LLVM `v18.1.8` with the RISC-V _embedded_ target, including the `compiler-rt` builtins. Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards.
The `Makefile` provides a shortcut target to obtain a compatible LLVM build:
```bash
make install-llvm
export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final
```
### The `resolc` Solidity frontend
To build the `resolc` Solidity frontend executable, make sure you have obtained a compatible LLVM build using [revive-llvm](crates/llvm-builder/README.md) and did export the `LLVM_SYS_181_PREFIX` environment variable pointing to it (see [above](#LLVM)).
To install the `resolc` Solidity frontend executable:
```bash
make install-bin
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.
```bash
# 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
```
### Development
Please consult the [Makefile](Makefile) targets to learn how to run tests and benchmarks.
Ensure that your branch passes `make test` locally when submitting a pull request.
## Design overview
`revive` uses [solc](https://github.com/ethereum/solidity/), the Ethereum Solidity compiler, as the [Solidity frontend](crates/solidity/src/lib.rs) to process smart contracts written in Solidity. The YUL IR code (or legacy EVM assembly as a fallback for older `solc` versions) emitted by `solc` is then translated to LLVM IR, targetting [Polkadots `revive` pallet](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html).
[Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc`.
## 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:
```bash
make test
```
- [ ] Efficient implementations of byte swaps, memset, memmove, mulmod and the like
- [ ] Use `drink` for integration tests once we have 64bit support in PolkaVM
- [x] Use PolkaVM allocator for heap space
- [ ] Exercice `schlau` and possibly `smart-bench` benchmark cases
- [x] Tests currently rely on the binary being in $PATH, which is very annoying and requires `cargo install` all the times
- [ ] Define how to do deployments
- [ ] Calling conventions for calling other contracts
- [ ] Runtime environment isn't fully figured out; implement all EVM builtins
- [ ] Iron out many leftovers from the ZKVM target
- [ ] Use of exceptions
- [ ] Change long calls (contract calls)
- [ ] Check all alignments, attributes etc. if they still make sense with our target
- [x] Custom extensions related to zk VM
- [ ] `Active Pointer`: Redundant to calldata forwarding in pallet contracts. [Mainly used here](https://github.com/matter-labs/era-contracts/blob/4aa7006153ad571643342dff22c16eaf4a70fdc1/system-contracts/contracts/libraries/EfficientCall.sol) however we could offer a similar optimization.
- []
- [ ] Add a lot more test cases
- [ ] Debug information
- [ ] Look for and implement further optimizations
- [ ] Differential testing against EVM
- [x] Switch to LLVM 18 which has `RV{32,64}E` targets upstream
- [ ] Minimize scope of "stdlib"
- [ ] Document differences from EVM
- [ ] Audit for bugs and correctness
- [ ] Rebranding
-11
View File
@@ -1,11 +0,0 @@
# Release checklist
Prior to the first stable release we neither have formal release processes nor do we follow a fixed release schedule.
To create a new pre-release:
1. Merge a release PR which updates the `-dev.X` versions in the workspace `Cargo.toml` and updates the `CHANGELOG.md` accordingly
2. Push a release tag to `main`
3. Create a __pre-release__ from the tag and manually upload the `resolc` binary from docker image
4. Manually upload `resolc.js` and `resolc.wasm` from the `build-revive-wasm` action artifacts.
5. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
-1
View File
@@ -1 +0,0 @@
https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/SECURITY.md
-30
View File
@@ -1,30 +0,0 @@
# Known issues
The following is known and we are either working on it or it is a hard limitation. Please do not open a new issue.
## Release
`0.1.0-dev-2`
## Missing features
- [Libraries with public functions are not supported](https://github.com/paritytech/revive/issues/91)
- [Automatic import resolution is not supported](https://github.com/paritytech/revive/issues/98)
- The emulated EVM linear contract memory is limited to 64kb in size. Will be fixed with support for metered dynamic memory.
- [The contract calldata is currently limited to 1kb in size](https://github.com/paritytech/revive/issues/57)
- [EIP-4844 opcodes are not supported](https://github.com/paritytech/revive/issues/64)
- [Delegate calls are not supported](https://github.com/paritytech/revive/issues/67)
- [The `blockhash` opcode is not supported](https://github.com/paritytech/revive/issues/61)
- [The `extcodesize` opcode is not supported](https://github.com/paritytech/revive/issues/58)
- [The `origin` opcode is not supported](https://github.com/paritytech/revive/issues/59)
- [Gas limits for contract calls are ignored](https://github.com/paritytech/revive/issues/60)
- [Gas related opcodes are not supported](https://github.com/paritytech/revive/issues/60)
- IPFS metadata hashes are not supported
- [Compiled contract artifacts can exceed the pallet static memory limit and fail to deploy](https://github.com/paritytech/revive/issues/96).
- [Transfers to inexistant accounts will fail if the transferred value lies below the ED.](https://github.com/paritytech/revive/issues/83) Will be fixed in the pallet to make the ED completely transparent for contracts.
## Wontfix
Please consult our documentation to learn more about Solidity and EVM features likely to remain unsupported (and why they will not be supported).
TODO: Insert link to the relevant documentation section.
Executable
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail
INSTALL_DIR="${PWD}/llvm18.0"
mkdir -p $INSTALL_DIR
# Clone LLVM 18 (any revision after commit bd32aaa is supposed to work)
if [ ! -d "llvm-project" ]; then
git clone --depth 1 --branch release/18.x https://github.com/llvm/llvm-project.git
fi
# Build LLVM, clang
cd llvm-project
mkdir -p build
cd build
cmake -G Ninja -DLLVM_ENABLE_ASSERTIONS=On \
-DLLVM_ENABLE_TERMINFO=Off \
-DLLVM_ENABLE_LIBXML2=Off \
-DLLVM_ENABLE_ZLIB=Off \
-DLLVM_ENABLE_PROJECTS='clang;lld' \
-DLLVM_TARGETS_TO_BUILD='RISCV' \
-DLLVM_ENABLE_ZSTD=Off \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \
../llvm
ninja
ninja install
# Build compiler builtins
cd ../compiler-rt
mkdir -p build
cd build
CFLAGS="--target=riscv64 -march=rv64em -mabi=lp64e -nostdlib -nodefaultlibs -mcpu=generic-rv64"
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \
-DCOMPILER_RT_BUILD_BUILTINS=ON \
-DCOMPILER_RT_BUILD_LIBFUZZER=OFF \
-DCOMPILER_RT_BUILD_MEMPROF=OFF \
-DCOMPILER_RT_BUILD_PROFILE=OFF \
-DCOMPILER_RT_BUILD_SANITIZERS=OFF \
-DCOMPILER_RT_BUILD_XRAY=OFF \
-DCMAKE_C_COMPILER=$INSTALL_DIR/bin/clang \
-DCMAKE_C_COMPILER_TARGET="riscv64" \
-DCMAKE_ASM_COMPILER_TARGET="riscv64" \
-DCMAKE_AR=$INSTALL_DIR/bin/llvm-ar \
-DCMAKE_NM=$INSTALL_DIR/bin/llvm-nm \
-DCMAKE_RANLIB=$INSTALL_DIR/bin/llvm-ranlib \
-DCOMPILER_RT_BAREMETAL_BUILD=ON \
-DLLVM_CONFIG_PATH=$INSTALL_DIR/bin/llvm-config \
-DCMAKE_C_FLAGS="$CFLAGS" \
-DCMAKE_ASM_FLAGS="$CFLAGS" \
-DCOMPILER_RT_TEST_COMPILER=$INSTALL_DIR/bin/clang \
-DCMAKE_CXX_FLAGS="$CFLAGS" \
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON \
-DCMAKE_SYSTEM_NAME=Linux \
..
ninja
ninja install
echo ""
echo "success"
echo "add this directory to your PATH: ${INSTALL_DIR}/bin/"
+88 -36
View File
@@ -4,69 +4,121 @@
- [Benchmark Results](#benchmark-results)
- [Baseline](#baseline)
- [OddPorduct](#oddporduct)
- [OddProduct](#oddproduct)
- [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative)
- [FibonacciBinet](#fibonaccibinet)
- [SHA1](#sha1)
- [PrepareBaseline](#preparebaseline)
- [PrepareOddProduct](#prepareoddproduct)
- [PrepareTriangleNumber](#preparetrianglenumber)
- [PrepareFibonacciRecursive](#preparefibonaccirecursive)
- [PrepareFibonacciIterative](#preparefibonacciiterative)
- [PrepareFibonacciBinet](#preparefibonaccibinet)
- [PrepareSHA1](#preparesha1)
## Benchmark Results
### Baseline
| | `EVM` | `PVMInterpreter` |
|:--------|:------------------------|:-------------------------------- |
| **`0`** | `3.36 us` (✅ **1.00x**) | `11.84 us` (❌ *3.52x slower*) |
| | `EVM` | `PVMInterpreter` | `PVM` |
|:--------|:--------------------------|:---------------------------------|:--------------------------------- |
| **`0`** | `855.27 ns` (✅ **1.00x**) | `729.35 ns` (✅ **1.17x faster**) | `23.19 us` (❌ *27.12x slower*) |
### OddPorduct
### OddProduct
| | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- |
| **`10000`** | `3.11 ms` (✅ **1.00x**) | `1.53 ms` (🚀 **2.03x faster**) |
| **`100000`** | `30.70 ms` (✅ **1.00x**) | `15.54 ms` (🚀 **1.98x faster**) |
| **`300000`** | `92.68 ms` (✅ **1.00x**) | `45.47 ms` (🚀 **2.04x faster**) |
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------------|:------------------------|:-------------------------------|:--------------------------------- |
| **`2000000`** | `1.51 s` (✅ **1.00x**) | `1.11 s` (✅ **1.35x faster**) | `16.91 ms` (🚀 **89.26x faster**) |
| **`4000000`** | `3.12 s` (✅ **1.00x**) | `2.09 s` (✅ **1.49x faster**) | `32.48 ms` (🚀 **96.10x faster**) |
| **`8000000`** | `6.22 s` (✅ **1.00x**) | `4.26 s` (✅ **1.46x faster**) | `65.36 ms` (🚀 **95.23x faster**) |
| **`120000000`** | `90.60 s` (✅ **1.00x**) | `59.54 s` (✅ **1.52x faster**) | `1.02 s` (🚀 **89.00x faster**) |
### TriangleNumber
| | `EVM` | `PVMInterpreter` |
|:-------------|:-------------------------|:-------------------------------- |
| **`10000`** | `2.29 ms` (✅ **1.00x**) | `1.09 ms` (🚀 **2.11x faster**) |
| **`100000`** | `22.84 ms` (✅ **1.00x**) | `10.66 ms` (🚀 **2.14x faster**) |
| **`360000`** | `82.29 ms` (✅ **1.00x**) | `37.01 ms` (🚀 **2.22x faster**) |
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------------|:------------------------|:-------------------------------|:--------------------------------- |
| **`3000000`** | `1.45 s` (✅ **1.00x**) | `1.01 s` (✅ **1.43x faster**) | `20.83 ms` (🚀 **69.67x faster**) |
| **`6000000`** | `2.92 s` (✅ **1.00x**) | `2.08 s` (✅ **1.41x faster**) | `41.97 ms` (🚀 **69.61x faster**) |
| **`12000000`** | `5.88 s` (✅ **1.00x**) | `4.05 s` (✅ **1.45x faster**) | `83.03 ms` (🚀 **70.82x faster**) |
| **`180000000`** | `89.53 s` (✅ **1.00x**) | `59.08 s` (✅ **1.52x faster**) | `1.24 s` (🚀 **72.49x faster**) |
### FibonacciRecursive
| | `EVM` | `PVMInterpreter` |
|:---------|:--------------------------|:--------------------------------- |
| **`12`** | `135.67 us` (✅ **1.00x**) | `125.02 us` ( **1.09x faster**) |
| **`16`** | `903.75 us` (✅ **1.00x**) | `762.79 us` ( **1.18x faster**) |
| **`20`** | `6.12 ms` (✅ **1.00x**) | `4.96 ms` ( **1.23x faster**) |
| **`24`** | `42.05 ms` (✅ **1.00x**) | `33.86 ms` (✅ **1.24x faster**) |
| | `EVM` | `PVMInterpreter` | `PVM` |
|:---------|:--------------------------|:---------------------------------|:---------------------------------- |
| **`26`** | `200.07 ms` (✅ **1.00x**) | `478.04 ms` (❌ *2.39x slower*) | `6.93 ms` (🚀 **28.88x faster**) |
| **`30`** | `1.37 s` (✅ **1.00x**) | `3.36 s` (❌ *2.45x slower*) | `45.17 ms` (🚀 **30.30x faster**) |
| **`34`** | `9.83 s` (✅ **1.00x**) | `22.55 s` (❌ *2.29x slower*) | `306.43 ms` (🚀 **32.08x faster**) |
| **`38`** | `66.98 s` (✅ **1.00x**) | `150.55 s` (❌ *2.25x slower*) | `2.22 s` (🚀 **30.21x faster**) |
### FibonacciIterative
| | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- |
| **`64`** | `15.04 us` (✅ **1.00x**) | `29.45 us` (❌ *1.96x slower*) |
| **`128`** | `26.36 us` (✅ **1.00x**) | `42.19 us` (❌ *1.60x slower*) |
| **`256`** | `48.61 us` (✅ **1.00x**) | `65.71 us` (❌ *1.35x slower*) |
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------------|:--------------------------|:---------------------------------|:--------------------------------- |
| **`256`** | `88.32 us` (✅ **1.00x**) | `294.08 us` (❌ *3.33x slower*) | `42.46 us` (🚀 **2.08x faster**) |
| **`100000`** | `32.88 ms` (✅ **1.00x**) | `121.70 ms` (❌ *3.70x slower*) | `1.73 ms` (🚀 **18.97x faster**) |
| **`1000000`** | `320.59 ms` (✅ **1.00x**) | `1.25 s` (❌ *3.89x slower*) | `15.60 ms` (🚀 **20.55x faster**) |
| **`100000000`** | `33.09 s` (✅ **1.00x**) | `125.08 s` (❌ *3.78x slower*) | `1.49 s` (🚀 **22.18x faster**) |
### FibonacciBinet
| | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:-------------------------------- |
| **`64`** | `15.22 us` (✅ **1.00x**) | `41.46 us` (❌ *2.72x slower*) |
| **`128`** | `17.05 us` (✅ **1.00x**) | `42.84 us` (❌ *2.51x slower*) |
| **`256`** | `19.00 us` (✅ **1.00x**) | `44.36 us` (❌ *2.34x slower*) |
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------|:-------------------------|:---------------------------------|:-------------------------------- |
| **`64`** | `20.15 us` (✅ **1.00x**) | `129.45 us` (❌ *6.42x slower*) | `39.56 us` (❌ *1.96x slower*) |
| **`128`** | `22.97 us` (✅ **1.00x**) | `150.62 us` (❌ *6.56x slower*) | `40.13 us` (❌ *1.75x slower*) |
| **`256`** | `26.20 us` (✅ **1.00x**) | `165.38 us` (❌ *6.31x slower*) | `39.70 us` (❌ *1.52x slower*) |
### SHA1
| | `EVM` | `PVMInterpreter` |
|:----------|:--------------------------|:--------------------------------- |
| **`1`** | `110.04 us` (✅ **1.00x**) | `216.11 us` (❌ *1.96x slower*) |
| **`64`** | `209.04 us` (✅ **1.00x**) | `309.48 us` (❌ *1.48x slower*) |
| **`512`** | `903.65 us` (✅ **1.00x**) | `980.49 us` ( **1.09x slower**) |
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------|:--------------------------|:---------------------------------|:--------------------------------- |
| **`1`** | `216.56 us` (✅ **1.00x**) | `328.90 us` (❌ *1.52x slower*) | `43.54 us` (🚀 **4.97x faster**) |
| **`64`** | `442.13 us` (✅ **1.00x**) | `553.22 us` (❌ *1.25x slower*) | `45.73 us` (🚀 **9.67x faster**) |
| **`512`** | `1.90 ms` (✅ **1.00x**) | `2.27 ms` (❌ *1.19x slower*) | `78.40 us` (🚀 **24.21x faster**) |
### PrepareBaseline
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:---------------------------------- |
| **`0`** | `177.34 ns` (✅ **1.00x**) | `10.83 us` (❌ *61.07x slower*) | `1.33 us` (❌ *7.49x slower*) | `33.43 us` (❌ *188.48x slower*) | `69.26 us` (❌ *390.56x slower*) |
### PrepareOddProduct
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:---------------------------------|:---------------------------------- |
| **`0`** | `486.78 ns` (✅ **1.00x**) | `11.43 us` (❌ *23.49x slower*) | `1.35 us` (❌ *2.78x slower*) | `33.95 us` (❌ *69.75x slower*) | `68.19 us` (❌ *140.07x slower*) |
### PrepareTriangleNumber
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:---------------------------------- |
| **`0`** | `489.04 ns` (✅ **1.00x**) | `23.99 us` (❌ *49.06x slower*) | `1.33 us` (❌ *2.72x slower*) | `61.40 us` (❌ *125.56x slower*) | `73.01 us` (❌ *149.29x slower*) |
### PrepareFibonacciRecursive
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:---------------------------------- |
| **`0`** | `411.19 ns` (✅ **1.00x**) | `22.32 us` (❌ *54.27x slower*) | `1.43 us` (❌ *3.49x slower*) | `54.52 us` (❌ *132.59x slower*) | `68.99 us` (❌ *167.77x slower*) |
### PrepareFibonacciIterative
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:---------------------------------- |
| **`0`** | `313.74 ns` (✅ **1.00x**) | `19.15 us` (❌ *61.04x slower*) | `1.39 us` (❌ *4.44x slower*) | `48.30 us` (❌ *153.95x slower*) | `69.20 us` (❌ *220.57x slower*) |
### PrepareFibonacciBinet
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:--------------------------------- |
| **`0`** | `700.40 ns` (✅ **1.00x**) | `41.78 us` (❌ *59.65x slower*) | `1.40 us` (❌ *2.00x slower*) | `92.23 us` (❌ *131.69x slower*) | `68.52 us` (❌ *97.83x slower*) |
### PrepareSHA1
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:------------------------|:----------------------------------|:-------------------------------------|:-----------------------------------|:--------------------------------- |
| **`0`** | `1.77 us` (✅ **1.00x**) | `124.24 us` (❌ *70.39x slower*) | `1.33 us` (✅ **1.33x faster**) | `242.14 us` (❌ *137.19x slower*) | `69.28 us` (❌ *39.25x slower*) |
---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
+15 -11
View File
@@ -1,28 +1,32 @@
[package]
name = "revive-benchmarks"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
description = "revive compiler benchmarks"
version = "0.1.0"
edition = "2021"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
]
[features]
default = ["bench-pvm-interpreter"]
bench-pvm-interpreter = ["revive-runner"]
bench-pvm-interpreter = []
bench-pvm = []
bench-evm = ["revive-differential"]
bench-extensive = []
[dependencies]
hex = { workspace = true }
polkavm = { workspace = true }
revive-integration = { path = "../integration" }
revive-differential = { path = "../differential", optional = true }
alloy-primitives = { workspace = true }
revive-integration = { workspace = true }
revive-differential = { workspace = true, optional = true }
revive-runner = { workspace = true, optional = true }
[dev-dependencies]
criterion = { workspace = true }
[[bench]]
name = "execute"
harness = false
[[bench]]
name = "prepare"
harness = false
+115 -54
View File
@@ -1,113 +1,174 @@
#![cfg(any(feature = "bench-pvm-interpreter", feature = "bench-evm"))]
#[cfg(feature = "bench-extensive")]
use std::time::Duration;
use alloy_primitives::U256;
use criterion::{
criterion_group, criterion_main,
measurement::{Measurement, WallTime},
BenchmarkGroup, BenchmarkId, Criterion,
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, BenchmarkId,
Criterion,
};
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
use polkavm::BackendKind;
use revive_benchmarks::prepare_pvm;
use revive_integration::cases::Contract;
fn bench<P, L, I>(
mut group: BenchmarkGroup<'_, WallTime>,
parameters: &[P],
labels: &[L],
contract: I,
) where
fn bench<P, L, I, M>(mut group: BenchmarkGroup<'_, M>, parameters: &[P], labels: &[L], contract: I)
where
P: Clone,
L: std::fmt::Display,
I: Fn(P) -> Contract,
M: Measurement,
{
assert_eq!(parameters.len(), labels.len());
group.sample_size(10);
for (p, l) in parameters.iter().zip(labels.iter()) {
let contract = contract(p.clone());
#[cfg(feature = "bench-evm")]
group.bench_with_input(BenchmarkId::new("EVM", l), p, |b, _| {
let code = &contract.evm_runtime;
let input = &contract.calldata;
b.iter_custom(|iters| revive_benchmarks::measure_evm(code, input, iters));
});
{
let contract = contract(p.clone());
let vm = revive_differential::prepare(contract.evm_runtime, contract.calldata);
group.bench_with_input(BenchmarkId::new("EVM", l), p, move |b, _| {
b.iter(|| {
revive_differential::execute(vm.clone());
});
});
}
#[cfg(feature = "bench-pvm-interpreter")]
group.bench_with_input(BenchmarkId::new("PVMInterpreter", l), p, |b, _| {
let specs = revive_benchmarks::create_specs(&contract);
b.iter_custom(|iters| revive_benchmarks::measure_pvm(&specs, iters));
});
{
let contract = contract(p.clone());
let (state, mut instance, export) = prepare_pvm(
&contract.pvm_runtime,
&contract.calldata,
BackendKind::Interpreter,
);
group.bench_with_input(BenchmarkId::new("PVMInterpreter", l), p, |b, _| {
b.iter(|| {
revive_integration::mock_runtime::call(state.clone(), &mut instance, export);
});
});
}
#[cfg(feature = "bench-pvm")]
{
let contract = contract(p.clone());
let (state, mut instance, export) = prepare_pvm(
&contract.pvm_runtime,
&contract.calldata,
BackendKind::Compiler,
);
group.bench_with_input(BenchmarkId::new("PVM", l), p, |b, _| {
b.iter(|| {
revive_integration::mock_runtime::call(state.clone(), &mut instance, export);
});
});
}
}
group.finish();
}
fn group<'error, M>(c: &'error mut Criterion<M>, group_name: &str) -> BenchmarkGroup<'error, M>
#[cfg(feature = "bench-extensive")]
fn group_extensive<'error, M>(
c: &'error mut Criterion<M>,
group_name: &str,
) -> BenchmarkGroup<'error, M>
where
M: Measurement,
{
c.benchmark_group(group_name)
let mut group = c.benchmark_group(group_name);
group
.sample_size(10)
.measurement_time(Duration::from_secs(60));
group
}
fn bench_baseline(c: &mut Criterion) {
let group = group(c, "Baseline");
let parameters = &[0u8];
bench(group, parameters, parameters, |_| Contract::baseline());
bench(
c.benchmark_group("Baseline"),
parameters,
parameters,
|_| Contract::baseline(),
);
}
fn bench_odd_product(c: &mut Criterion) {
let group = group(c, "OddPorduct");
let parameters = &[10_000, 100_000, 300000];
#[cfg(feature = "bench-extensive")]
let group = group_extensive(c, "OddProduct");
#[cfg(not(feature = "bench-extensive"))]
let group = c.benchmark_group("OddProduct");
#[cfg(feature = "bench-extensive")]
let parameters = &[2_000_000i32, 4_000_000, 8_000_000, 120_000_000];
#[cfg(not(feature = "bench-extensive"))]
let parameters = &[10_000, 100_000];
bench(group, parameters, parameters, Contract::odd_product);
}
fn bench_triangle_number(c: &mut Criterion) {
let group = group(c, "TriangleNumber");
let parameters = &[10_000, 100_000, 360000];
#[cfg(feature = "bench-extensive")]
let group = group_extensive(c, "TriangleNumber");
#[cfg(not(feature = "bench-extensive"))]
let group = c.benchmark_group("TriangleNumber");
#[cfg(feature = "bench-extensive")]
let parameters = &[3_000_000i64, 6_000_000, 12_000_000, 180_000_000];
#[cfg(not(feature = "bench-extensive"))]
let parameters = &[10_000, 100_000];
bench(group, parameters, parameters, Contract::triangle_number);
}
fn bench_fibonacci_recurisve(c: &mut Criterion) {
let group = group(c, "FibonacciRecursive");
let parameters = [12, 16, 20, 24]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
#[cfg(not(feature = "bench-extensive"))]
let group = c.benchmark_group("FibonacciRecursive");
#[cfg(feature = "bench-extensive")]
let group = group_extensive(c, "FibonacciRecursive");
bench(group, &parameters, &parameters, Contract::fib_recursive);
#[cfg(feature = "bench-extensive")]
let parameters = &[26, 30, 34, 38];
#[cfg(not(feature = "bench-extensive"))]
let parameters = &[12, 16, 20];
bench(group, parameters, parameters, Contract::fib_recursive);
}
fn bench_fibonacci_iterative(c: &mut Criterion) {
let group = group(c, "FibonacciIterative");
let parameters = [64, 128, 256]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
#[cfg(not(feature = "bench-extensive"))]
let group = c.benchmark_group("FibonacciIterative");
#[cfg(feature = "bench-extensive")]
let group = group_extensive(c, "FibonacciIterative");
bench(group, &parameters, &parameters, Contract::fib_iterative);
#[cfg(feature = "bench-extensive")]
let parameters = &[256, 100000, 1000000, 100000000];
#[cfg(not(feature = "bench-extensive"))]
let parameters = &[64, 128, 256];
bench(group, parameters, parameters, Contract::fib_iterative);
}
fn bench_fibonacci_binet(c: &mut Criterion) {
let group = group(c, "FibonacciBinet");
let parameters = [64, 128, 256]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
let parameters = &[64, 128, 256];
bench(group, &parameters, &parameters, Contract::fib_binet);
bench(
c.benchmark_group("FibonacciBinet"),
parameters,
parameters,
Contract::fib_binet,
);
}
fn bench_sha1(c: &mut Criterion) {
let group = group(c, "SHA1");
let parameters = &[vec![0xff], vec![0xff; 64], vec![0xff; 512]];
let labels = parameters.iter().map(|p| p.len()).collect::<Vec<_>>();
bench(group, parameters, &labels, |input| {
Contract::sha1(input.into())
});
bench(
c.benchmark_group("SHA1"),
parameters,
&labels,
Contract::sha1,
);
}
criterion_group!(
+172
View File
@@ -0,0 +1,172 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use polkavm::BackendKind;
use revive_benchmarks::instantiate_engine;
use revive_integration::cases::Contract;
fn bench(
c: &mut Criterion,
group_name: &str,
#[cfg(feature = "bench-evm")] evm_runtime: Vec<u8>,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))] pvm_runtime: Vec<u8>,
) {
let mut group = c.benchmark_group(group_name);
let code_size = 0;
#[cfg(feature = "bench-evm")]
group.bench_with_input(
BenchmarkId::new("Evm", code_size),
&evm_runtime,
|b, code| b.iter(|| revive_differential::prepare(code.clone(), Vec::new())),
);
#[cfg(feature = "bench-pvm-interpreter")]
{
let engine = instantiate_engine(BackendKind::Interpreter);
group.bench_with_input(
BenchmarkId::new("PVMInterpreterCompile", code_size),
&(&pvm_runtime, engine),
|b, (code, engine)| {
b.iter(|| {
revive_integration::mock_runtime::recompile_code(code, engine);
});
},
);
}
#[cfg(feature = "bench-pvm-interpreter")]
{
let engine = instantiate_engine(BackendKind::Interpreter);
let module = revive_integration::mock_runtime::recompile_code(&pvm_runtime, &engine);
group.bench_with_input(
BenchmarkId::new("PVMInterpreterInstantiate", code_size),
&(module, engine),
|b, (module, engine)| {
b.iter(|| {
revive_integration::mock_runtime::instantiate_module(module, engine);
});
},
);
}
#[cfg(feature = "bench-pvm")]
{
let engine = instantiate_engine(BackendKind::Compiler);
group.bench_with_input(
BenchmarkId::new("PVMCompile", code_size),
&(&pvm_runtime, engine),
|b, (code, engine)| {
b.iter(|| {
revive_integration::mock_runtime::recompile_code(code, engine);
});
},
);
}
#[cfg(feature = "bench-pvm")]
{
let engine = instantiate_engine(BackendKind::Compiler);
let module = revive_integration::mock_runtime::recompile_code(&pvm_runtime, &engine);
group.bench_with_input(
BenchmarkId::new("PVMInstantiate", code_size),
&(module, engine),
|b, (module, engine)| {
b.iter(|| {
revive_integration::mock_runtime::instantiate_module(module, engine);
});
},
);
}
group.finish();
}
fn bench_baseline(c: &mut Criterion) {
bench(
c,
"PrepareBaseline",
#[cfg(feature = "bench-evm")]
Contract::baseline().evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::baseline().pvm_runtime,
);
}
fn bench_odd_product(c: &mut Criterion) {
bench(
c,
"PrepareOddProduct",
#[cfg(feature = "bench-evm")]
Contract::odd_product(0).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::baseline().pvm_runtime,
);
}
fn bench_triangle_number(c: &mut Criterion) {
bench(
c,
"PrepareTriangleNumber",
#[cfg(feature = "bench-evm")]
Contract::triangle_number(0).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::triangle_number(0).pvm_runtime,
);
}
fn bench_fibonacci_recursive(c: &mut Criterion) {
bench(
c,
"PrepareFibonacciRecursive",
#[cfg(feature = "bench-evm")]
Contract::fib_recursive(0).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::fib_recursive(0).pvm_runtime,
);
}
fn bench_fibonacci_iterative(c: &mut Criterion) {
bench(
c,
"PrepareFibonacciIterative",
#[cfg(feature = "bench-evm")]
Contract::fib_iterative(0).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::fib_iterative(0).pvm_runtime,
);
}
fn bench_fibonacci_binet(c: &mut Criterion) {
bench(
c,
"PrepareFibonacciBinet",
#[cfg(feature = "bench-evm")]
Contract::fib_binet(0).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::fib_binet(0).pvm_runtime,
);
}
fn bench_sha1(c: &mut Criterion) {
bench(
c,
"PrepareSHA1",
#[cfg(feature = "bench-evm")]
Contract::sha1(Default::default()).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::sha1(Default::default()).pvm_runtime,
);
}
criterion_group!(
name = prepare;
config = Criterion::default();
targets = bench_baseline,
bench_odd_product,
bench_triangle_number,
bench_fibonacci_recursive,
bench_fibonacci_iterative,
bench_fibonacci_binet,
bench_sha1
);
criterion_main!(prepare);
+21 -67
View File
@@ -1,70 +1,24 @@
#[cfg(feature = "bench-pvm-interpreter")]
pub fn create_specs(contract: &revive_integration::cases::Contract) -> revive_runner::Specs {
use revive_runner::*;
use SpecsAction::*;
Specs {
differential: false,
actions: vec![
Instantiate {
code: Code::Bytes(contract.pvm_runtime.to_vec()),
origin: TestAddress::Alice,
data: Default::default(),
value: Default::default(),
gas_limit: Default::default(),
storage_deposit_limit: Default::default(),
salt: Default::default(),
},
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
data: contract.calldata.to_vec(),
value: Default::default(),
gas_limit: Default::default(),
storage_deposit_limit: Default::default(),
},
],
..Default::default()
}
use polkavm::{BackendKind, Config, Engine, ExportIndex, Instance, SandboxKind};
use revive_integration::mock_runtime;
use revive_integration::mock_runtime::State;
pub fn prepare_pvm(
code: &[u8],
input: &[u8],
backend: BackendKind,
) -> (State, Instance<State>, ExportIndex) {
let mut config = Config::new();
config.set_backend(Some(backend));
config.set_sandbox(Some(SandboxKind::Linux));
let (instance, export_index) = mock_runtime::prepare(code, Some(config));
(State::new(input.to_vec()), instance, export_index)
}
#[cfg(feature = "bench-pvm-interpreter")]
pub fn measure_pvm(specs: &revive_runner::Specs, iters: u64) -> std::time::Duration {
use revive_runner::*;
let mut total_time = std::time::Duration::default();
for _ in 0..iters {
let results = specs.clone().run();
let CallResult::Exec { result, wall_time } =
results.get(1).expect("contract should have been called")
else {
panic!("expected a execution result");
};
let ret = result.result.as_ref().unwrap();
assert!(!ret.did_revert());
total_time += *wall_time;
}
total_time
}
#[cfg(feature = "bench-evm")]
pub fn measure_evm(code: &[u8], input: &[u8], iters: u64) -> std::time::Duration {
let mut total_time = std::time::Duration::default();
let code = hex::encode(code);
for _ in 0..iters {
let log = revive_differential::Evm::default()
.code_blob(code.as_bytes().to_vec())
.input(input.to_vec().into())
.bench(true)
.run();
assert!(log.output.run_success(), "evm run failed: {log:?}");
total_time += log.execution_time().unwrap();
}
total_time
pub fn instantiate_engine(backend: BackendKind) -> Engine {
let mut config = Config::new();
config.set_backend(Some(backend));
config.set_sandbox(Some(SandboxKind::Linux));
mock_runtime::setup(Some(config))
}
-14
View File
@@ -1,14 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# IDE
/.idea/
/.vscode/
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "revive-build-utils"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
]
description = "Shared build utilities of the revive compiler"
[lib]
doctest = false
[dependencies]
-10
View File
@@ -1,10 +0,0 @@
# revive: Compiler Common
## License
This library is distributed under the terms of either
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.
-48
View File
@@ -1,48 +0,0 @@
//! The compiler build utilities library.
/// The revive LLVM host dependency directory prefix environment variable.
pub const REVIVE_LLVM_HOST_PREFIX: &str = "LLVM_SYS_181_PREFIX";
/// The revive LLVM target dependency directory prefix environment variable.
pub const REVIVE_LLVM_TARGET_PREFIX: &str = "REVIVE_LLVM_TARGET_PREFIX";
/// Constructs a path to the LLVM tool `name`.
///
/// Respects the [`REVIVE_LLVM_HOST_PREFIX`] environment variable.
pub fn llvm_host_tool(name: &str) -> std::path::PathBuf {
std::env::var_os(REVIVE_LLVM_HOST_PREFIX)
.map(Into::<std::path::PathBuf>::into)
.unwrap_or_else(|| {
panic!(
"install LLVM using the revive-llvm builder and export {REVIVE_LLVM_HOST_PREFIX}",
)
})
.join("bin")
.join(name)
}
/// Returns the LLVM lib dir.
///
/// Respects the [`REVIVE_LLVM_HOST_PREFIX`] environment variable.
pub fn llvm_lib_dir() -> std::path::PathBuf {
std::path::PathBuf::from(llvm_config("--libdir").trim())
}
/// Returns the LLVM CXX compiler flags.
///
/// Respects the [`REVIVE_LLVM_HOST_PREFIX`] environment variable.
pub fn llvm_cxx_flags() -> String {
llvm_config("--cxxflags")
}
/// Execute the `llvm-config` utility respecting the [`REVIVE_LLVM_HOST_PREFIX`] environment variable.
fn llvm_config(arg: &str) -> String {
let llvm_config = llvm_host_tool("llvm-config");
let output = std::process::Command::new(&llvm_config)
.arg(arg)
.output()
.unwrap_or_else(|error| panic!("`{} {arg}` failed: {error}", llvm_config.display()));
String::from_utf8(output.stdout)
.unwrap_or_else(|_| panic!("output of `{} {arg}` should be utf8", llvm_config.display()))
}
+5 -8
View File
@@ -1,12 +1,9 @@
[package]
name = "revive-builtins"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
version = "0.1.0"
edition = "2021"
build = "build.rs"
description = "compiler builtins for the revive compiler"
[build-dependencies]
revive-build-utils = { workspace = true }
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+11 -25
View File
@@ -1,44 +1,30 @@
use std::{env, fs, io::Read, path::Path, process::Command};
pub const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
fn main() {
println!(
"cargo:rerun-if-env-changed={}",
revive_build_utils::REVIVE_LLVM_HOST_PREFIX
);
let llvm_config = revive_build_utils::llvm_host_tool("llvm-config");
let lib = "libclang_rt.builtins-riscv64.a";
let mut llvm_lib_dir = String::new();
Command::new(&llvm_config)
.arg("--libdir")
Command::new("llvm-config")
.args(["--libdir"])
.output()
.unwrap_or_else(|_| {
panic!(
"{} should be able to provide LD path",
llvm_config.display()
)
})
.expect("llvm-config should be able to provide LD path")
.stdout
.as_slice()
.read_to_string(&mut llvm_lib_dir)
.expect("llvm-config output should be utf8");
let lib_path = revive_build_utils::llvm_lib_dir()
.join("unknown")
.join(BUILTINS_ARCHIVE_FILE);
let archive = fs::read(&lib_path).expect("clang builtins not found");
println!("cargo:rerun-if-env-changed={}", lib_path.display());
let lib_path = std::path::PathBuf::from(llvm_lib_dir.trim())
.join("linux")
.join(lib);
let archive = fs::read(lib_path).expect("clang builtins for riscv64 not found");
let out_dir = env::var_os("OUT_DIR").expect("has OUT_DIR");
let archive_path = Path::new(&out_dir).join(BUILTINS_ARCHIVE_FILE);
let archive_path = Path::new(&out_dir).join(lib);
let len = archive.len();
std::fs::write(archive_path, &archive).expect("can write to OUT_DIR");
let src_path = Path::new(&out_dir).join("compiler_rt.rs");
let src = format!(
"pub static COMPILER_RT: &[u8; {len}] = include_bytes!(\"{BUILTINS_ARCHIVE_FILE}\");"
);
let src = format!("pub static COMPILER_RT: &[u8; {len}] = include_bytes!(\"{lib}\");");
fs::write(src_path, src).expect("can write to OUT_DIR");
println!("cargo:rerun-if-changed=build.rs");
+39
View File
@@ -0,0 +1,39 @@
---
name: Bug report
about: Use this template for reporting issues
title: ''
labels: bug
assignees: ''
---
### 🐛 Bug Report
#### 📝 Description
Provide a clear and concise description of the bug.
#### 🔄 Reproduction Steps
Steps to reproduce the behaviour
#### 🤔 Expected Behavior
Describe what you expected to happen.
#### 😯 Current Behavior
Describe what actually happened.
#### 🖥️ Environment
Any relevant environment details.
#### 📋 Additional Context
Add any other context about the problem here. If applicable, add screenshots to help explain.
#### 📎 Log Output
```
Paste any relevant log output here.
```
+21
View File
@@ -0,0 +1,21 @@
---
name: Feature request
about: Use this template for requesting features
title: ''
labels: feat
assignees: ''
---
### 🌟 Feature Request
#### 📝 Description
Provide a clear and concise description of the feature you'd like to see.
#### 🤔 Rationale
Explain why this feature is important and how it benefits the project.
#### 📋 Additional Context
Add any other context or information about the feature request here.
+20
View File
@@ -0,0 +1,20 @@
# What ❔
<!-- What are the changes this PR brings about? -->
<!-- Example: This PR adds a PR template to the repo. -->
<!-- (For bigger PRs adding more context is appreciated) -->
## Why ❔
<!-- Why are these changes done? What goal do they contribute to? What are the principles behind them? -->
<!-- Example: PR templates ensure PR reviewers, observers, and future iterators are in context about the evolution of repos. -->
## Checklist
<!-- Check your PR fulfills the following items. -->
<!-- For draft PRs check the boxes as you complete them. -->
- [ ] PR title corresponds to the body of PR.
- [ ] Tests for the changes have been added / updated.
- [ ] Documentation comments have been added / updated.
- [ ] Code has been formatted via `cargo fmt` and checked with `cargo clippy`.
+9
View File
@@ -0,0 +1,9 @@
name: Cargo license check
on: pull_request
jobs:
cargo-deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1
+23
View File
@@ -0,0 +1,23 @@
name: "Rust CI"
on:
pull_request:
jobs:
build:
name: cargo build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo build --verbose
formatting:
name: cargo fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: rustfmt
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
+17
View File
@@ -0,0 +1,17 @@
name: Leaked Secrets Scan
on: [pull_request]
jobs:
TruffleHog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
with:
fetch-depth: 0
- name: TruffleHog OSS
uses: trufflesecurity/trufflehog@0c66d30c1f4075cee1aada2e1ab46dabb1b0071a
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --debug --only-verified
+3 -4
View File
@@ -1,13 +1,12 @@
[package]
name = "revive-common"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
version = "0.1.0"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
"Oleksandr Zarudnyi <a.zarudnyy@matterlabs.dev>",
]
license = "MIT OR Apache-2.0"
edition = "2021"
description = "Shared constants of the revive compiler"
[lib]
+26 -1
View File
@@ -1,4 +1,13 @@
# revive: Compiler Common
# zkSync Era: Compiler Common
[![Logo](eraLogo.svg)](https://zksync.io/)
zkSync Era is a layer 2 rollup that uses zero-knowledge proofs to scale Ethereum without compromising on security
or decentralization. As it's EVM-compatible (with Solidity/Vyper), 99% of Ethereum projects can redeploy without
needing to refactor or re-audit any code. zkSync Era also uses an LLVM-based compiler that will eventually enable
developers to write smart contracts in popular languages such as C++ and Rust.
This repository contains the common compiler constants.
## License
@@ -8,3 +17,19 @@ This library is distributed under the terms of either
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.
## Official Links
- [Website](https://zksync.io/)
- [GitHub](https://github.com/matter-labs)
- [Twitter](https://twitter.com/zksync)
- [Twitter for Devs](https://twitter.com/zkSyncDevs)
- [Discord](https://join.zksync.dev/)
## Disclaimer
zkSync Era has been through extensive testing and audits, and although it is live, it is still in alpha state and
will undergo further audits and bug bounty programs. We would love to hear our community's thoughts and suggestions
about it!
It's important to note that forking it now could potentially lead to missing important
security updates, critical features, and performance improvements.
+2
View File
@@ -1,4 +1,6 @@
//!
//! The number base constants.
//!
/// The binary number base.
pub const BASE_BINARY: u32 = 2;
+5 -11
View File
@@ -1,4 +1,6 @@
//!
//! The common sizes in bits.
//!
/// The `bool` type bit-length.
pub const BIT_LENGTH_BOOLEAN: usize = 1;
@@ -16,16 +18,8 @@ pub const BIT_LENGTH_X64: usize = crate::byte_length::BYTE_LENGTH_X64 * BIT_LENG
pub const BIT_LENGTH_ETH_ADDRESS: usize =
crate::byte_length::BYTE_LENGTH_ETH_ADDRESS * BIT_LENGTH_BYTE;
/// The VM word (usually `u256` or `i256`) bit-length.
pub const BIT_LENGTH_WORD: usize = crate::byte_length::BYTE_LENGTH_WORD * BIT_LENGTH_BYTE;
/// The field (usually `u256` or `i256`) bit-length.
pub const BIT_LENGTH_FIELD: usize = crate::byte_length::BYTE_LENGTH_FIELD * BIT_LENGTH_BYTE;
/// Bit length of the runtime value type.
pub const BIT_LENGTH_VALUE: usize = BIT_LENGTH_WORD;
/// Bit length of thre runimte block number type.
pub const BIT_LENGTH_BLOCK_NUMBER: usize =
crate::byte_length::BYTE_LENGTH_BLOCK_NUMBER * BIT_LENGTH_BYTE;
/// Bit length of thre runimte block timestamp type.
pub const BIT_LENGTH_BLOCK_TIMESTAMP: usize =
crate::byte_length::BYTE_LENGTH_BLOCK_TIMESTAMP * BIT_LENGTH_BYTE;
pub const BIT_LENGTH_VALUE: usize = crate::byte_length::BYTE_LENGTH_VALUE * BIT_LENGTH_BYTE;
+8 -12
View File
@@ -1,4 +1,6 @@
//!
//! The common sizes in bytes.
//!
/// The byte-length.
pub const BYTE_LENGTH_BYTE: usize = 1;
@@ -6,23 +8,17 @@ pub const BYTE_LENGTH_BYTE: usize = 1;
/// The x86 word byte-length.
pub const BYTE_LENGTH_X32: usize = 4;
/// Native stack alignment size in bytes
pub const BYTE_LENGTH_STACK_ALIGN: usize = BYTE_LENGTH_X64;
/// The x86_64 word byte-length.
pub const BYTE_LENGTH_X64: usize = 8;
/// EVM native stack alignment size in bytes
pub const BYTE_LENGTH_STACK_ALIGN: usize = 32;
/// The ETH address byte-length.
pub const BYTE_LENGTH_ETH_ADDRESS: usize = 20;
/// The word byte-length.
pub const BYTE_LENGTH_WORD: usize = 32;
/// The field byte-length.
pub const BYTE_LENGTH_FIELD: usize = 32;
/// Byte length of the runtime value type.
pub const BYTE_LENGTH_VALUE: usize = 32;
/// Byte length of the runtime block number type.
pub const BYTE_LENGTH_BLOCK_NUMBER: usize = 4;
/// Byte length of the runtime block timestamp type.
pub const BYTE_LENGTH_BLOCK_TIMESTAMP: usize = 4;
pub const BYTE_LENGTH_VALUE: usize = 16;
+117
View File
@@ -0,0 +1,117 @@
//!
//! The EraVM address constants.
//!
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_TO_L1: u16 = 0xFFFF;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_PRECOMPILE: u16 = 0xFFFD;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_META: u16 = 0xFFFC;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_MIMIC_CALL: u16 = 0xFFFB;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_SYSTEM_MIMIC_CALL: u16 = 0xFFFA;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_MIMIC_CALL_BYREF: u16 = 0xFFF9;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_SYSTEM_MIMIC_CALL_BYREF: u16 = 0xFFF8;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_RAW_FAR_CALL: u16 = 0xFFF7;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_RAW_FAR_CALL_BYREF: u16 = 0xFFF6;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_SYSTEM_CALL: u16 = 0xFFF5;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_SYSTEM_CALL_BYREF: u16 = 0xFFF4;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_SET_CONTEXT_VALUE_CALL: u16 = 0xFFF3;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_SET_PUBDATA_PRICE: u16 = 0xFFF2;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_INCREMENT_TX_COUNTER: u16 = 0xFFF1;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_GET_GLOBAL_PTR_CALLDATA: u16 = 0xFFF0;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_GET_GLOBAL_CALL_FLAGS: u16 = 0xFFEF;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_GET_GLOBAL_PTR_RETURN_DATA: u16 = 0xFFEE;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_EVENT_INITIALIZE: u16 = 0xFFED;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_EVENT_WRITE: u16 = 0xFFEC;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_LOAD_CALLDATA: u16 = 0xFFEB;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_LOAD_RETURN_DATA: u16 = 0xFFEA;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_ADD: u16 = 0xFFE9;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_SHRINK: u16 = 0xFFE8;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_PACK: u16 = 0xFFE7;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_MULTIPLICATION_HIGH_REGISTER: u16 = 0xFFE6;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_GET_GLOBAL_EXTRA_ABI_DATA: u16 = 0xFFE5;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_DATA_LOAD: u16 = 0xFFE4;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_DATA_COPY: u16 = 0xFFE3;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_DATA_SIZE: u16 = 0xFFE2;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_CONST_ARRAY_DECLARE: u16 = 0xFFE1;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_CONST_ARRAY_SET: u16 = 0xFFE0;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_CONST_ARRAY_FINALIZE: u16 = 0xFFDF;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_CONST_ARRAY_GET: u16 = 0xFFDE;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_DECOMMIT: u16 = 0xFFDD;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_LOAD_DECOMMIT: u16 = 0xFFDC;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_RETURN_FORWARD: u16 = 0xFFDB;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_REVERT_FORWARD: u16 = 0xFFDA;
/// The corresponding simulation predefined address.
pub const ERAVM_ADDRESS_ACTIVE_PTR_SWAP: u16 = 0xFFD9;
+5
View File
@@ -0,0 +1,5 @@
//!
//! The EraVM constants.
//!
pub mod address;
+4
View File
@@ -1,9 +1,13 @@
//!
//! The EVM version.
//!
use serde::Deserialize;
use serde::Serialize;
///
/// The EVM version.
///
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "camelCase")]
pub enum EVMVersion {
+3 -1
View File
@@ -1,4 +1,6 @@
//! The revive exit code constants.
//!
//! The exit code constants.
//!
/// The common application success exit code.
pub const EXIT_CODE_SUCCESS: i32 = 0;
+15 -7
View File
@@ -1,4 +1,6 @@
//!
//! The file extensions.
//!
/// The manifest file extension.
pub static EXTENSION_MANIFEST: &str = "toml";
@@ -12,6 +14,12 @@ pub static EXTENSION_ABI: &str = "abi";
/// The Yul IR file extension.
pub static EXTENSION_YUL: &str = "yul";
/// The EVM legacy assembly IR file extension.
pub static EXTENSION_EVMLA: &str = "evmla";
/// The Ethereal IR file extension.
pub static EXTENSION_ETHIR: &str = "ethir";
/// The EVM file extension.
pub static EXTENSION_EVM: &str = "evm";
@@ -24,17 +32,17 @@ pub static EXTENSION_SOLIDITY: &str = "sol";
/// The LLL IR file extension.
pub static EXTENSION_LLL: &str = "lll";
/// The Vyper file extension.
pub static EXTENSION_VYPER: &str = "vy";
/// The LLVM source code file extension.
pub static EXTENSION_LLVM_SOURCE: &str = "ll";
/// The LLVM bitcode file extension.
pub static EXTENSION_LLVM_BINARY: &str = "bc";
/// The PolkaVM assembly file extension.
pub static EXTENSION_POLKAVM_ASSEMBLY: &str = "pvmasm";
/// The EraVM assembly file extension.
pub static EXTENSION_ERAVM_ASSEMBLY: &str = "zasm";
/// The PolkaVM bytecode file extension.
pub static EXTENSION_POLKAVM_BINARY: &str = "pvm";
/// The ELF shared object file extension.
pub static EXTENSION_SHARED_OBJECT: &str = "so";
/// The EraVM bytecode file extension.
pub static EXTENSION_ERAVM_BINARY: &str = "zbin";
+4
View File
@@ -1,8 +1,11 @@
//!
//! The compiler common library.
//!
pub(crate) mod base;
pub(crate) mod bit_length;
pub(crate) mod byte_length;
pub(crate) mod eravm;
pub(crate) mod evm_version;
pub(crate) mod exit_code;
pub(crate) mod extension;
@@ -11,6 +14,7 @@ pub(crate) mod utils;
pub use self::base::*;
pub use self::bit_length::*;
pub use self::byte_length::*;
pub use self::eravm::address::*;
pub use self::evm_version::EVMVersion;
pub use self::exit_code::*;
pub use self::extension::*;
+8
View File
@@ -1,7 +1,12 @@
//!
//! The compiler common utils.
//!
///
/// Deserializes a `serde_json` object from slice with the recursion limit disabled.
///
/// Must be used for all JSON I/O to avoid crashes due to the aforementioned limit.
///
pub fn deserialize_from_slice<O>(input: &[u8]) -> anyhow::Result<O>
where
O: serde::de::DeserializeOwned,
@@ -13,8 +18,11 @@ where
Ok(result)
}
///
/// Deserializes a `serde_json` object from string with the recursion limit disabled.
///
/// Must be used for all JSON I/O to avoid crashes due to the aforementioned limit.
///
pub fn deserialize_from_str<O>(input: &str) -> anyhow::Result<O>
where
O: serde::de::DeserializeOwned,
+6 -12
View File
@@ -1,16 +1,10 @@
[package]
name = "revive-differential"
version.workspace = true
license.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = { workspace = true }
tempfile = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
alloy-primitives = { workspace = true, features = ["serde"] }
alloy-genesis = { workspace = true }
alloy-serde = { workspace = true }
evm-interpreter = { workspace = true }
primitive-types = { workspace = true }
-47
View File
@@ -1,47 +0,0 @@
{
"config": {
"chainId": 420420420,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 0,
"arrowGlacierBlock": 0,
"grayGlacierBlock": 0,
"shanghaiTime": 0,
"cancunTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
},
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": "0x00",
"alloc": {
"0101010101010101010101010101010101010101": {
"balance": "1000000000"
},
"0202020202020202020202020202020202020202": {
"balance": "1000000000"
},
"0303030303030303030303030303030303030303": {
"balance": "1000000000"
}
}
}
-199
View File
@@ -1,199 +0,0 @@
use std::time::Duration;
/// Parse a go formatted duration.
///
/// Sources:
/// - https://crates.io/crates/go-parse-duration (fixed an utf8 bug)
/// - https://github.com/golang/go/blob/master/src/time/format.go
pub fn parse_go_duration(value: &str) -> Result<Duration, String> {
parse_duration(value).map(|ns| Duration::from_nanos(ns.unsigned_abs()))
}
fn parse_duration(string: &str) -> Result<i64, String> {
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
let mut s = string;
let mut d: i64 = 0; // duration to be returned
let mut neg = false;
// Consume [-+]?
if !s.is_empty() {
let c = *s.as_bytes().first().unwrap();
if c == b'-' || c == b'+' {
neg = c == b'-';
s = &s[1..];
}
}
// Special case: if all that is left is "0", this is zero.
if s == "0" {
return Ok(0);
}
if s.is_empty() {
return Err(format!("invalid duration: {string}"));
}
while !s.is_empty() {
// integers before, after decimal point
let mut v: i64;
let mut f: i64 = 0;
// value = v + f / scale
let mut scale: f64 = 1f64;
// The next character must be [0-9.]
let c = *s.as_bytes().first().unwrap();
if !(c == b'.' || c.is_ascii_digit()) {
return Err(format!("invalid duration: {string}"));
}
// Consume [0-9]*
let pl = s.len();
match leading_int(s) {
Ok((_v, _s)) => {
v = _v;
s = _s;
}
Err(_) => {
return Err(format!("invalid duration: {string}"));
}
}
let pre = pl != s.len(); // whether we consume anything before a period
// Consume (\.[0-9]*)?
let mut post = false;
if !s.is_empty() && *s.as_bytes().first().unwrap() == b'.' {
s = &s[1..];
let pl = s.len();
let (f_, scale_, s_) = leading_fraction(s);
{
f = f_;
scale = scale_;
s = s_;
}
post = pl != s.len();
}
if !pre && !post {
// no digits (e.g. ".s" or "-.s")
return Err(format!("invalid duration: {string}"));
}
// Consume unit.
let mut i = 0;
while i < s.len() {
let c = *s.as_bytes().get(i).unwrap();
if c == b'.' || c.is_ascii_digit() {
break;
}
i += 1;
}
if i == 0 {
return Err(format!("missing unit in duration: {string}"));
}
let u = &s[..i];
s = &s[i..];
let unit = match u {
"ns" => 1i64,
"us" => 1000i64,
"µs" => 1000i64, // U+00B5 = micro symbol
"μs" => 1000i64, // U+03BC = Greek letter mu
"ms" => 1000000i64,
"s" => 1000000000i64,
"m" => 60000000000i64,
"h" => 3600000000000i64,
_ => {
return Err(format!("unknown unit {u} in duration {string}"));
}
};
if v > (1 << (63 - 1)) / unit {
// overflow
return Err(format!("invalid duration {string}"));
}
v *= unit;
if f > 0 {
// f64 is needed to be nanosecond accurate for fractions of hours.
// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
v += (f as f64 * (unit as f64 / scale)) as i64;
if v < 0 {
// overflow
return Err(format!("invalid duration {string}"));
}
}
d += v;
if d < 0 {
// overflow
return Err(format!("invalid duration {string}"));
}
}
if neg {
d = -d;
}
Ok(d)
}
fn leading_int(s: &str) -> Result<(i64, &str), String> {
let mut x = 0;
let mut i = 0;
while i < s.len() {
let c = s.chars().nth(i).unwrap();
if !c.is_ascii_digit() {
break;
}
if x > (1 << (63 - 1)) / 10 {
return Err("overflow".into());
}
let d = i64::from(c.to_digit(10).unwrap());
x = x * 10 + d;
if x < 0 {
// overflow
return Err("overflow".into());
}
i += 1;
}
Ok((x, &s[i..]))
}
fn leading_fraction(s: &str) -> (i64, f64, &str) {
let mut i = 0;
let mut x = 0i64;
let mut scale = 1f64;
let mut overflow = false;
while i < s.len() {
let c = s.chars().nth(i).unwrap();
if !c.is_ascii_digit() {
break;
}
if overflow {
continue;
}
if x > (1 << (63 - 1)) / 10 {
// It's possible for overflow to give a positive number, so take care.
overflow = true;
continue;
}
let d = i64::from(c.to_digit(10).unwrap());
let y = x * 10 + d;
if y < 0 {
overflow = true;
continue;
}
x = y;
scale *= 10f64;
i += 1;
}
(x, scale, &s[i..])
}
#[cfg(test)]
mod tests {
use super::parse_duration;
#[test]
fn test_parse_duration() {
assert_eq!(parse_duration("8.731µs"), Ok(8731));
assert_eq!(parse_duration("50ns"), Ok(50));
assert_eq!(parse_duration("3ms"), Ok(3000000));
assert_eq!(parse_duration("2us"), Ok(2000));
assert_eq!(parse_duration("4.0s"), Ok(4000000000));
assert_eq!(parse_duration("1h45m"), Ok(6300000000000));
assert_eq!(
parse_duration("1"),
Err(String::from("missing unit in duration: 1")),
);
}
}
+130 -546
View File
@@ -1,570 +1,154 @@
use core::str;
use std::{
collections::BTreeMap,
io::Write,
path::PathBuf,
process::{Command, Stdio},
str::FromStr,
time::Duration,
use evm_interpreter::{
interpreter::{EtableInterpreter, RunInterpreter},
trap::CallCreateTrap,
Context, Etable, ExitError, Log, Machine, RuntimeBackend, RuntimeBaseBackend,
RuntimeEnvironment, RuntimeState, TransactionContext, Valids,
};
use primitive_types::{H160, H256, U256};
use alloy_genesis::{Genesis, GenesisAccount};
use alloy_primitives::{hex::ToHexExt, Address, Bytes, B256, U256};
use alloy_serde::storage::deserialize_storage_map;
use serde::{Deserialize, Serialize};
use serde_json::{Deserializer, Value};
use tempfile::{NamedTempFile, TempPath};
static RUNTIME_ETABLE: Etable<RuntimeState, UnimplementedHandler, CallCreateTrap> =
Etable::runtime();
pub use self::go_duration::parse_go_duration;
pub struct UnimplementedHandler;
mod go_duration;
const GENESIS_JSON: &str = include_str!("../genesis.json");
const EXECUTABLE_NAME: &str = "evm";
const EXECUTABLE_ARGS: [&str; 8] = [
"--log.format=json",
"run",
"--dump",
"--nomemory=false",
"--noreturndata=false",
"--json",
"--codefile",
"-",
];
const EXECUTABLE_ARGS_BENCH: [&str; 6] = [
"run",
"--bench",
"--nomemory=false",
"--noreturndata=false",
"--codefile",
"-",
];
const GAS_USED_MARKER: &str = "EVM gas used:";
const REVERT_MARKER: &str = " error: ";
/// The geth EVM state dump structure
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct StateDump {
pub root: Bytes,
pub accounts: BTreeMap<Address, Account>,
}
impl From<StateDump> for Genesis {
fn from(value: StateDump) -> Self {
let mut genesis: Genesis = serde_json::from_str(GENESIS_JSON).unwrap();
genesis.alloc = value
.accounts
.iter()
.map(|(address, account)| (*address, account.clone().into()))
.collect();
genesis
impl RuntimeEnvironment for UnimplementedHandler {
fn block_hash(&self, _number: U256) -> H256 {
unimplemented!()
}
fn block_number(&self) -> U256 {
unimplemented!()
}
fn block_coinbase(&self) -> H160 {
unimplemented!()
}
fn block_timestamp(&self) -> U256 {
unimplemented!()
}
fn block_difficulty(&self) -> U256 {
unimplemented!()
}
fn block_randomness(&self) -> Option<H256> {
unimplemented!()
}
fn block_gas_limit(&self) -> U256 {
unimplemented!()
}
fn block_base_fee_per_gas(&self) -> U256 {
unimplemented!()
}
fn chain_id(&self) -> U256 {
unimplemented!()
}
}
/// The geth EVM state dump account structure
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Account {
pub balance: U256,
pub nonce: u64,
pub code: Option<Bytes>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_storage_map"
)]
pub storage: Option<BTreeMap<B256, B256>>,
pub key: Option<B256>,
}
impl RuntimeBaseBackend for UnimplementedHandler {
fn balance(&self, _address: H160) -> U256 {
unimplemented!()
}
fn code_size(&self, _address: H160) -> U256 {
unimplemented!()
}
fn code_hash(&self, _address: H160) -> H256 {
unimplemented!()
}
fn code(&self, _address: H160) -> Vec<u8> {
unimplemented!()
}
fn storage(&self, _address: H160, _index: H256) -> H256 {
unimplemented!()
}
impl From<Account> for GenesisAccount {
fn from(value: Account) -> Self {
GenesisAccount {
balance: value.balance,
nonce: Some(value.nonce),
code: value.code,
storage: value.storage,
private_key: value.key,
}
fn exists(&self, _address: H160) -> bool {
unimplemented!()
}
fn nonce(&self, _address: H160) -> U256 {
unimplemented!()
}
}
/// Contains the output from geth `emv` invocations
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct EvmOutput {
pub output: Bytes,
#[serde(rename = "gasUsed")]
pub gas_used: U256,
pub error: Option<String>,
}
impl RuntimeBackend for UnimplementedHandler {
fn original_storage(&self, _address: H160, _index: H256) -> H256 {
unimplemented!()
}
impl EvmOutput {
/// Return if there was no error found.
///
/// Panics if the gas used is zero as this indicates nothing was run.
pub fn run_success(&self) -> bool {
assert_ne!(self.gas_used, U256::ZERO, "nothing was executed: {self:?}");
self.error.is_none()
fn deleted(&self, _address: H160) -> bool {
unimplemented!()
}
fn is_cold(&self, _address: H160, _index: Option<H256>) -> bool {
unimplemented!()
}
fn mark_hot(&mut self, _address: H160, _index: Option<H256>) {
unimplemented!()
}
fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> {
unimplemented!()
}
fn log(&mut self, _log: Log) -> Result<(), ExitError> {
unimplemented!()
}
fn mark_delete(&mut self, _address: H160) {
unimplemented!()
}
fn reset_storage(&mut self, _address: H160) {
unimplemented!()
}
fn set_code(&mut self, _address: H160, _code: Vec<u8>) -> Result<(), ExitError> {
unimplemented!()
}
fn reset_balance(&mut self, _address: H160) {
unimplemented!()
}
fn deposit(&mut self, _address: H160, _value: U256) {
unimplemented!()
}
fn withdrawal(&mut self, _address: H160, _value: U256) -> Result<(), ExitError> {
unimplemented!()
}
fn inc_nonce(&mut self, _address: H160) -> Result<(), ExitError> {
unimplemented!()
}
}
/// Contains the full log from geth `emv` invocations
#[derive(Clone, Debug)]
pub struct EvmLog {
pub account_deployed: Option<Address>,
pub output: EvmOutput,
pub state_dump: StateDump,
pub stderr: String,
#[derive(Clone)]
pub struct PreparedEvm {
pub valids: Valids,
pub vm: Machine<RuntimeState>,
}
impl EvmLog {
pub const EXECUTION_TIME_MARKER: &'static str = "execution time:";
/// Parse the reported execution time from stderr (requires --bench)
pub fn execution_time(&self) -> Result<Duration, String> {
for line in self.stderr.lines() {
if let Some(value) = line.split("execution time:").nth(1) {
return parse_go_duration(value.trim());
}
pub fn prepare(code: Vec<u8>, data: Vec<u8>) -> PreparedEvm {
let state = RuntimeState {
context: Context {
address: H160::default(),
caller: H160::default(),
apparent_value: U256::default(),
},
transaction_context: TransactionContext {
gas_price: U256::default(),
origin: H160::default(),
}
.into(),
retbuf: Vec::new(),
};
Err(format!(
"execution time marker '{}' not found in raw EVM log",
Self::EXECUTION_TIME_MARKER
))
}
fn parse_gas_used_from_bench(&mut self) {
for line in self.stderr.lines() {
if let Some(gas_line) = line.split(GAS_USED_MARKER).nth(1) {
let gas_used = gas_line.trim().parse::<u64>().unwrap_or_else(|error| {
panic!("invalid output '{gas_line}' for gas used: {error}")
});
self.output.gas_used = U256::from(gas_used);
}
}
PreparedEvm {
valids: Valids::new(&code[..]),
vm: evm_interpreter::Machine::new(code.into(), data.to_vec().into(), 1024, 0xFFFF, state),
}
}
impl From<&str> for EvmLog {
fn from(value: &str) -> Self {
let mut output = None;
let mut state_dump = None;
for value in Deserializer::from_str(value).into_iter::<Value>() {
let Ok(value) = value else { continue };
if let Ok(value @ EvmOutput { .. }) = serde_json::from_value(value.clone()) {
output = Some(value);
continue;
}
if let Ok(value @ StateDump { .. }) = serde_json::from_value(value) {
state_dump = Some(value);
}
}
pub fn execute(pre: PreparedEvm) -> Vec<u8> {
let mut vm = EtableInterpreter::new_valid(pre.vm, &RUNTIME_ETABLE, pre.valids);
vm.run(&mut UnimplementedHandler {})
.exit()
.unwrap()
.unwrap();
if let (Some(output), Some(state_dump)) = (output, state_dump) {
return Self {
account_deployed: None,
output,
state_dump,
stderr: value.into(),
};
}
EvmLog {
account_deployed: None,
output: EvmOutput {
error: value.find(REVERT_MARKER).map(|_| REVERT_MARKER.to_string()),
..Default::default()
},
state_dump: Default::default(),
stderr: Default::default(),
}
}
}
/// Builder for running contracts in geth `evm`
pub struct Evm {
genesis_json: Option<String>,
genesis_path: Option<PathBuf>,
code: Option<Vec<u8>>,
input: Option<Bytes>,
receiver: Option<String>,
sender: String,
value: Option<u128>,
gas: Option<u64>,
create: bool,
bench: bool,
}
impl Default for Evm {
fn default() -> Self {
Self {
genesis_json: Some(GENESIS_JSON.to_string()),
genesis_path: None,
code: None,
input: None,
receiver: None,
sender: Address::default().encode_hex(),
value: None,
gas: None,
create: false,
bench: false,
}
}
}
impl Evm {
/// Create a new EVM with the given `genesis`
pub fn from_genesis(genesis: Genesis) -> Self {
Self::default().genesis_json(genesis)
}
/// Run the `code`
pub fn code_blob(self, blob: Vec<u8>) -> Self {
Self {
code: Some(blob),
..self
}
}
/// Set the calldata
pub fn input(self, bytes: Bytes) -> Self {
Self {
input: (!bytes.is_empty()).then_some(bytes),
..self
}
}
/// Set the create flag
pub fn deploy(self, enable: bool) -> Self {
Self {
create: enable,
..self
}
}
/// Set the transferred value
pub fn value(self, value: u128) -> Self {
Self {
value: Some(value),
..self
}
}
/// Set the gas limit
pub fn gas(self, limit: u64) -> Self {
Self {
gas: Some(limit),
..self
}
}
/// Provide the prestate genesis configuration
pub fn genesis_json(self, genesis: Genesis) -> Self {
let genesis_json = serde_json::to_string(&genesis).expect("state dump should be valid");
// TODO: Investigate
let genesis_json = genesis_json.replace("\"0x0\"", "0").into();
Self {
genesis_json,
genesis_path: None,
..self
}
}
/// Provide a path to the genesis file to be used
pub fn genesis_path(self, path: PathBuf) -> Self {
Self {
genesis_path: Some(path),
genesis_json: None,
..self
}
}
/// Set the callee address
pub fn receiver(self, address: Address) -> Self {
Self {
receiver: Some(address.encode_hex()),
..self
}
}
/// Set the caller address
pub fn sender(self, address: Address) -> Self {
Self {
sender: address.encode_hex(),
..self
}
}
/// Run as a benchmark
pub fn bench(self, flag: bool) -> Self {
Self {
bench: flag,
..self
}
}
/// Calculate the address of the contract account this deploy call would create
pub fn expect_account_created(&self) -> Address {
assert!(self.create, "expected a deploy call");
let sender = Address::from_str(&self.sender).expect("sender address should be valid");
let genesis: Genesis = match (self.genesis_json.as_ref(), self.genesis_path.as_ref()) {
(Some(json), None) => serde_json::from_str(json).unwrap(),
(None, Some(path)) => {
serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap()
}
_ => panic!("provided a genesis json and a genesis json path"),
};
let nonce = genesis
.alloc
.get(&sender)
.map(|account| account.nonce.unwrap_or(0))
.unwrap_or(0);
sender.create(nonce)
}
/// Return the path to the genesis file;
/// writes the genesis file into a tmpdir if necessary.
///
/// `TempPath`` will delete on drop, so need to keep it around
fn write_genesis_file(&self, temp_path: &mut Option<TempPath>) -> String {
match (self.genesis_json.as_ref(), self.genesis_path.as_ref()) {
(Some(json), None) => {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(json.as_bytes()).unwrap();
let path = temp_file.into_temp_path();
*temp_path = Some(path);
temp_path.as_ref().unwrap().display().to_string()
}
(None, Some(path)) => path.display().to_string(),
_ => panic!("provided a genesis json and a genesis json path"),
}
}
/// Run the call in a geth `evm` subprocess.
///
/// Definitively not a hairy plumbing function.
pub fn run(self) -> EvmLog {
let mut temp_path = None;
let genesis_json_path = &self.write_genesis_file(&mut temp_path);
// Static args
let mut command = Command::new(PathBuf::from(EXECUTABLE_NAME));
command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
if self.bench {
command.args(EXECUTABLE_ARGS_BENCH);
} else {
command.args(EXECUTABLE_ARGS);
};
// Dynamic args
command.args(["--prestate", genesis_json_path]);
command.args(["--sender", &self.sender]);
if let Some(input) = &self.input {
command.args(["--input", hex::encode(input).as_str()]);
}
let account_deployed = if self.create {
command.arg("--create");
self.expect_account_created().into()
} else {
None
};
match (&self.code, &self.receiver) {
(Some(_), None) => {}
(None, Some(address)) => {
command.args(["--receiver", address]);
}
(Some(_), Some(_)) => panic!("code and receiver specified"),
_ => panic!("no code file or receiver specified"),
}
if let Some(gas) = self.gas {
command.args(["--gas", &format!("{gas}")]);
}
if let Some(value) = self.value {
command.args(["--value", &format!("{value}")]);
}
// Run the evm subprocess and assert success return value
let process = command.spawn().unwrap_or_else(|error| {
panic!("{EXECUTABLE_NAME} subprocess spawning error: {error:?}")
});
let buf = vec![];
process
.stdin
.as_ref()
.unwrap_or_else(|| panic!("{EXECUTABLE_NAME} stdin getting error"))
.write_all(self.code.as_ref().unwrap_or(&buf))
.unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stdin writing error: {err:?}"));
let output = process
.wait_with_output()
.unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} subprocess output error: {err}"));
assert!(
output.status.success(),
"{EXECUTABLE_NAME} command failed: {output:?}",
);
drop(temp_path);
let stdout = str::from_utf8(output.stdout.as_slice())
.unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stdout failed to parse: {err}"));
let stderr = str::from_utf8(output.stderr.as_slice())
.unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stderr failed to parse: {err}"));
let mut log: EvmLog = format!("{stdout}{stderr}").as_str().into();
log.stderr = stderr.into();
if self.bench {
log.parse_gas_used_from_bench();
}
// Set the deployed account
log.account_deployed = account_deployed;
log
}
}
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use alloy_genesis::Genesis;
use alloy_primitives::{Bytes, B256, U256};
use crate::{Evm, EvmLog, EvmOutput, StateDump};
const OUTPUT_JSON_OK: &str = r#"{"output":"0000000000000000000000000000000000000000000000000000000000000000","gasUsed":"0x11d"}"#;
const OUTPUT_JSON_REVERTED: &str =
r#"{"output":"","gasUsed":"0x2d","error":"execution reverted"}"#;
const STATE_DUMP: &str = r#"
{
"root": "eb5d51177cb9049b848ea92f87f9a3f00abfb683d0866c2eddecc5692ad27f86",
"accounts": {
"0x1f2a98889594024BFfdA3311CbE69728d392C06D": {
"balance": "0",
"nonce": 1,
"root": "0x63cfcda8d81a8b1840b1b9722c37f929a4037e53ad1ce6abdef31c0c8bac1f61",
"codeHash": "0xa6e0062c5ba829446695f179b97702a75f7d354e33445d2e928ed00e1a39e88f",
"code": "0x608060405260043610610028575f3560e01c80633fa4f2451461002c578063b144adfb1461004a575b5f80fd5b610034610086565b60405161004191906100c5565b60405180910390f35b348015610055575f80fd5b50610070600480360381019061006b919061013c565b61008d565b60405161007d91906100c5565b60405180910390f35b5f34905090565b5f8173ffffffffffffffffffffffffffffffffffffffff16319050919050565b5f819050919050565b6100bf816100ad565b82525050565b5f6020820190506100d85f8301846100b6565b92915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61010b826100e2565b9050919050565b61011b81610101565b8114610125575f80fd5b50565b5f8135905061013681610112565b92915050565b5f60208284031215610151576101506100de565b5b5f61015e84828501610128565b9150509291505056fea2646970667358221220a2109c2f05a629fff4640e9f0cf12a698bbea9b0858a4029901e88bf5d1c926964736f6c63430008190033",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "02"
},
"address": "0x1f2a98889594024bffda3311cbe69728d392c06d",
"key": "0xcbeeb4463624bc2f332dcfe2b479eddb1c380ec862ee63d9f31b31b854fb7c61"
}
}
}"#;
const EVM_BIN_FIXTURE: &str = "6080604052348015600e575f80fd5b506040516101403803806101408339818101604052810190602e9190607f565b805f806101000a81548160ff0219169083151502179055505060a5565b5f80fd5b5f8115159050919050565b606181604f565b8114606a575f80fd5b50565b5f81519050607981605a565b92915050565b5f602082840312156091576090604b565b5b5f609c84828501606d565b91505092915050565b608f806100b15f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063cde4efa914602a575b5f80fd5b60306032565b005b5f8054906101000a900460ff16155f806101000a81548160ff02191690831515021790555056fea264697066735822122046c92dd2fd612b1ed93d184dad4c49f61c44690722c4a6c7c746ebeb0aadeb4a64736f6c63430008190033";
const EVM_BIN_RUNTIME_FIXTURE: &str = "6080604052348015600e575f80fd5b50600436106026575f3560e01c8063cde4efa914602a575b5f80fd5b60306032565b005b5f8054906101000a900460ff16155f806101000a81548160ff02191690831515021790555056fea264697066735822122046c92dd2fd612b1ed93d184dad4c49f61c44690722c4a6c7c746ebeb0aadeb4a64736f6c63430008190033";
const EVM_BIN_FIXTURE_INPUT: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
const EVM_BIN_RUNTIME_FIXTURE_INPUT: &str = "cde4efa9";
const STDERR_BENCH_OK: &str = r#"EVM gas used: 560071
execution time: 1.460881ms
allocations: 29
allocated bytes: 2558
"#;
const STDERR_BENCH_REVERT: &str = r#"EVM gas used: 69
execution time: 10.11µs
allocations: 43
allocated bytes: 3711"#;
const STDOUT_BENCH_REVERT: &str = r#" error: execution reverted"#;
#[test]
fn parse_evm_output_ok() {
serde_json::from_str::<EvmOutput>(OUTPUT_JSON_OK).unwrap();
}
#[test]
fn parse_evm_output_revert() {
serde_json::from_str::<EvmOutput>(OUTPUT_JSON_REVERTED).unwrap();
}
#[test]
fn parse_evm_output_bench_ok() {
let mut log = EvmLog::from("");
log.stderr = STDERR_BENCH_OK.into();
log.parse_gas_used_from_bench();
assert!(log.output.run_success());
assert_eq!(log.execution_time().unwrap(), Duration::from_nanos(1460881));
}
#[test]
fn parse_evm_output_bench_revert() {
let mut log = EvmLog::from(STDOUT_BENCH_REVERT);
log.stderr = STDERR_BENCH_REVERT.into();
log.parse_gas_used_from_bench();
assert!(!log.output.run_success());
}
#[test]
fn parse_state_dump() {
serde_json::from_str::<StateDump>(STATE_DUMP).unwrap();
}
#[test]
fn evm_log_from_str() {
let log = format!("{OUTPUT_JSON_OK}\n{STATE_DUMP}");
let _ = EvmLog::from(log.as_str());
}
#[test]
fn generate_genesis() {
let log = format!("{OUTPUT_JSON_OK}\n{STATE_DUMP}");
let log = EvmLog::from(log.as_str());
let mut genesis: Genesis = log.state_dump.into();
let storage = genesis
.alloc
.pop_first()
.expect("should have one account in genesis")
.1
.storage
.expect("genesis account should have storage");
let storage_value = storage
.get(&B256::ZERO)
.expect("genesis account should have key 0 occupied");
assert_eq!(*storage_value, B256::from(U256::from(2)));
}
#[test]
fn flipper() {
let log_runtime = Evm::default()
.code_blob(EVM_BIN_RUNTIME_FIXTURE.as_bytes().to_vec())
.input(Bytes::from_str(EVM_BIN_RUNTIME_FIXTURE_INPUT).unwrap())
.run();
assert!(log_runtime.output.run_success());
}
#[test]
fn prestate() {
let log_deploy = Evm::default()
.code_blob(EVM_BIN_FIXTURE.as_bytes().to_vec())
.input(Bytes::from_str(EVM_BIN_FIXTURE_INPUT).unwrap())
.deploy(true)
.run();
assert!(log_deploy.output.run_success());
let address = log_deploy.account_deployed.unwrap();
let genesis: Genesis = log_deploy.state_dump.into();
let log_runtime = Evm::default()
.genesis_json(genesis)
.receiver(address)
.input(Bytes::from_str(EVM_BIN_RUNTIME_FIXTURE_INPUT).unwrap())
.run();
assert!(log_runtime.output.run_success(), "{:?}", log_runtime.output);
}
#[test]
#[ignore] // https://github.com/ethereum/go-ethereum/issues/30778
fn bench_flipper() {
let log_runtime = Evm::default()
.code_blob(EVM_BIN_RUNTIME_FIXTURE.as_bytes().to_vec())
.input(Bytes::from_str(EVM_BIN_RUNTIME_FIXTURE_INPUT).unwrap())
.bench(true)
.run();
assert!(log_runtime.output.run_success());
assert!(log_runtime.execution_time().unwrap() > Duration::from_nanos(0));
}
vm.retval.clone()
}
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "revive-extensions"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-0"] }
+15
View File
@@ -0,0 +1,15 @@
; target datalayout = "e-m:e-p:32:32-i64:64-n32-S128"
; target triple = "riscv32-unknown-unknown-elf"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-unknown-elf"
define dso_local noundef i256 @__bswap(i256 noundef %0) local_unnamed_addr #0 {
%2 = tail call i256 @llvm.bswap.i256(i256 %0)
ret i256 %2
}
; Function Attrs: mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare i256 @llvm.bswap.i256(i256) #1
attributes #0 = { alwaysinline mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
attributes #1 = { mustprogress nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+40
View File
@@ -0,0 +1,40 @@
use std::{env, fs, path::Path, process::Command};
fn compile(source_path: &str, output_path: &str) {
let output = Command::new("llc")
.args([
"-O3",
"-filetype=asm",
"-mattr=+zbb,+e",
source_path,
"-o",
output_path,
])
.output()
.expect("should be able to invoke llc");
assert!(
output.status.success(),
"failed to compile {}: {:?}",
source_path,
output
);
}
fn main() {
let in_file = "bswap.ll";
let out_file = "bswap.s";
let out_dir = env::var_os("OUT_DIR").expect("env should have $OUT_DIR");
let out_path = Path::new(&out_dir).join(out_file);
compile(
in_file,
out_path.to_str().expect("$OUT_DIR should be UTF-8"),
);
let src_path = Path::new(&out_dir).join("bswap.rs");
let src = format!("pub static ASSEMBLY: &str = include_str!(\"{out_file}\");");
fs::write(src_path, src).expect("should be able to write in $OUT_DIR");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=bswap.ll");
}
+40
View File
@@ -0,0 +1,40 @@
//! Custom RISC-V extension in PolkaVM that are partially supported.
//! We use inline assembly to emit partially supported instructions.
use inkwell::{context::Context, module::Module, support::LLVMString};
include!(concat!(env!("OUT_DIR"), "/bswap.rs"));
/// Returns a LLVM module containing a `__bswap` function, which
/// - Takes a `i256` value argument
/// - Byte swaps it using `rev8` from the `zbb` extension
/// - Returns the `i256` value
///
/// Returns `Error` if the module fails to validate, which should never happen.
pub fn module<'context>(
context: &'context Context,
module_name: &str,
) -> Result<Module<'context>, LLVMString> {
let module = context.create_module(module_name);
module.set_inline_assembly(ASSEMBLY);
module.verify()?;
Ok(module)
}
#[cfg(test)]
mod tests {
#[test]
fn assembly_contains_rev8_instruction() {
assert!(crate::ASSEMBLY.contains("rev8"));
}
#[test]
fn module_is_valid() {
inkwell::targets::Target::initialize_riscv(&Default::default());
let context = inkwell::context::Context::create();
assert!(crate::module(&context, "polkavm_bswap").is_ok());
}
}
+9 -10
View File
@@ -1,23 +1,22 @@
[package]
name = "revive-integration"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
description = "revive compiler integration test cases"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
polkavm = { workspace = true }
alloy-primitives = { workspace = true }
alloy-sol-types = { workspace = true }
hex = { workspace = true }
serde_json = { workspace = true }
env_logger = { workspace = true }
revive-solidity = { workspace = true }
revive-runner = { workspace = true }
revive-solidity = { path = "../solidity" }
revive-differential = { path = "../differential" }
era-compiler-llvm-context = { path = "../llvm-context" }
[dev-dependencies]
sha1 = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
rayon = { workspace = true }
+5 -8
View File
@@ -1,10 +1,7 @@
{
"Baseline": 1110,
"Computation": 2389,
"DivisionArithmetics": 14822,
"ERC20": 23973,
"Events": 1605,
"FibonacciIterative": 2023,
"Flipper": 1989,
"SHA1": 17026
"Flipper": 3958,
"Baseline": 3551,
"Computation": 5912,
"Fibonacci": 4909,
"ERC20": 1966
}
-81
View File
@@ -1,81 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "BalanceReceiver"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Balance"
}
},
"value": 24
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "6ada15d90000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
}
]
}
*/
contract BalanceReceiver {
constructor() payable {}
fallback() external payable {}
}
contract Balance {
constructor() payable {
// 0 to EOA
transfer_to(payable(address(0xdeadbeef)), 0);
send_to(payable(address(0xdeadbeef)), 0);
// 1 to EOA
transfer_to(payable(address(0xcafebabe)), 1);
send_to(payable(address(0xcafebabe)), 1);
BalanceReceiver balanceReceiver = new BalanceReceiver();
// 0 to contract
transfer_to(payable(address(balanceReceiver)), 0);
send_to(payable(address(balanceReceiver)), 0);
// 1 to contract
transfer_to(payable(address(balanceReceiver)), 1);
send_to(payable(address(balanceReceiver)), 1);
}
function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount);
}
function send_to(address payable _dest, uint _amount) public payable {
require(_dest.send(_amount));
}
}
-31
View File
@@ -1,31 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "BaseFee"
}
}
}
},
{
"VerifyCall": {
"success": true
}
}
]
}
*/
contract BaseFee {
constructor() payable {
assert(block.basefee == 0);
}
}
+1 -16
View File
@@ -1,21 +1,6 @@
// SPDX-License-Identifier: MIT
/* runner.json
{
"actions": [
{
"Instantiate": {
"origin": "Alice",
"value": 0
}
}
]
}
*/
pragma solidity ^0.8;
pragma solidity ^0.8.24;
contract Baseline {
function baseline() public payable {}
-36
View File
@@ -1,36 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Bitwise"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "3fa4f245"
}
}
]
}
*/
contract Bitwise {
function opByte(uint i, uint x) public payable returns (uint ret) {
assembly {
ret := byte(i, x)
}
}
}
-50
View File
@@ -1,50 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Block"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "8381f58a"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b80777ea"
}
}
]
}
*/
contract Block {
function timestamp() public view returns (uint ret) {
ret = block.timestamp;
}
function number() public view returns (uint ret) {
if (block.number == 0) {
ret = 1;
} else {
ret = block.number;
}
}
}
@@ -1,33 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "BlockHash"
}
},
"data": "4545454545454545454545454545454545454545454545454545454545454545"
}
}
]
}
*/
contract BlockHash {
constructor(bytes32 expected) payable {
assert(blockhash(0) == expected);
assert(blockhash(1) == 0);
assert(
blockhash(
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
) == 0
);
}
}
-58
View File
@@ -1,58 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "Callee"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Caller"
}
},
"value": 123
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "5a6535fc00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004cafebabe00000000000000000000000000000000000000000000000000000000"
}
}
]
}
*/
contract Callee {
function echo(bytes memory payload) public pure returns (bytes memory) {
return payload;
}
receive() external payable {}
}
contract Caller {
constructor() payable {
Callee callee = new Callee();
payable(address(callee)).transfer(msg.value);
}
function call(bytes memory payload) public returns (bytes memory) {
Callee callee = new Callee();
return callee.echo(payload);
}
}
-27
View File
@@ -1,27 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Coinbase"
}
}
}
}
]
}
*/
contract Coinbase {
constructor() payable {
address coinbase = address(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF);
assert(block.coinbase == coinbase);
}
}
+1 -39
View File
@@ -1,44 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"actions": [
{
"Instantiate": {}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "0f760610000000000000000000000000000000000000000000000000000000000000000d"
}
},
{
"VerifyCall": {
"success": true,
"output": "000000000000000000000000000000000000000000000000000000000000005b"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "00261b660000000000000000000000000000000000000000000000000000000000000005"
}
},
{
"VerifyCall": {
"success": true,
"output": "00000000000000000000000000000000000000000000000000000000000003b1"
}
}
]
}
*/
pragma solidity ^0.8.24;
contract Computation {
function triangle_number(int64 n) public pure returns (int64 sum) {
-62
View File
@@ -1,62 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Context"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "846a1ee1"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fc9c8d39"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "3af973b1"
}
}
]
}
*/
contract Context {
function address_this() public view returns (address ret) {
ret = address(this);
}
function caller() public view returns (address ret) {
ret = msg.sender;
}
function chain_id() public view returns (uint) {
uint256 id;
assembly {
id := chainid()
}
return id;
}
}
-72
View File
@@ -1,72 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "CreateA"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "CreateB"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"value": 10000
}
},
{
"Call": {
"dest": {
"Instantiated": 0
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
}
}
}
]
}
*/
contract CreateA {
constructor() payable {}
}
contract CreateB {
receive() external payable {
new CreateA{value: msg.value}();
}
fallback() external {
new CreateA{salt: hex"01"}();
}
}
-50
View File
@@ -1,50 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "CreateA"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "CreateB"
}
},
"value": 100000
}
}
]
}
*/
contract CreateA {
constructor() payable {}
}
contract CreateB {
constructor() payable {
bytes32 salt = hex"ff";
try new CreateA{salt: salt}() returns (CreateA) {} catch {
revert("the first instantiation should succeed");
}
try new CreateA{salt: salt}() returns (CreateA) {} catch {
return;
}
revert("the second instantiation should have failed");
}
}
-35
View File
@@ -1,35 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "TestSha3"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "f9fbd5540000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c68656c6c6f20776f726c64210000000000000000000000000000000000000000"
}
}
]
}
*/
contract TestSha3 {
function test(string memory _pre) external payable returns (bytes32) {
bytes32 hash = keccak256(bytes(_pre));
return bytes32(uint(hash) + 1);
}
}
-84
View File
@@ -1,84 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "Logic"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Tester"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"value": 123,
"data": "6466414b0000000000000000000000000000000000000000000000000000000000000020"
}
}
]
}
*/
contract Logic {
// NOTE: storage layout must be the same as contract Tester
uint256 public num;
address public sender;
uint256 public value;
uint public immutable multiplier = 4;
event DidSetVars();
function setVars(uint256 _num) public payable returns (uint256) {
num = _num * multiplier;
sender = msg.sender;
value = msg.value;
emit DidSetVars();
return _num;
}
}
contract Tester {
uint256 public num;
address public sender;
uint256 public value;
uint public immutable multiplier = 2;
function setVars(uint256 _num) public payable returns (bool, bytes memory) {
Logic impl = new Logic();
// Tester's storage is set, Logic is not modified.
(bool success, bytes memory data) = address(impl).delegatecall(
abi.encodeWithSignature("setVars(uint256)", _num)
);
assert(success);
assert(impl.num() == 0);
assert(impl.sender() == address(0));
assert(impl.value() == 0);
assert(num == _num * 4);
assert(sender == msg.sender);
assert(value == msg.value);
return (success, data);
}
}
@@ -1,29 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract DivisionArithmetics {
function div(uint n, uint d) public pure returns (uint q) {
assembly {
q := div(n, d)
}
}
function sdiv(int n, int d) public pure returns (int q) {
assembly {
q := sdiv(n, d)
}
}
function mod(uint n, uint d) public pure returns (uint r) {
assembly {
r := mod(n, d)
}
}
function smod(int n, int d) public pure returns (int r) {
assembly {
r := smod(n, d)
}
}
}
+1 -50
View File
@@ -1,32 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "ERC20"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "ERC20Tester"
}
}
}
}
]
}
*/
pragma solidity ^0.8.20;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
interface IERC20 {
@@ -98,26 +72,3 @@ contract ERC20 is IERC20 {
emit Transfer(msg.sender, address(0), amount);
}
}
contract ERC20Tester {
constructor() {
address BOB = address(0xffffffffffffffffffffffffffffffffffffff);
ERC20 token = new ERC20();
assert(token.decimals() == 18);
token.mint(300);
assert(token.balanceOf(address(this)) == 300);
token.transfer(BOB, 100);
assert(token.balanceOf(address(this)) == 200);
assert(token.balanceOf(BOB) == 100);
token.approve(address(this), 100);
token.transferFrom(address(this), BOB, 100);
assert(token.balanceOf(BOB) == 200);
assert(token.balanceOf(address(this)) == 100);
token.burn(100);
assert(token.balanceOf(address(this)) == 0);
}
}
-49
View File
@@ -1,49 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Events"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "4d43bec90000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "4d43bec9000000000000000000000000000000000000000000000000000000000000007b"
}
}
]
}
*/
contract Events {
event A() anonymous;
event E(uint, uint indexed, uint indexed, uint indexed);
function emitEvent(uint topics) public {
if (topics == 0) {
emit A();
} else {
emit E(topics, 1, 2, 3);
}
}
}
-29
View File
@@ -1,29 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract ExtCode {
function ExtCodeSize(address who) public view returns (uint ret) {
assembly {
ret := extcodesize(who)
}
}
function CodeSize() public pure returns (uint ret) {
assembly {
ret := codesize()
}
}
function ExtCodeHash(address who) public view returns (bytes32 ret) {
assembly {
ret := extcodehash(who)
}
}
function CodeHash() public view returns (bytes32 ret) {
assembly {
ret := extcodehash(address())
}
}
}
+2 -25
View File
@@ -1,30 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8;
pragma solidity ^0.8.24;
/* runner.json
{
"actions": [
{
"Instantiate": {}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "6b83dd2e0000000000000000000000000000000000000000000000000000000000000006"
}
},
{
"VerifyCall": {
"success": true,
"output": "0000000000000000000000000000000000000000000000000000000000000008"
}
}
]
}
*/
// https://medium.com/coinmonks/fibonacci-in-solidity-8477d907e22a
contract FibonacciRecursive {
function f(uint n) internal pure returns (uint) {
@@ -58,7 +36,6 @@ contract FibonacciIterative {
}
}
// https://medium.com/coinmonks/fibonacci-in-solidity-8477d907e22a
contract FibonacciBinet {
function fib3(uint n) external pure returns (uint a) {
if (n == 0) {
@@ -1,42 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "FunctionPointer"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "26121ff0"
}
}
]
}
*/
contract FunctionPointer {
bool public flag = false;
function f0() public {
flag = true;
}
function f() public returns (bool) {
function() internal x = f0;
x();
return flag;
}
}
-32
View File
@@ -1,32 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "GasLeft"
}
}
}
},
{
"VerifyCall": {
"success": true
}
}
]
}
*/
contract GasLeft {
constructor() payable {
assert(gasleft() > gasleft());
assert(gasleft() > 0 && gasleft() < 0xffffffffffffffff);
}
}
-31
View File
@@ -1,31 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "GasLimit"
}
}
}
},
{
"VerifyCall": {
"success": true
}
}
]
}
*/
contract GasLimit {
constructor() payable {
assert(block.gaslimit == 2000000000000);
}
}
-31
View File
@@ -1,31 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "GasPrice"
}
}
}
},
{
"VerifyCall": {
"success": true
}
}
]
}
*/
contract GasPrice {
constructor() payable {
assert(tx.gasprice == 1000);
}
}
@@ -1,63 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "ImmutablesTester"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Immutables"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
}
}
}
]
}
*/
contract ImmutablesTester {
// Read should work in the runtime code
uint public immutable foo;
// Read should work in the runtime code
uint public immutable bar;
// Read should work in the runtime code
uint public immutable zoo;
// Assign and read should work in the constructor
constructor(uint _foo) payable {
foo = _foo;
bar = foo + 1;
zoo = bar + 2;
assert(zoo == _foo + 3);
}
}
contract Immutables {
fallback() external {
ImmutablesTester tester = new ImmutablesTester(127);
assert(tester.foo() == 127);
assert(tester.bar() == tester.foo() + 1);
assert(tester.zoo() == tester.bar() + 2);
}
}
-34
View File
@@ -1,34 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "MCopy"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "0ee188b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030102030000000000000000000000000000000000000000000000000000000000"
}
}
]
}
*/
contract MCopy {
function memcpy(bytes memory payload) public pure returns (bytes memory) {
return payload;
}
}
+1 -35
View File
@@ -1,40 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "MSize",
"solc_optimizer": false
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "f016832c"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "f4a63aa5"
}
}
]
}
*/
pragma solidity ^0.8.24;
contract MSize {
uint[] public data;
-101
View File
@@ -1,101 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "MStore8"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad4210000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad4210000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad4210000000000000000000000000000000000000000000000000000000000000002"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad42100000000000000000000000000000000000000000000000000000000000000ff"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad4210000000000000000000000000000000000000000000000000000000000000100"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad4210000000000000000000000000000000000000000000000000000000000000101"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad4210000000000000000000000000000000000000000000000000000000000000102"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad42100000000000000000000000000000000000000000000000000000000075bcd15"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "b09ad421ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
}
]
}
*/
contract MStore8 {
function mStore8(uint value) public pure returns (uint256 word) {
assembly {
mstore8(0x80, value)
word := mload(0x80)
}
}
}
@@ -1,53 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "Callee"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "ReturnDataOob"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
}
}
}
]
}
*/
contract Callee {
function echo(bytes memory payload) public pure returns (bytes memory) {
return payload;
}
}
contract ReturnDataOob {
fallback() external {
new Callee().echo(hex"1234");
assembly {
let pos := mload(64)
let size := add(returndatasize(), 1)
returndatacopy(pos, 0, size)
}
}
}
-25
View File
@@ -1,31 +1,6 @@
// SPDX-License-Identifier: BSD-2-Clause
pragma solidity ^0.8.4;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "SHA1"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1605782b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
}
]
}
*/
contract SHA1 {
function sha1(bytes memory data) public pure returns (bytes20 ret) {
assembly {
-75
View File
@@ -1,75 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Send"
}
},
"value": 211
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"VerifyCall": {
"success": true
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
}
}
]
}
*/
contract Send {
constructor() payable {}
function transfer_self(uint _amount) public payable {
transfer_to(payable(address(this)), _amount);
}
function transfer_to(address payable _dest, uint _amount) public payable {
if (_dest.send(_amount)) {}
}
fallback() external {}
receive() external payable {}
}
-55
View File
@@ -1,55 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Storage"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fabc9efaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "558b9f9bffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
}
]
}
*/
contract Storage {
function transient(uint value) public returns (uint ret) {
assembly {
let slot := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
tstore(slot, value)
let success := call(0, 0, 0, 0, 0, 0, 0)
ret := tload(slot)
}
}
function persistent(uint value) public returns (uint ret) {
assembly {
let slot := 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
sstore(slot, value)
ret := sload(slot)
}
}
}
@@ -1,54 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "TransactionOrigin"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "TransactionTester"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "f8a8fd6d"
}
}
]
}
*/
contract TransactionTester {
constructor() payable {
assert(tx.origin == new TransactionOrigin().test());
}
function test() public payable returns (address ret) {
ret = tx.origin;
}
}
contract TransactionOrigin {
function test() public payable returns (address ret) {
assert(msg.sender != tx.origin);
ret = tx.origin;
}
}
-75
View File
@@ -1,75 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Transfer"
}
},
"value": 211
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"VerifyCall": {
"success": true
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
}
}
]
}
*/
contract Transfer {
constructor() payable {}
function transfer_self(uint _amount) public payable {
transfer_to(payable(address(this)), _amount);
}
function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount);
}
fallback() external {}
receive() external payable {}
}
+1 -58
View File
@@ -1,65 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "ValueTester"
}
}
}
},
{
"Instantiate": {
"value": 1024,
"code": {
"Solidity": {
"contract": "Value"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"value": 123,
"data": "3fa4f245"
}
}
]
}
*/
contract ValueTester {
constructor() payable {}
function balance_self() public view returns (uint ret) {
ret = address(this).balance;
}
}
pragma solidity ^0.8.24;
contract Value {
constructor() payable {
ValueTester tester = new ValueTester{value: msg.value}();
// own account
assert(address(this).balance == 0);
// tester account
assert(address(tester).balance == msg.value);
assert(tester.balance_self() == msg.value);
// non-existant account
assert(address(0xdeadbeef).balance == 0);
}
function value() public payable returns (uint ret) {
ret = msg.value;
}
+1 -43
View File
@@ -1,51 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"actions": [
{
"Instantiate": {
"data": "0000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyStorage": {
"contract": {
"Instantiated": 0
},
"key": "0000000000000000000000000000000000000000000000000000000000000000",
"expected": "0100000000000000000000000000000000000000000000000000000000000000"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "cde4efa9"
}
},
{
"VerifyStorage": {
"contract": {
"Instantiated": 0
},
"key": "0000000000000000000000000000000000000000000000000000000000000000",
"expected": "0000000000000000000000000000000000000000000000000000000000000000"
}
}
]
}
*/
pragma solidity ^0.8.24;
contract Flipper {
bool coin;
constructor(bool _coin) {
coin = _coin;
}
function flip() public {
coin = !coin;
}
+12
View File
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract MStore8 {
function mStore8(uint value) public pure returns (uint256 word) {
assembly {
mstore8(0x80, value)
word := mload(0x80)
}
}
}
+122 -224
View File
@@ -1,100 +1,48 @@
use alloy_primitives::{Address, Bytes, I256, U256};
use alloy_sol_types::{sol, SolCall, SolConstructor};
use revive_solidity::test_utils::*;
use alloy_primitives::U256;
use alloy_sol_types::{sol, SolCall};
#[derive(Clone)]
pub struct Contract {
pub name: &'static str,
pub evm_runtime: Vec<u8>,
pub pvm_runtime: Vec<u8>,
pub calldata: Vec<u8>,
}
macro_rules! case {
// Arguments:
// 1. The file name, expect to live under "../contracts/"
// 2. The Solidity contract name
// 3. The derived Solidity function call name
// 4. The method name on [Contract]
// 5. Any parameters to the Solidity functions
($file_name:literal, $contract_name:ident, $contract_method:ident, $method_name:ident, $( $v:ident: $t:ty ),* ) => {
impl Contract {
pub fn $method_name($($v: $t),*) -> Self {
let code = include_str!(concat!("../contracts/", $file_name));
let args = $contract_name::$contract_method::new(($($v,)*)).abi_encode();
let name = stringify!($contract_name);
Contract::build(args, name, code)
}
}
};
// Arguments:
// 1. The file name, expect to live under "../contracts/"
// 2. The Solidity contract name
// 3. Raw Calldata
// 4. The method name on [Contract]
($file_name:literal, $contract_name:literal, $calldata:expr, $method_name:ident) => {
impl Contract {
pub fn $method_name() -> Self {
let code = include_str!(concat!("../contracts/", $file_name));
Contract::build($calldata, $contract_name, code)
}
}
};
}
case!("Create.sol", "CreateA", vec![0; 4], create_a);
case!("Create.sol", "CreateB", vec![0; 4], create_b);
sol!(contract Baseline { function baseline() public payable; });
case!("Baseline.sol", Baseline, baselineCall, baseline,);
sol!(contract Flipper {
constructor (bool);
function flip() public;
});
case!("flipper.sol", Flipper, flipCall, flipper,);
case!("flipper.sol", Flipper, constructorCall, flipper_constructor, coin: bool);
sol!(contract Flipper { function flip() public; });
sol!(contract Computation {
function odd_product(int32 n) public pure returns (int64);
function triangle_number(int64 n) public pure returns (int64 sum);
});
case!("Computation.sol", Computation, odd_productCall, odd_product, n: i32);
case!("Computation.sol", Computation, triangle_numberCall, triangle_number, n: i64);
sol!(
contract FibonacciRecursive {
function fib3(uint n) public pure returns (uint);
}
);
case!("Fibonacci.sol", FibonacciRecursive, fib3Call, fib_recursive, n: U256);
sol!(
contract FibonacciIterative {
function fib3(uint n) external pure returns (uint b);
}
);
case!("Fibonacci.sol", FibonacciIterative, fib3Call, fib_iterative, n: U256);
sol!(
contract FibonacciBinet {
function fib3(uint n) external pure returns (uint a);
}
);
case!("Fibonacci.sol", FibonacciBinet, fib3Call, fib_binet, n: U256);
sol!(
contract SHA1 {
function sha1(bytes memory data) public pure returns (bytes20 ret);
}
);
case!("SHA1.sol", SHA1, sha1Call, sha1, pre: Bytes);
sol!(
contract ERC20 {
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
@@ -118,154 +66,112 @@ sol!(
event Approval(address indexed owner, address indexed spender, uint value);
}
);
case!("ERC20.sol", ERC20, totalSupplyCall, erc20,);
sol!(
contract Block {
function timestamp() public view returns (uint ret);
function number() public view returns (uint ret);
}
);
case!("Block.sol", Block, numberCall, block_number,);
case!("Block.sol", Block, timestampCall, block_timestamp,);
sol!(
contract Context {
function address_this() public view returns (address);
function caller() public pure returns (address);
}
);
case!("Context.sol", Context, address_thisCall, context_address,);
case!("Context.sol", Context, callerCall, context_caller,);
sol!(
contract DivisionArithmetics {
function div(uint n, uint d) public pure returns (uint q);
function sdiv(int n, int d) public pure returns (int q);
function mod(uint n, uint d) public pure returns (uint r);
function smod(int n, int d) public pure returns (int r);
}
);
case!("DivisionArithmetics.sol", DivisionArithmetics, divCall, division_arithmetics_div, n: U256, d: U256);
case!("DivisionArithmetics.sol", DivisionArithmetics, sdivCall, division_arithmetics_sdiv, n: I256, d: I256);
case!("DivisionArithmetics.sol", DivisionArithmetics, modCall, division_arithmetics_mod, n: U256, d: U256);
case!("DivisionArithmetics.sol", DivisionArithmetics, smodCall, division_arithmetics_smod, n: I256, d: I256);
sol!(
contract Send {
function transfer_self(uint _amount) public payable;
}
);
case!("Send.sol", Send, transfer_selfCall, send_self, amount: U256);
sol!(
contract Transfer {
function transfer_self(uint _amount) public payable;
}
);
case!("Transfer.sol", Transfer, transfer_selfCall, transfer_self, amount: U256);
sol!(
contract MStore8 {
function mStore8(uint value) public pure returns (uint256 word);
}
);
case!("MStore8.sol", MStore8, mStore8Call, mstore8, value: U256);
sol!(
contract Events {
event A(uint) anonymous;
event E(uint indexed, uint indexed, uint indexed);
function emitEvent(uint topics) public;
}
);
case!("Events.sol", Events, emitEventCall, event, topics: U256);
sol!(
contract ExtCode {
function ExtCodeSize(address who) public view returns (uint ret);
function CodeSize() public pure returns (uint ret);
function ExtCodeHash(address who) public view returns (bytes32 ret);
function CodeHash() public view returns (bytes32 ret);
}
);
case!("ExtCode.sol", ExtCode, ExtCodeSizeCall, ext_code_size, address: Address);
case!("ExtCode.sol", ExtCode, CodeSizeCall, code_size,);
case!("ExtCode.sol", ExtCode, ExtCodeHashCall, ext_code_hash, address: Address);
case!("ExtCode.sol", ExtCode, CodeHashCall, code_hash,);
sol!(
contract MCopy {
function memcpy(bytes memory payload) public pure returns (bytes memory);
}
);
case!("MCopy.sol", MCopy, memcpyCall, memcpy, payload: Bytes);
sol!(
contract Call {
function value_transfer(address payable destination) public payable;
function echo(bytes memory payload) public payable returns (bytes memory);
function call(
address callee,
bytes memory payload
) public payable returns (bytes memory);
}
);
case!("Call.sol", Call, value_transferCall, call_value_transfer, destination: Address);
case!("Call.sol", Call, callCall, call_call, destination: Address, payload: Bytes);
case!("Call.sol", "Call", vec![], call_constructor);
sol!(
contract Value {
function balance_of(address _address) public view returns (uint ret);
function balance_self() public view returns (uint ret);
}
);
case!("Value.sol", Value, balance_ofCall, value_balance_of, address: Address);
case!("Value.sol", Value, balance_selfCall, value_balance_self,);
sol!(
contract Bitwise {
function opByte(uint i, uint x) public payable returns (uint ret);
}
);
case!("Bitwise.sol", Bitwise, opByteCall, bitwise_byte, index: U256, value: U256);
sol!(
contract Storage {
function transient(uint value) public returns (uint ret);
}
);
case!("Storage.sol", Storage, transientCall, storage_transient, value: U256);
impl Contract {
fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
pub fn baseline() -> Self {
let code = include_str!("../contracts/Baseline.sol");
let name = "Baseline";
Self {
name,
evm_runtime: compile_evm_bin_runtime(name, code),
pvm_runtime: compile_blob(name, code),
calldata,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Baseline::baselineCall::new(()).abi_encode(),
}
}
pub fn odd_product(n: i32) -> Self {
let code = include_str!("../contracts/Computation.sol");
let name = "Computation";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Computation::odd_productCall::new((n,)).abi_encode(),
}
}
pub fn triangle_number(n: i64) -> Self {
let code = include_str!("../contracts/Computation.sol");
let name = "Computation";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Computation::triangle_numberCall::new((n,)).abi_encode(),
}
}
pub fn fib_recursive(n: u32) -> Self {
let code = include_str!("../contracts/Fibonacci.sol");
let name = "FibonacciRecursive";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: FibonacciRecursive::fib3Call::new((U256::from(n),)).abi_encode(),
}
}
pub fn fib_iterative(n: u32) -> Self {
let code = include_str!("../contracts/Fibonacci.sol");
let name = "FibonacciIterative";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: FibonacciIterative::fib3Call::new((U256::from(n),)).abi_encode(),
}
}
pub fn fib_binet(n: u32) -> Self {
let code = include_str!("../contracts/Fibonacci.sol");
let name = "FibonacciBinet";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: FibonacciBinet::fib3Call::new((U256::from(n),)).abi_encode(),
}
}
pub fn sha1(pre: Vec<u8>) -> Self {
let code = include_str!("../contracts/SHA1.sol");
let name = "SHA1";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: SHA1::sha1Call::new((pre,)).abi_encode(),
}
}
pub fn flipper() -> Self {
let code = include_str!("../contracts/flipper.sol");
let name = "Flipper";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Flipper::flipCall::new(()).abi_encode(),
}
}
pub fn erc20() -> Self {
let code = include_str!("../contracts/ERC20.sol");
let name = "ERC20";
Self {
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: IERC20::totalSupplyCall::new(()).abi_encode(),
}
}
}
#[cfg(test)]
mod tests {
use alloy_primitives::{Bytes, U256};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::{de::Deserialize, Serialize};
use std::{collections::BTreeMap, fs::File};
use std::{collections::HashMap, fs::File};
use super::Contract;
@@ -275,48 +181,40 @@ mod tests {
let existing = File::open(path)
.map(|file| {
BTreeMap::<String, usize>::deserialize(&mut serde_json::Deserializer::from_reader(
HashMap::<String, usize>::deserialize(&mut serde_json::Deserializer::from_reader(
file,
))
.expect("should be able to deserialze codesize data")
})
.ok();
let extract_code_size = |compile: fn() -> Contract| {
let contract = compile();
let contract_length = contract.pvm_runtime.len();
let size_change = existing
let sizes = HashMap::from([
("Baseline", Contract::baseline().pvm_runtime.len()),
("Flipper", Contract::flipper().pvm_runtime.len()),
("Computation", Contract::odd_product(0).pvm_runtime.len()),
("Fibonacci", Contract::fib_iterative(0).pvm_runtime.len()),
("ERC20", Contract::erc20().pvm_runtime.len()),
]);
for (name, bytes) in sizes.iter() {
let change = existing
.as_ref()
.and_then(|map| map.get(contract.name))
.filter(|old| **old != 0)
.and_then(|map| map.get(*name))
.map(|old| {
let new = *bytes as f32;
let old = *old as f32;
let p = (contract_length as f32 - old) / old * 100.0;
format!("({p}% change from {old} bytes)",)
let p = (new - old) / new * 100.0;
format!("({p}% change from {old} bytes)")
})
.unwrap_or_default();
println!("{}: {contract_length} bytes {size_change}", contract.name);
println!("{name}: {bytes} bytes {change}");
}
(contract.name, contract_length)
};
[
Contract::baseline as fn() -> Contract,
Contract::flipper as fn() -> Contract,
(|| Contract::odd_product(0)) as fn() -> Contract,
(|| Contract::fib_iterative(U256::ZERO)) as fn() -> Contract,
Contract::erc20 as fn() -> Contract,
(|| Contract::sha1(Bytes::new())) as fn() -> Contract,
(|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract,
(|| Contract::event(U256::ZERO)) as fn() -> Contract,
]
.into_par_iter()
.map(extract_code_size)
.collect::<BTreeMap<_, _>>()
.serialize(&mut serde_json::Serializer::pretty(
File::create(path).unwrap(),
))
.unwrap_or_else(|err| panic!("can not write codesize data to '{path}': {err}"));
sizes
.serialize(&mut serde_json::Serializer::pretty(
File::create(path).unwrap(),
))
.unwrap_or_else(|err| panic!("can not write codesize data to '{}': {}", path, err));
}
}
+81
View File
@@ -1,4 +1,85 @@
use cases::Contract;
use mock_runtime::State;
pub mod cases;
pub mod mock_runtime;
#[cfg(test)]
mod tests;
/// Compile the blob of `contract_name` found in given `source_code`.
/// The `solc` optimizer will be enabled
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
compile_blob_with_options(
contract_name,
source_code,
true,
revive_solidity::SolcPipeline::Yul,
)
}
/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`.
/// The `solc` optimizer will be enabled
pub fn compile_evm_bin_runtime(contract_name: &str, source_code: &str) -> Vec<u8> {
let file_name = "contract.sol";
let contracts = revive_solidity::test_utils::build_solidity_with_options_evm(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
revive_solidity::SolcPipeline::Yul,
true,
)
.expect("source should compile");
let bin_runtime = &contracts
.get(contract_name)
.unwrap_or_else(|| panic!("contract '{}' didn't produce bin-runtime", contract_name))
.object;
hex::decode(bin_runtime).expect("bin-runtime shold be hex encoded")
}
/// Compile the blob of `contract_name` found in given `source_code`.
pub fn compile_blob_with_options(
contract_name: &str,
source_code: &str,
solc_optimizer_enabled: bool,
pipeline: revive_solidity::SolcPipeline,
) -> Vec<u8> {
let file_name = "contract.sol";
let contracts = revive_solidity::test_utils::build_solidity_with_options(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
pipeline,
era_compiler_llvm_context::OptimizerSettings::cycles(),
solc_optimizer_enabled,
)
.expect("source should compile")
.contracts
.expect("source should contain at least one contract");
let bytecode = contracts[file_name][contract_name]
.evm
.as_ref()
.expect("source should produce EVM output")
.assembly_text
.as_ref()
.expect("source should produce assembly text");
hex::decode(bytecode).expect("hex encoding should always be valid")
}
pub fn assert_success(contract: Contract, differential: bool) -> State {
let (mut instance, export) = mock_runtime::prepare(&contract.pvm_runtime, None);
let state = mock_runtime::call(State::new(contract.calldata.clone()), &mut instance, export);
assert_eq!(state.output.flags, 0);
if differential {
let evm = revive_differential::prepare(contract.evm_runtime, contract.calldata);
assert_eq!(state.output.data.clone(), revive_differential::execute(evm));
}
state
}
+256
View File
@@ -0,0 +1,256 @@
//! Mock environment used for integration tests.
//! TODO: Switch to drink! once RISCV is ready in polkadot-sdk
use std::collections::HashMap;
use alloy_primitives::{Keccak256, U256};
use polkavm::{
Caller, Config, Engine, ExportIndex, GasMeteringKind, Instance, Linker, Module, ModuleConfig,
ProgramBlob, Trap,
};
#[derive(Default, Clone, Debug)]
pub struct State {
pub input: Vec<u8>,
pub output: CallOutput,
pub value: u128,
pub storage: HashMap<U256, U256>,
}
#[derive(Clone, Debug)]
pub struct CallOutput {
pub flags: u32,
pub data: Vec<u8>,
}
impl Default for CallOutput {
fn default() -> Self {
Self {
flags: u32::MAX,
data: Vec::new(),
}
}
}
impl State {
pub fn new(input: Vec<u8>) -> Self {
Self {
input,
..Default::default()
}
}
pub fn reset_output(&mut self) {
self.output = Default::default();
}
pub fn assert_storage_key(&self, at: U256, expect: U256) {
assert_eq!(self.storage[&at], expect);
}
}
fn link_host_functions(engine: &Engine) -> Linker<State> {
let mut linker = Linker::new(engine);
linker
.func_wrap(
"input",
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, state) = caller.split();
assert!(state.input.len() <= caller.read_u32(out_len_ptr).unwrap() as usize);
caller.write_memory(out_ptr, &state.input)?;
caller.write_memory(out_len_ptr, &(state.input.len() as u32).to_le_bytes())?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
"seal_return",
|caller: Caller<State>, flags: u32, data_ptr: u32, data_len: u32| -> Result<(), Trap> {
let (caller, state) = caller.split();
state.output.flags = flags;
state.output.data = caller.read_memory_into_vec(data_ptr, data_len)?;
Err(Default::default())
},
)
.unwrap();
linker
.func_wrap(
"value_transferred",
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, state) = caller.split();
let value = state.value.to_le_bytes();
caller.write_memory(out_ptr, &value)?;
caller.write_memory(out_len_ptr, &(value.len() as u32).to_le_bytes())?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
"debug_message",
|caller: Caller<State>, str_ptr: u32, str_len: u32| -> Result<u32, Trap> {
let (caller, _) = caller.split();
let data = caller.read_memory_into_vec(str_ptr, str_len)?;
print!("debug_message: {}", String::from_utf8(data).unwrap());
Ok(0)
},
)
.unwrap();
linker
.func_wrap(
"set_storage",
|caller: Caller<State>,
key_ptr: u32,
key_len: u32,
value_ptr: u32,
value_len: u32|
-> Result<u32, Trap> {
let (caller, state) = caller.split();
assert_eq!(key_len, 32, "storage key must be 32 bytes");
assert_eq!(value_len, 32, "storage value must be 32 bytes");
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
let value = caller.read_memory_into_vec(value_ptr, value_len)?;
state.storage.insert(
U256::from_be_bytes::<32>(key.try_into().unwrap()),
U256::from_be_bytes::<32>(value.try_into().unwrap()),
);
Ok(0)
},
)
.unwrap();
linker
.func_wrap(
"get_storage",
|caller: Caller<State>,
key_ptr: u32,
key_len: u32,
out_ptr: u32,
out_len_ptr: u32|
-> Result<u32, Trap> {
let (mut caller, state) = caller.split();
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
let out_len = caller.read_u32(out_len_ptr)?;
assert!(out_len >= 32);
let value = state
.storage
.get(&U256::from_be_bytes::<32>(key.try_into().unwrap()))
.map(U256::to_be_bytes::<32>)
.unwrap_or_default();
caller.write_memory(out_ptr, &value[..])?;
caller.write_memory(out_len_ptr, &32u32.to_le_bytes())?;
Ok(0)
},
)
.unwrap();
linker
.func_wrap(
"hash_keccak_256",
|caller: Caller<State>,
input_ptr: u32,
input_len: u32,
out_ptr: u32|
-> Result<(), Trap> {
let (mut caller, _) = caller.split();
let pre = caller.read_memory_into_vec(input_ptr, input_len)?;
let mut hasher = Keccak256::new();
hasher.update(&pre);
caller.write_memory(out_ptr, &hasher.finalize()[..])?;
Ok(())
},
)
.unwrap();
linker
}
pub fn setup(config: Option<Config>) -> Engine {
Engine::new(&config.unwrap_or_default()).unwrap()
}
pub fn recompile_code(code: &[u8], engine: &Engine) -> Module {
let mut module_config = ModuleConfig::new();
module_config.set_gas_metering(Some(GasMeteringKind::Sync));
Module::new(engine, &module_config, code).unwrap()
}
pub fn instantiate_module(module: &Module, engine: &Engine) -> (Instance<State>, ExportIndex) {
let export = module.lookup_export("call").unwrap();
let func = link_host_functions(engine).instantiate_pre(module).unwrap();
let instance = func.instantiate().unwrap();
(instance, export)
}
pub fn prepare(code: &[u8], config: Option<Config>) -> (Instance<State>, ExportIndex) {
let blob = ProgramBlob::parse(code).unwrap();
let engine = Engine::new(&config.unwrap_or_default()).unwrap();
let mut module_config = ModuleConfig::new();
module_config.set_gas_metering(Some(GasMeteringKind::Sync));
let module = Module::from_blob(&engine, &module_config, &blob).unwrap();
let export = module.lookup_export("call").unwrap();
let func = link_host_functions(&engine)
.instantiate_pre(&module)
.unwrap();
let instance = func.instantiate().unwrap();
(instance, export)
}
pub fn call(mut state: State, on: &mut Instance<State>, export: ExportIndex) -> State {
state.reset_output();
let mut state_args = polkavm::StateArgs::default();
state_args.set_gas(polkavm::Gas::MAX);
let call_args = polkavm::CallArgs::new(&mut state, export);
init_logs();
match on.call(state_args, call_args) {
Err(polkavm::ExecutionError::Trap(_)) => state,
Err(other) => panic!("unexpected error: {other}"),
Ok(_) => panic!("unexpected return"),
}
}
fn init_logs() {
if std::env::var("RUST_LOG").is_ok() {
#[cfg(test)]
let test = true;
#[cfg(not(test))]
let test = false;
let _ = env_logger::builder().is_test(test).try_init();
}
}
+253 -470
View File
@@ -1,483 +1,266 @@
use std::str::FromStr;
use alloy_primitives::{FixedBytes, Keccak256, I256, U256};
use alloy_sol_types::{sol, SolCall};
use sha1::Digest;
use alloy_primitives::*;
use revive_runner::*;
use SpecsAction::*;
use crate::{
assert_success,
cases::Contract,
mock_runtime::{self, State},
};
use crate::cases::Contract;
#[test]
fn fibonacci() {
let parameter = 6;
/// Parameters:
/// - The function name of the test
/// - The contract name to fill in empty code based on the file path
/// - The contract source file
macro_rules! test_spec {
($test_name:ident, $contract_name:literal, $source_file:literal) => {
#[test]
fn $test_name() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("should always exist");
let path = format!("{manifest_dir}/../integration/contracts/{}", $source_file);
Specs::from_comment($contract_name, &path).remove(0).run();
for contract in [
Contract::fib_recursive(parameter),
Contract::fib_iterative(parameter),
Contract::fib_binet(parameter),
] {
let state = assert_success(contract, true);
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
let expected = U256::from(8);
assert_eq!(received, expected);
}
}
#[test]
fn flipper() {
let contract = Contract::flipper();
let (mut instance, export) = mock_runtime::prepare(&contract.pvm_runtime, None);
let state = crate::mock_runtime::call(State::new(contract.calldata), &mut instance, export);
assert_eq!(state.output.flags, 0);
assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap());
let state = crate::mock_runtime::call(state, &mut instance, export);
assert_eq!(state.output.flags, 0);
assert_eq!(state.storage[&U256::ZERO], U256::ZERO);
}
#[test]
fn hash_keccak_256() {
sol!(
#[derive(Debug, PartialEq, Eq)]
contract TestSha3 {
function test(string memory _pre) external payable returns (bytes32);
}
};
}
test_spec!(baseline, "Baseline", "Baseline.sol");
test_spec!(flipper, "Flipper", "flipper.sol");
test_spec!(fibonacci_recursive, "FibonacciRecursive", "Fibonacci.sol");
test_spec!(fibonacci_iterative, "FibonacciIterative", "Fibonacci.sol");
test_spec!(fibonacci_binet, "FibonacciBinet", "Fibonacci.sol");
test_spec!(hash_keccak_256, "TestSha3", "Crypto.sol");
test_spec!(erc20, "ERC20", "ERC20.sol");
test_spec!(computation, "Computation", "Computation.sol");
test_spec!(msize, "MSize", "MSize.sol");
test_spec!(sha1, "SHA1", "SHA1.sol");
test_spec!(block, "Block", "Block.sol");
test_spec!(mcopy, "MCopy", "MCopy.sol");
test_spec!(events, "Events", "Events.sol");
test_spec!(storage, "Storage", "Storage.sol");
test_spec!(mstore8, "MStore8", "MStore8.sol");
test_spec!(address, "Context", "Context.sol");
test_spec!(value, "Value", "Value.sol");
test_spec!(create, "CreateB", "Create.sol");
test_spec!(call, "Caller", "Call.sol");
test_spec!(balance, "Balance", "Balance.sol");
test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol");
test_spec!(immutables, "Immutables", "Immutables.sol");
test_spec!(transaction, "Transaction", "Transaction.sol");
test_spec!(block_hash, "BlockHash", "BlockHash.sol");
test_spec!(delegate, "Delegate", "Delegate.sol");
test_spec!(gas_price, "GasPrice", "GasPrice.sol");
test_spec!(gas_left, "GasLeft", "GasLeft.sol");
test_spec!(gas_limit, "GasLimit", "GasLimit.sol");
test_spec!(base_fee, "BaseFee", "BaseFee.sol");
test_spec!(coinbase, "Coinbase", "Coinbase.sol");
test_spec!(create2, "CreateB", "Create2.sol");
test_spec!(transfer, "Transfer", "Transfer.sol");
test_spec!(send, "Send", "Send.sol");
test_spec!(function_pointer, "FunctionPointer", "FunctionPointer.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate {
origin: TestAddress::Alice,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
code: Code::Solidity {
path: Some(path.into()),
contract: contract.to_string(),
solc_optimizer: None,
},
data: vec![],
salt: OptionalHex::default(),
}]
}
fn run_differential(actions: Vec<SpecsAction>) {
Specs {
differential: true,
actions,
..Default::default()
}
.run();
}
#[test]
fn bitwise_byte() {
let mut actions = instantiate("contracts/Bitwise.sol", "Bitwise");
let de_bruijn_sequence =
hex::decode("4060503824160d0784426150b864361d0f88c4a27148ac5a2f198d46e391d8f4").unwrap();
let value = U256::from_be_bytes::<32>(de_bruijn_sequence.clone().try_into().unwrap());
for input in de_bruijn_sequence
.iter()
.enumerate()
.map(|(index, _)| Contract::bitwise_byte(U256::from(index), value).calldata)
.chain([
Contract::bitwise_byte(U256::ZERO, U256::ZERO).calldata,
Contract::bitwise_byte(U256::ZERO, U256::MAX).calldata,
Contract::bitwise_byte(U256::MAX, U256::ZERO).calldata,
Contract::bitwise_byte(U256::from_str("18446744073709551619").unwrap(), U256::MAX)
.calldata,
])
{
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: input,
})
}
run_differential(actions);
}
#[test]
fn unsigned_division() {
let mut actions = instantiate("contracts/DivisionArithmetics.sol", "DivisionArithmetics");
let one = U256::from(1);
let two = U256::from(2);
let five = U256::from(5);
for (n, d) in [
(five, five),
(five, one),
(U256::ZERO, U256::MAX),
(five, two),
(one, U256::ZERO),
] {
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::division_arithmetics_div(n, d).calldata,
})
}
run_differential(actions);
}
#[test]
fn signed_division() {
let mut actions = instantiate("contracts/DivisionArithmetics.sol", "DivisionArithmetics");
let one = I256::try_from(1).unwrap();
let two = I256::try_from(2).unwrap();
let minus_two = I256::try_from(-2).unwrap();
let five = I256::try_from(5).unwrap();
let minus_five = I256::try_from(-5).unwrap();
for (n, d) in [
(five, five),
(five, one),
(I256::ZERO, I256::MAX),
(I256::ZERO, I256::MINUS_ONE),
(five, two),
(five, I256::MINUS_ONE),
(I256::MINUS_ONE, minus_two),
(minus_five, minus_five),
(minus_five, two),
(I256::MINUS_ONE, I256::MIN),
(one, I256::ZERO),
] {
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::division_arithmetics_sdiv(n, d).calldata,
})
}
run_differential(actions);
}
#[test]
fn unsigned_remainder() {
let mut actions = instantiate("contracts/DivisionArithmetics.sol", "DivisionArithmetics");
let one = U256::from(1);
let two = U256::from(2);
let five = U256::from(5);
for (n, d) in [
(five, five),
(five, one),
(U256::ZERO, U256::MAX),
(U256::MAX, U256::MAX),
(five, two),
(two, five),
(U256::MAX, U256::ZERO),
] {
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::division_arithmetics_mod(n, d).calldata,
})
}
run_differential(actions);
}
#[test]
fn signed_remainder() {
let mut actions = instantiate("contracts/DivisionArithmetics.sol", "DivisionArithmetics");
let one = I256::try_from(1).unwrap();
let two = I256::try_from(2).unwrap();
let minus_two = I256::try_from(-2).unwrap();
let five = I256::try_from(5).unwrap();
let minus_five = I256::try_from(-5).unwrap();
for (n, d) in [
(five, five),
(five, one),
(I256::ZERO, I256::MAX),
(I256::MAX, I256::MAX),
(five, two),
(two, five),
(five, minus_five),
(five, I256::MINUS_ONE),
(five, minus_two),
(minus_five, two),
(minus_two, five),
(minus_five, minus_five),
(minus_five, I256::MINUS_ONE),
(minus_five, minus_two),
(minus_two, minus_five),
(I256::MIN, I256::MINUS_ONE),
(I256::ZERO, I256::ZERO),
] {
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::division_arithmetics_smod(n, d).calldata,
})
}
run_differential(actions);
}
#[test]
fn ext_code_hash() {
let mut actions = instantiate("contracts/ExtCode.sol", "ExtCode");
// First do contract instantiation to figure out address and code hash
let results = Specs {
actions: actions.clone(),
..Default::default()
}
.run();
let (addr, code_hash) = match results.first().cloned() {
Some(CallResult::Instantiate {
result, code_hash, ..
}) => (result.result.unwrap().addr, code_hash),
_ => panic!("instantiate contract failed"),
};
// code hash of itself
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::code_hash().calldata,
});
actions.push(VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from(code_hash.as_bytes().to_vec()),
gas_consumed: None,
}));
// code hash for a given contract address
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::ext_code_hash(Address::from(addr.to_fixed_bytes())).calldata,
});
actions.push(VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from(code_hash.as_bytes().to_vec()),
gas_consumed: None,
}));
// EOA returns fixed hash
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::ext_code_hash(Address::from(CHARLIE.to_fixed_bytes())).calldata,
});
actions.push(VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from(
hex!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(),
),
gas_consumed: None,
}));
// non-existing account
actions.push(Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::ext_code_hash(Address::from([8u8; 20])).calldata,
});
actions.push(VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from([0u8; 32].to_vec()),
gas_consumed: None,
}));
Specs {
actions,
..Default::default()
}
.run();
}
#[test]
fn ext_code_size() {
let alice = Address::from(ALICE.0);
let own_address = alice.create(0);
let baseline_address = alice.create2([0u8; 32], keccak256(Contract::baseline().pvm_runtime));
let own_code_size = U256::from(
Contract::ext_code_size(Default::default())
.pvm_runtime
.len(),
);
let baseline_code_size = U256::from(Contract::baseline().pvm_runtime.len());
let source = r#"contract TestSha3 {
function test(string memory _pre) external payable returns (bytes32 hash) {
hash = keccak256(bytes(_pre));
}
}"#;
let code = crate::compile_blob("TestSha3", source);
Specs {
actions: vec![
// Instantiate the test contract
instantiate("contracts/ExtCode.sol", "ExtCode").remove(0),
// Instantiate the baseline contract
Instantiate {
origin: TestAddress::Alice,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: None,
code: Code::Solidity {
path: Some("contracts/Baseline.sol".into()),
contract: "Baseline".to_string(),
solc_optimizer: None,
},
data: vec![],
salt: OptionalHex::from([0; 32]),
},
// Alice is not a contract and returns a code size of 0
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::ext_code_size(alice).calldata,
},
VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from([0u8; 32].to_vec()),
gas_consumed: None,
}),
// Unknown address returns a code size of 0
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::ext_code_size(Address::from([0xff; 20])).calldata,
},
VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from([0u8; 32].to_vec()),
gas_consumed: None,
}),
// Own address via extcodesize returns own code size
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::ext_code_size(own_address).calldata,
},
VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from(own_code_size.to_be_bytes::<32>().to_vec()),
gas_consumed: None,
}),
// Own address via codesize returns own code size
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::code_size().calldata,
},
VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from(own_code_size.to_be_bytes::<32>().to_vec()),
gas_consumed: None,
}),
// Baseline address returns the baseline code size
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value: 0,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::ext_code_size(baseline_address).calldata,
},
VerifyCall(VerifyCallExpectation {
success: true,
output: OptionalHex::from(baseline_code_size.to_be_bytes::<32>().to_vec()),
gas_consumed: None,
}),
],
..Default::default()
}
.run();
let param = "hello";
let input = TestSha3::testCall::new((param.to_string(),)).abi_encode();
let state = State::new(input);
let (mut instance, export) = mock_runtime::prepare(&code, None);
let state = crate::mock_runtime::call(state, &mut instance, export);
assert_eq!(state.output.flags, 0);
let mut hasher = Keccak256::new();
hasher.update(param);
let expected = hasher.finalize();
let received = FixedBytes::<32>::from_slice(&state.output.data);
assert_eq!(received, expected);
}
#[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();
fn erc20() {
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
}
#[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();
fn triangle_number() {
let state = assert_success(Contract::triangle_number(13), true);
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
let expected = U256::try_from(91).unwrap();
assert_eq!(received, expected);
}
#[test]
fn odd_product() {
let state = assert_success(Contract::odd_product(5), true);
let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
let expected = I256::try_from(945i64).unwrap();
assert_eq!(received, expected);
}
#[test]
fn msize_plain() {
sol!(
#[derive(Debug, PartialEq, Eq)]
contract MSize {
function mSize() public pure returns (uint);
}
);
let code = crate::compile_blob_with_options(
"MSize",
include_str!("../contracts/MSize.sol"),
false,
revive_solidity::SolcPipeline::EVMLA,
);
let (mut instance, export) = mock_runtime::prepare(&code, None);
let input = MSize::mSizeCall::new(()).abi_encode();
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
assert_eq!(state.output.flags, 0);
// Solidity always stores the "free memory pointer" (32 byte int) at offset 64.
let expected = U256::try_from(64 + 32).unwrap();
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
}
#[test]
fn transferred_value() {
sol!(
contract Value {
function value() public payable returns (uint);
}
);
let code = crate::compile_blob("Value", include_str!("../contracts/Value.sol"));
let mut state = State::new(Value::valueCall::SELECTOR.to_vec());
state.value = 0x1;
let (mut instance, export) = mock_runtime::prepare(&code, None);
let state = crate::mock_runtime::call(state, &mut instance, export);
assert_eq!(state.output.flags, 0);
let expected = I256::try_from(state.value).unwrap();
let received = I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
}
#[test]
fn msize_non_word_sized_access() {
sol!(
#[derive(Debug, PartialEq, Eq)]
contract MSize {
function mStore100() public pure returns (uint);
}
);
let code = crate::compile_blob_with_options(
"MSize",
include_str!("../contracts/MSize.sol"),
false,
revive_solidity::SolcPipeline::Yul,
);
let (mut instance, export) = mock_runtime::prepare(&code, None);
let input = MSize::mStore100Call::new(()).abi_encode();
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
assert_eq!(state.output.flags, 0);
// https://docs.zksync.io/build/developer-reference/differences-with-ethereum.html#mstore-mload
// "Unlike EVM, where the memory growth is in words, on zkEVM the memory growth is counted in bytes."
// "For example, if you write mstore(100, 0) the msize on zkEVM will be 132, but on the EVM it will be 160."
let expected = U256::try_from(132).unwrap();
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
}
#[test]
fn mstore8() {
sol!(
#[derive(Debug, PartialEq, Eq)]
contract MStore8 {
function mStore8(uint value) public pure returns (uint256 word);
}
);
let code = crate::compile_blob("MStore8", include_str!("../contracts/mStore8.sol"));
let (mut instance, export) = mock_runtime::prepare(&code, None);
let mut assert = |parameter, expected| {
let input = MStore8::mStore8Call::new((parameter,)).abi_encode();
let state = crate::mock_runtime::call(State::new(input), &mut instance, export);
assert_eq!(state.output.flags, 0);
let received = U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
};
for (parameter, expected) in [
(U256::MIN, U256::MIN),
(
U256::from(1),
U256::from_str_radix(
"452312848583266388373324160190187140051835877600158453279131187530910662656",
10,
)
.unwrap(),
),
(
U256::from(2),
U256::from_str_radix(
"904625697166532776746648320380374280103671755200316906558262375061821325312",
10,
)
.unwrap(),
),
(
U256::from(255),
U256::from_str_radix(
"115339776388732929035197660848497720713218148788040405586178452820382218977280",
10,
)
.unwrap(),
),
(U256::from(256), U256::from(0)),
(
U256::from(257),
U256::from_str_radix(
"452312848583266388373324160190187140051835877600158453279131187530910662656",
10,
)
.unwrap(),
),
(
U256::from(258),
U256::from_str_radix(
"904625697166532776746648320380374280103671755200316906558262375061821325312",
10,
)
.unwrap(),
),
(
U256::from(123456789),
U256::from_str_radix(
"9498569820248594155839807363993929941088553429603327518861754938149123915776",
10,
)
.unwrap(),
),
(
U256::MAX,
U256::from_str_radix(
"115339776388732929035197660848497720713218148788040405586178452820382218977280",
10,
)
.unwrap(),
),
] {
assert(parameter, expected);
}
}
#[test]
fn sha1() {
let pre = vec![0xffu8; 512];
let mut hasher = sha1::Sha1::new();
hasher.update(&pre);
let hash = hasher.finalize();
let state = assert_success(Contract::sha1(pre), true);
let expected = FixedBytes::<20>::from_slice(&hash[..]);
let received = FixedBytes::<20>::from_slice(&state.output.data[..20]);
assert_eq!(received, expected);
}
+8 -8
View File
@@ -1,17 +1,17 @@
[package]
name = "revive-linker"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
description = "revive compiler linker utils"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
inkwell = { workspace = true }
tempfile = { workspace = true }
polkavm-linker = { workspace = true }
polkavm-common = { workspace = true }
libc = { workspace = true }
anyhow = { workspace = true }
revive-builtins = { workspace = true }
lld-sys = { workspace = true }
revive-builtins = { path = "../builtins" }
lld-sys = { path = "../lld-sys" }
+11 -10
View File
@@ -8,9 +8,6 @@ SECTIONS {
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
}"#;
const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
const BUILTINS_LIB_NAME: &str = "clang_rt.builtins-riscv64";
fn invoke_lld(cmd_args: &[&str]) -> bool {
let c_strings = cmd_args
.iter()
@@ -22,12 +19,12 @@ fn invoke_lld(cmd_args: &[&str]) -> bool {
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
}
pub fn polkavm_linker<T: AsRef<[u8]>>(code: T, strip_binary: bool) -> anyhow::Result<Vec<u8>> {
fn polkavm_linker<T: AsRef<[u8]>>(code: T) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default();
config.set_strip(strip_binary);
config.set_optimize(true);
config.set_strip(true);
polkavm_linker::program_from_elf(config, code.as_ref())
.map(|blob| blob.as_bytes().to_vec())
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
}
@@ -36,7 +33,7 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
let output_path = dir.path().join("out.so");
let object_path = dir.path().join("out.o");
let linker_script_path = dir.path().join("linker.ld");
let compiler_rt_path = dir.path().join(BUILTINS_ARCHIVE_FILE);
let compiler_rt_path = dir.path().join("libclang_rt.builtins-riscv64.a");
fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?;
@@ -57,12 +54,11 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
"--relocatable",
"--emit-relocs",
"--no-relax",
"--unique",
"--gc-sections",
"--library-path",
dir.path().to_str().expect("should be utf8"),
"--library",
BUILTINS_LIB_NAME,
"clang_rt.builtins-riscv64",
linker_script_path.to_str().expect("should be utf8"),
object_path.to_str().expect("should be utf8"),
"-o",
@@ -73,5 +69,10 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
return Err(anyhow::anyhow!("ld.lld failed"));
}
Ok(fs::read(&output_path)?)
if env::var("PVM_LINKER_DUMP_SO").is_ok() {
fs::copy(&output_path, "/tmp/out.so")?;
};
let blob = fs::read(&output_path)?;
polkavm_linker(blob)
}
+5 -7
View File
@@ -1,16 +1,14 @@
[package]
name = "lld-sys"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
version = "0.1.0"
edition = "2021"
build = "build.rs"
description = "bindings for ld.lld core"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = { workspace = true }
[build-dependencies]
cc = { workspace = true }
revive-build-utils = { workspace = true }
+14 -76
View File
@@ -1,16 +1,17 @@
fn set_rustc_link_flags() {
let llvm_lib_path = match std::env::var(revive_build_utils::REVIVE_LLVM_TARGET_PREFIX) {
Ok(path) => std::path::PathBuf::from(path).join("lib"),
_ => revive_build_utils::llvm_lib_dir(),
};
fn llvm_config(arg: &str) -> String {
let output = std::process::Command::new("llvm-config")
.args([arg])
.output()
.unwrap_or_else(|_| panic!("`llvm-config {arg}` failed"));
println!(
"cargo:rustc-link-search=native={}",
llvm_lib_path.to_string_lossy()
);
String::from_utf8(output.stdout)
.unwrap_or_else(|_| panic!("output of `llvm-config {arg}` should be utf8"))
}
fn set_rustc_link_flags() {
println!("cargo:rustc-link-search=native={}", llvm_config("--libdir"));
for lib in [
// These are required by ld.lld
"lldELF",
"lldCommon",
"lldMachO",
@@ -21,79 +22,16 @@ fn set_rustc_link_flags() {
"LLVMTargetParser",
"LLVMBinaryFormat",
"LLVMDemangle",
// The `llvm-sys` crate relies on `llvm-config` to obtain a list of required LLVM libraries
// during the build process. This works well in typical native environments, where `llvm-config`
// can accurately list the necessary libraries.
// However, when cross-compiling to WebAssembly using Emscripten, `llvm-config` fails to recognize
// JavaScript-based libraries, making it necessary to manually inject the required dependencies.
"LLVMRISCVDisassembler",
"LLVMRISCVAsmParser",
"LLVMRISCVCodeGen",
"LLVMRISCVDesc",
"LLVMRISCVInfo",
"LLVMExecutionEngine",
"LLVMOption",
"LLVMMCDisassembler",
"LLVMPasses",
"LLVMHipStdPar",
"LLVMCFGuard",
"LLVMCoroutines",
"LLVMipo",
"LLVMVectorize",
"LLVMInstrumentation",
"LLVMFrontendOpenMP",
"LLVMFrontendOffloading",
"LLVMGlobalISel",
"LLVMAsmPrinter",
"LLVMSelectionDAG",
"LLVMCodeGen",
"LLVMTarget",
"LLVMObjCARCOpts",
"LLVMCodeGenTypes",
"LLVMIRPrinter",
"LLVMScalarOpts",
"LLVMInstCombine",
"LLVMAggressiveInstCombine",
"LLVMTransformUtils",
"LLVMBitWriter",
"LLVMAnalysis",
"LLVMProfileData",
"LLVMDebugInfoDWARF",
"LLVMObject",
"LLVMMCParser",
"LLVMIRReader",
"LLVMAsmParser",
"LLVMMC",
"LLVMDebugInfoCodeView",
"LLVMBitReader",
"LLVMRemarks",
"LLVMBitstreamReader",
] {
println!("cargo:rustc-link-lib=static={lib}");
}
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
if target_os == "linux" {
if target_env == "musl" {
println!("cargo:rustc-link-lib=static=c++");
} else {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
}
#[cfg(target_os = "linux")]
println!("cargo:rustc-link-lib=dylib=stdc++");
}
fn main() {
println!(
"cargo:rerun-if-env-changed={}",
revive_build_utils::REVIVE_LLVM_HOST_PREFIX
);
println!(
"cargo:rerun-if-env-changed={}",
revive_build_utils::REVIVE_LLVM_TARGET_PREFIX
);
revive_build_utils::llvm_cxx_flags()
llvm_config("--cxxflags")
.split_whitespace()
.fold(&mut cc::Build::new(), |builder, flag| builder.flag(flag))
.flag("-Wno-unused-parameter")
-38
View File
@@ -1,38 +0,0 @@
[package]
name = "revive-llvm-builder"
description = "revive LLVM compiler framework builder"
authors = [
"Oleksandr Zarudnyi <a.zarudnyy@matterlabs.dev>",
"Anton Baliasnikov <aba@matterlabs.dev>",
"Cyrill Leutwiler <cyrill@parity.io>",
]
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
[[bin]]
name = "revive-llvm"
path = "src/revive_llvm/main.rs"
[lib]
doctest = false
[dependencies]
clap = { workspace = true, features = ["help", "std", "derive"] }
anyhow = { workspace = true }
serde = { workspace = true, features = [ "derive" ] }
toml = { workspace = true }
num_cpus = { workspace = true }
fs_extra = { workspace = true }
path-slash = { workspace = true }
regex = { workspace = true }
downloader = { workspace = true }
tar = { workspace = true }
flate2 = { workspace = true }
env_logger = { workspace = true }
log = { workspace = true }
[dev-dependencies]
assert_cmd = { workspace = true }
assert_fs = { workspace = true }

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