Compare commits

..

1 Commits

Author SHA1 Message Date
xermicus 2913fbc494 use __bswap in favor of LLVM intrinsic
Signed-off-by: xermicus <cyrill@parity.io>
2024-04-25 11:45:28 +02:00
331 changed files with 10306 additions and 20982 deletions
-38
View File
@@ -1,38 +0,0 @@
name: Build revive-debian
on:
workflow_dispatch:
env:
REVIVE_DEBIAN_PACKAGE: revive-debian-x86
DEBIAN_CONTAINER: revive-builder-debian-x86
DEBIAN_CONTAINER_BUILDER: build-debian-builder.sh
DEBIAN_CONTAINER_RUNNER: run-debian-builder.sh
REVIVE_DEBIAN_INSTALL: ${{ github.workspace }}/target/release
REVIVE_DEBIAN_BINARY: resolc
RUST_VERSION: "1.80"
jobs:
build-revive-debian-x86:
name: debian-container-x86
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: build-container
run: |
(cd utils && ./${{ env.DEBIAN_CONTAINER_BUILDER}} --build-arg RUST_VERSION=${{ env.RUST_VERSION}} . )
- name: build-revive-debian
run: |
rustup show
cargo --version
rustup +nightly show
cargo +nightly --version
bash --version
utils/${{ env.DEBIAN_CONTAINER_RUNNER }} utils/build-revive.sh -o ${{ env.REVIVE_DEBIAN_INSTALL}}
- uses: actions/upload-artifact@v4
with:
name: ${{ env.REVIVE_DEBIAN_PACKAGE }}
path: ${{ env.REVIVE_DEBIAN_INSTALL }}/${{ env.REVIVE_DEBIAN_BINARY }}
retention-days: 1
-80
View File
@@ -1,80 +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 }}/js/dist/revive-cjs
EMSCRIPTEN_VERSION: 3.1.64
jobs:
build-revive-wasm:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Cache LLVM build
id: cache-llvm
uses: actions/cache@v3
with:
path: |
llvm18.0-emscripten
# Use a unique key based on LLVM version or configuration files to avoid cache invalidation
key: llvm-build-${{ runner.os }}-${{ hashFiles('clone-llvm.sh', 'emscripten-build-llvm.sh') }}
- name: Install Dependencies
run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build libncurses5
rustup target add wasm32-unknown-emscripten
# Install LLVM required for the compiler runtime, runtime-api and stdlib
curl -sSL --output llvm.tar.xz https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.4/clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04.tar.xz
tar Jxf llvm.tar.xz
mv clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04 llvm18/
echo "$(pwd)/llvm18/bin" >> $GITHUB_PATH
# Install Emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install ${{ env.EMSCRIPTEN_VERSION }}
./emsdk activate ${{ env.EMSCRIPTEN_VERSION }}
- run: |
rustup show
cargo --version
rustup +nightly show
cargo +nightly --version
cmake --version
bash --version
llvm-config --version
- name: Build LLVM
if: steps.cache-llvm.outputs.cache-hit != 'true'
run: |
export EMSDK_ROOT=${PWD}/emsdk
./emscripten-build-llvm.sh
- name: Use Cached LLVM
if: steps.cache-llvm.outputs.cache-hit == 'true'
run: |
echo "Using cached LLVM"
- name: Build revive
run: |
export LLVM_LINK_PREFIX=${PWD}/llvm18.0-emscripten
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 }}/worker.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
retention-days: 1
-55
View File
@@ -1,55 +0,0 @@
name: Build
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
env:
CARGO_TERM_COLOR: always
jobs:
build-ubuntu-x86:
runs-on: ubuntu-latest
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/llvm/llvm-project/releases/download/llvmorg-18.1.4/clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04.tar.xz
tar Jxf llvm.tar.xz
mv clang+llvm-18.1.4-x86_64-linux-gnu-ubuntu-18.04 llvm18/
echo "$(pwd)/llvm18/bin" >> $GITHUB_PATH
- name: Install apt dependencies
run: |
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
sudo apt install -y libtinfo5 ethereum
- 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
+1 -6
View File
@@ -8,12 +8,7 @@
/*.s
/llvm-project
/llvm18.0
/llvm18.0-emscripten
node_modules
artifacts
tmp
package-lock.json
/*.html
/js/src/resolc.*
/js/dist/
/build
package-lock.json
-28
View File
@@ -1,28 +0,0 @@
# Changelog
## Unreleased
## 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
+451 -10034
View File
File diff suppressed because it is too large Load Diff
+11 -42
View File
@@ -2,31 +2,7 @@
resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.1.0-dev.5"
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.80.0"
[workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.5", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.5", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.5", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.5", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.5", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.5", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.5", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.5", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.5", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.5", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.5", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.5", path = "crates/stdlib" }
hex = "0.4"
petgraph = "0.6"
cc = "1.0"
@@ -49,34 +25,27 @@ thiserror = "1.0"
which = "5.0"
path-slash = "0.2"
rayon = "1.8"
clap = { version = "4", default-features = false, features = ["derive"] }
structopt = { version = "0.3", default-features = false }
rand = "0.8"
polkavm-common = "0.14"
polkavm-linker = "0.14"
polkavm-disassembler = "0.14"
polkavm = "0.14"
alloy-primitives = { version = "0.8", features = ["serde"] }
alloy-sol-types = "0.8"
alloy-genesis = "0.3"
alloy-serde = "0.3"
polkavm-common = { branch = "master_byteswap", git = "https://github.com/koute/polkavm.git" }
polkavm-linker = { branch = "master_byteswap", git = "https://github.com/koute/polkavm.git" }
polkavm = { branch = "master_byteswap", git = "https://github.com/koute/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"] }
log = { version = "0.4" }
# polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.1", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "0449b214accd0f0fbf7ea3e8f3a8d8b7f99445e4" }
# 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
+16 -51
View File
@@ -1,18 +1,4 @@
.PHONY: install format test test-solidity test-cli test-integration test-workspace clean docs docs-build
RUSTFLAGS_EMSCRIPTEN := \
-Clink-arg=-sEXPORTED_FUNCTIONS=_main,_free,_malloc \
-Clink-arg=-sNO_INVOKE_RUN \
-Clink-arg=-sEXIT_RUNTIME \
-Clink-arg=-sINITIAL_MEMORY=64MB \
-Clink-arg=-sTOTAL_MEMORY=3GB \
-Clink-arg=-sALLOW_MEMORY_GROWTH \
-Clink-arg=-sEXPORTED_RUNTIME_METHODS=FS,callMain,stringToNewUTF8,cwrap \
-Clink-arg=-sMODULARIZE \
-Clink-arg=-sEXPORT_ES6 \
-Clink-arg=-sEXPORT_NAME=createRevive \
-Clink-arg=--js-library=js/embed/soljson_interface.js \
-Clink-arg=--pre-js=js/embed/pre.js
.PHONY: install test test-solidity test-cli test-integration clean
install: install-bin install-npm
@@ -22,25 +8,7 @@ install-bin:
install-npm:
npm install && npm fund
install-wasm:
RUSTFLAGS='$(RUSTFLAGS_EMSCRIPTEN)' cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features
npm install
npm run build:revive
# install-revive: Build and install to the directory specified in REVIVE_INSTALL_DIR
ifeq ($(origin REVIVE_INSTALL_DIR), undefined)
REVIVE_INSTALL_DIR=`pwd`/release/revive-debian
endif
install-revive:
cargo install --path crates/solidity --root $(REVIVE_INSTALL_DIR)
format:
cargo fmt --all --check
clippy:
cargo clippy --all-features --workspace --tests --benches -- --deny warnings --allow dead_code
test: format clippy test-cli test-workspace
test: install test-integration test-cli test-solidity
test-integration: install-bin
cargo test --package revive-integration
@@ -48,35 +16,32 @@ test-integration: install-bin
test-solidity: install
cargo test --package revive-solidity
test-workspace: install
cargo test --workspace
test-cli: install
npm run test:cli
bench-pvm: install-bin
cargo criterion --bench execute --features bench-pvm-interpreter --message-format=json \
| criterion-table > crates/benchmarks/PVM.md
bench-prepare: install-bin
cargo criterion --bench prepare --features bench-evm,bench-pvm --message-format=json \
| criterion-table > crates/benchmarks/PREPARE.md
bench-evm: install-bin
cargo criterion --bench execute --features bench-evm --message-format=json \
| criterion-table > crates/benchmarks/EVM.md
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
docs: docs-build
mdbook serve --open docs/
bench-quick: install-bin
cargo criterion --all --features bench-evm
bench: install-bin
cargo criterion --all --features bench-evm,bench-pvm --message-format=json \
| criterion-table > crates/benchmarks/BENCHMARKS.md
docs-build:
mdbook test docs/ && mdbook build docs/
clean:
cargo clean ; \
rm -rf node_modules ; \
rm -rf crates/solidity/src/tests/cli-tests/artifacts ; \
cargo uninstall revive-solidity ; \
rm -f package-lock.json ; \
rm -rf js/dist ; \
rm -f js/src/resolc.{wasm,js}
rm -f package-lock.json
+30 -60
View File
@@ -1,68 +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.polakdot.io)
# revive
YUL and EVM assembly 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](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.
To install the `resolc` Solidity frontend executable:
```bash
bash build-llvm.sh
export PATH=${PWD}/llvm18.0/bin:$PATH
make install-bin
resolc --version
```
### LLVM
`revive` requires a build of LLVM 18.1.4 or later including `compiler-rt`. Use the provided [build-llvm.sh](build-llvm.sh) build script to compile a compatible LLVM build locally in `$PWD/llvm18.0` (don't forget to add that to `$PATH` afterwards).
### Cross-compilation to WASM
Cross-compiles the Revive compiler to WASM for running it in a Node.js or browser environment.
Install [emscripten](https://emscripten.org/docs/getting_started/downloads.html). Tested on version 3.1.64.
To build resolc.js execute:
```bash
bash build-llvm.sh
export PATH=${PWD}/llvm18.0/bin:$PATH
export EMSDK_ROOT=<PATH_TO_EMSCRIPTEN_SDK>
bash emscripten-build-llvm.sh
source $EMSDK_ROOT/emsdk_env.sh
export LLVM_LINK_PREFIX=${PWD}/llvm18.0-emscripten
export PATH=$PATH:$PWD/llvm18.0-emscripten/bin/
make install-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. Manually trigger the `Build revive-debian` action
4. Create a __pre-release__ from the tag and manually upload the build artifact generated by the action
5. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
-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.
+44 -62
View File
@@ -3,23 +3,21 @@
set -euo pipefail
INSTALL_DIR="${PWD}/llvm18.0"
mkdir -p ${INSTALL_DIR}
mkdir -p $INSTALL_DIR
# Build LLVM, clang
LLVM_SRC_PREFIX=${PWD}/llvm-project
LLVM_SRC_DIR=${LLVM_SRC_PREFIX}/llvm
LLVM_BUILD_DIR=${PWD}/build/llvm
./clone-llvm.sh "${LLVM_SRC_PREFIX}"
if [ ! -d ${LLVM_BUILD_DIR} ] ; then
mkdir -p ${LLVM_BUILD_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
cmake -G Ninja \
-S ${LLVM_SRC_DIR} \
-B ${LLVM_BUILD_DIR} \
-DLLVM_ENABLE_ASSERTIONS=On \
# 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 \
@@ -27,61 +25,45 @@ cmake -G Ninja \
-DLLVM_TARGETS_TO_BUILD='RISCV' \
-DLLVM_ENABLE_ZSTD=Off \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
-DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \
../llvm
ninja
ninja install
cmake --build ${LLVM_BUILD_DIR}
cmake --install ${LLVM_BUILD_DIR}
# Build compiler builtins
COMPILER_RT_SRC_DIR=${LLVM_SRC_PREFIX}/compiler-rt
COMPILER_RT_BUILD_DIR=${PWD}/build/compiler-rt
if [ ! -d ${COMPILER_RT_BUILD_DIR} ] ; then
mkdir -p ${COMPILER_RT_BUILD_DIR}
fi
cd ../compiler-rt
mkdir -p build
cd build
build_compiler_rt() {
case "$1" in
64) TARGET_ABI=lp64e ;;
32) TARGET_ABI=ilp32e ;;
*) exit -1
esac
CFLAGS="--target=riscv${1} -march=rv${1}em -mabi=${TARGET_ABI} -mcpu=generic-rv${1} -nostdlib -nodefaultlibs"
CFLAGS="--target=riscv32 -march=rv32em -mabi=ilp32e -nostdlib -nodefaultlibs -mcpu=generic-rv32"
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="riscv32" \
-DCMAKE_ASM_COMPILER_TARGET="riscv32" \
-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 \
..
cmake -G Ninja \
-S ${COMPILER_RT_SRC_DIR} \
-B ${COMPILER_RT_BUILD_DIR} \
-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=riscv${1} \
-DCMAKE_ASM_COMPILER_TARGET=riscv${1} \
-DCMAKE_CXX_COMPILER_TARGET=riscv${1} \
-DCMAKE_C_TARGET_BITS=riscv${1} \
-DCMAKE_ASM_TARGET_BITS=riscv${1} \
-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}" \
-DCMAKE_SYSTEM_NAME=unknown \
-DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON
cmake --build ${COMPILER_RT_BUILD_DIR}
cmake --install ${COMPILER_RT_BUILD_DIR}
}
ninja
ninja install
build_compiler_rt 32
build_compiler_rt 64
echo ""
echo "success"
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash
set -euo pipefail
# Default directory for cloning the llvm-project repository
DEFAULT_DIR="llvm-project"
# Check if a directory argument is provided
if [ $# -eq 1 ]; then
DIR=$1
else
DIR=$DEFAULT_DIR
fi
# Clone LLVM 18 (any revision after commit bd32aaa is supposed to work)
if [ ! -d "${DIR}" ]; then
git clone --depth 1 --branch release/18.x https://github.com/llvm/llvm-project.git "${DIR}"
fi
-73
View File
@@ -1,73 +0,0 @@
# Benchmarks
## Table of Contents
- [Benchmark Results](#benchmark-results)
- [Baseline](#baseline)
- [OddPorduct](#oddporduct)
- [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative)
- [FibonacciBinet](#fibonaccibinet)
- [SHA1](#sha1)
## Benchmark Results
### Baseline
| | `EVM` | `PVMInterpreter` |
|:--------|:------------------------|:-------------------------------- |
| **`0`** | `5.97 us` (✅ **1.00x**) | `27.04 us` (❌ *4.53x slower*) |
### OddPorduct
| | `EVM` | `PVMInterpreter` |
|:-------------|:--------------------------|:-------------------------------- |
| **`10000`** | `4.26 ms` (✅ **1.00x**) | `2.88 ms` (✅ **1.48x faster**) |
| **`100000`** | `42.37 ms` (✅ **1.00x**) | `28.35 ms` (✅ **1.49x faster**) |
| **`300000`** | `127.88 ms` (✅ **1.00x**) | `88.43 ms` (✅ **1.45x faster**) |
### TriangleNumber
| | `EVM` | `PVMInterpreter` |
|:-------------|:--------------------------|:-------------------------------- |
| **`10000`** | `2.85 ms` (✅ **1.00x**) | `2.37 ms` (✅ **1.20x faster**) |
| **`100000`** | `27.85 ms` (✅ **1.00x**) | `23.01 ms` (✅ **1.21x faster**) |
| **`360000`** | `103.01 ms` (✅ **1.00x**) | `83.66 ms` (✅ **1.23x faster**) |
### FibonacciRecursive
| | `EVM` | `PVMInterpreter` |
|:---------|:--------------------------|:--------------------------------- |
| **`12`** | `195.19 us` (✅ **1.00x**) | `333.53 us` (❌ *1.71x slower*) |
| **`16`** | `1.22 ms` (✅ **1.00x**) | `1.97 ms` (❌ *1.62x slower*) |
| **`20`** | `8.14 ms` (✅ **1.00x**) | `13.20 ms` (❌ *1.62x slower*) |
| **`24`** | `55.09 ms` (✅ **1.00x**) | `88.56 ms` (❌ *1.61x slower*) |
### FibonacciIterative
| | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:--------------------------------- |
| **`64`** | `33.39 us` (✅ **1.00x**) | `86.02 us` (❌ *2.58x slower*) |
| **`128`** | `52.91 us` (✅ **1.00x**) | `126.38 us` (❌ *2.39x slower*) |
| **`256`** | `82.33 us` (✅ **1.00x**) | `208.74 us` (❌ *2.54x slower*) |
### FibonacciBinet
| | `EVM` | `PVMInterpreter` |
|:----------|:-------------------------|:--------------------------------- |
| **`64`** | `32.29 us` (✅ **1.00x**) | `161.75 us` (❌ *5.01x slower*) |
| **`128`** | `36.02 us` (✅ **1.00x**) | `172.59 us` (❌ *4.79x slower*) |
| **`256`** | `41.21 us` (✅ **1.00x**) | `185.30 us` (❌ *4.50x slower*) |
### SHA1
| | `EVM` | `PVMInterpreter` |
|:----------|:--------------------------|:--------------------------------- |
| **`1`** | `160.17 us` (✅ **1.00x**) | `403.46 us` (❌ *2.52x slower*) |
| **`64`** | `286.69 us` (✅ **1.00x**) | `479.79 us` (❌ *1.67x slower*) |
| **`512`** | `1.18 ms` (✅ **1.00x**) | `1.37 ms` (❌ *1.16x 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,
{
return 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))
}
+3 -8
View File
@@ -1,14 +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"
[features]
riscv-64 = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+6 -16
View File
@@ -1,11 +1,7 @@
use std::{env, fs, io::Read, path::Path, process::Command};
#[cfg(not(feature = "riscv-64"))]
pub const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv32.a";
#[cfg(feature = "riscv-64")]
pub const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
fn main() {
let lib = "libclang_rt.builtins-riscv32.a";
let mut llvm_lib_dir = String::new();
Command::new("llvm-config")
@@ -17,24 +13,18 @@ fn main() {
.read_to_string(&mut llvm_lib_dir)
.expect("llvm-config output should be utf8");
let mut lib_path = std::path::PathBuf::from(llvm_lib_dir.trim())
let lib_path = std::path::PathBuf::from(llvm_lib_dir.trim())
.join("linux")
.join(BUILTINS_ARCHIVE_FILE);
if !lib_path.exists() {
lib_path = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join(BUILTINS_ARCHIVE_FILE);
}
let archive = fs::read(lib_path).expect("clang builtins not found");
.join(lib);
let archive = fs::read(lib_path).expect("clang builtins for riscv32 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");
Binary file not shown.
Binary file not shown.
+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 -7
View File
@@ -1,21 +1,17 @@
[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]
doctest = false
[features]
riscv-64 = []
[dependencies]
anyhow = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+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 = 4;
/// 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;
+9 -7
View File
@@ -1,4 +1,6 @@
//!
//! The file extensions.
//!
/// The manifest file extension.
pub static EXTENSION_MANIFEST: &str = "toml";
@@ -30,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 }
-40
View File
@@ -1,40 +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
},
"coinbase": "0x0000000000000000000000000000000000000000",
"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 = stdout.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"] }
+13
View File
@@ -0,0 +1,13 @@
target datalayout = "e-m:e-p:32:32-i64:64-n32-S128"
target triple = "riscv32-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,+m",
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());
}
}
+7 -16
View File
@@ -1,11 +1,9 @@
[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 }
@@ -13,17 +11,10 @@ alloy-primitives = { workspace = true }
alloy-sol-types = { workspace = true }
hex = { workspace = true }
env_logger = { workspace = true }
log = { workspace = true }
serde_json = { workspace = true }
revive-solidity = { workspace = true }
revive-differential = { workspace = true }
revive-llvm-context = { workspace = true }
revive-common = { 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 }
-10
View File
@@ -1,10 +0,0 @@
{
"Baseline": 961,
"Computation": 4024,
"DivisionArithmetics": 31789,
"ERC20": 44214,
"Events": 1737,
"FibonacciIterative": 2929,
"Flipper": 3402,
"SHA1": 26003
}
+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);
}
}
+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"}();
}
}
-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,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 {
-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;
}
}
-54
View File
@@ -1,54 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Transfer"
}
},
"value": 11
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d0500000000000000000000000003030303030303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000001"
}
}
]
}
*/
contract Transfer {
constructor() payable {
transfer_self(msg.value);
}
function address_self() internal view returns (address payable) {
return payable(address(this));
}
function transfer_self(uint _amount) public payable {
transfer_to(address_self(), _amount);
}
function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount);
}
}
+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)
}
}
}
+75 -264
View File
@@ -1,308 +1,119 @@
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 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 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(
address owner,
address spender
) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
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 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(),
}
}
}
#[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 super::Contract;
#[test]
fn codesize() {
let path = "codesize.json";
let existing = File::open(path)
.map(|file| {
BTreeMap::<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
.as_ref()
.and_then(|map| map.get(contract.name))
.filter(|old| **old != 0)
.map(|old| {
let old = *old as f32;
let p = (contract_length as f32 - old) / old * 100.0;
format!("({p}% change from {old} bytes)",)
})
.unwrap_or_default();
println!("{}: {contract_length} bytes {size_change}", contract.name);
(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}"));
}
}
+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();
}
}
+257 -452
View File
@@ -1,462 +1,267 @@
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 code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol"));
let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec());
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);
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!(balance, "Value", "Value.sol");
test_spec!(create, "CreateB", "Create.sol");
test_spec!(call, "Caller", "Call.sol");
test_spec!(transfer, "Transfer", "Transfer.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");
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,
pipeline: 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,
pipeline: 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);
}
/*
// These test were implement for the mock-runtime and need to be ported yet.
#[test]
fn create2_failure() {
let mut state = State::default();
let contract_a = Contract::create_a();
state.upload_code(&contract_a.pvm_runtime);
let contract = Contract::create_b();
let (state, output) = state
.transaction()
.with_default_account(&contract.pvm_runtime)
.calldata(contract.calldata.clone())
.call();
assert_eq!(output.flags, ReturnFlags::Success);
// The address already exists, which should cause the contract to revert
let (_, output) = state
.transaction()
.with_default_account(&contract.pvm_runtime)
.calldata(contract.calldata)
.call();
assert_eq!(output.flags, ReturnFlags::Revert);
fn erc20() {
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
}
#[test]
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);
}
*/
+5 -10
View File
@@ -1,14 +1,9 @@
[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"
[features]
riscv-64 = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
inkwell = { workspace = true }
@@ -18,5 +13,5 @@ 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 -16
View File
@@ -8,16 +8,6 @@ SECTIONS {
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
}"#;
#[cfg(not(feature = "riscv-64"))]
const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv32.a";
#[cfg(feature = "riscv-64")]
const BUILTINS_ARCHIVE_FILE: &str = "libclang_rt.builtins-riscv64.a";
#[cfg(not(feature = "riscv-64"))]
const BUILTINS_LIB_NAME: &str = "clang_rt.builtins-riscv32";
#[cfg(feature = "riscv-64")]
const BUILTINS_LIB_NAME: &str = "clang_rt.builtins-riscv64";
fn invoke_lld(cmd_args: &[&str]) -> bool {
let c_strings = cmd_args
.iter()
@@ -29,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))
}
@@ -43,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-riscv32.a");
fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?;
@@ -68,7 +58,7 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
"--library-path",
dir.path().to_str().expect("should be utf8"),
"--library",
BUILTINS_LIB_NAME,
"clang_rt.builtins-riscv32",
linker_script_path.to_str().expect("should be utf8"),
object_path.to_str().expect("should be utf8"),
"-o",
@@ -79,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 -6
View File
@@ -1,12 +1,11 @@
[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 }
+8 -79
View File
@@ -1,19 +1,5 @@
use std::{
env,
path::{Path, PathBuf},
};
const LLVM_LINK_PREFIX: &str = "LLVM_LINK_PREFIX";
fn locate_llvm_config() -> PathBuf {
let prefix = env::var_os(LLVM_LINK_PREFIX)
.map(|p| PathBuf::from(p).join("bin"))
.unwrap_or_default();
prefix.join("llvm-config")
}
fn llvm_config(llvm_config_path: &Path, arg: &str) -> String {
let output = std::process::Command::new(llvm_config_path)
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"));
@@ -22,11 +8,8 @@ fn llvm_config(llvm_config_path: &Path, arg: &str) -> String {
.unwrap_or_else(|_| panic!("output of `llvm-config {arg}` should be utf8"))
}
fn set_rustc_link_flags(llvm_config_path: &Path) {
println!(
"cargo:rustc-link-search=native={}",
llvm_config(llvm_config_path, "--libdir")
);
fn set_rustc_link_flags() {
println!("cargo:rustc-link-search=native={}", llvm_config("--libdir"));
for lib in [
"lldELF",
@@ -39,70 +22,16 @@ fn set_rustc_link_flags(llvm_config_path: &Path) {
"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();
if target_os == "linux" {
println!("cargo:rustc-link-lib=dylib=stdc++");
println!("cargo:rustc-link-lib=tinfo");
}
#[cfg(target_os = "linux")]
println!("cargo:rustc-link-lib=dylib=stdc++");
}
fn main() {
println!("cargo:rerun-if-env-changed={}", LLVM_LINK_PREFIX);
let llvm_config_path = locate_llvm_config();
llvm_config(&llvm_config_path, "--cxxflags")
llvm_config("--cxxflags")
.split_whitespace()
.fold(&mut cc::Build::new(), |builder, flag| builder.flag(flag))
.flag("-Wno-unused-parameter")
@@ -110,7 +39,7 @@ fn main() {
.file("src/linker.cpp")
.compile("liblinker.a");
set_rustc_link_flags(&llvm_config_path);
set_rustc_link_flags();
println!("cargo:rerun-if-changed=build.rs");
}
+13 -22
View File
@@ -1,27 +1,17 @@
[package]
name = "revive-llvm-context"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
name = "era-compiler-llvm-context"
version = "1.4.1"
authors = [
"Oleksandr Zarudnyi <a.zarudnyy@matterlabs.dev>",
"Cyrill Leutwiler <cyrill@parity.io>",
]
description = "Shared front end code of the revive PolkaVM compilers"
license = "MIT OR Apache-2.0"
edition = "2021"
description = "Shared front end code of the EraVM compilers"
[lib]
doctest = false
[features]
riscv-zbb = []
riscv-64 = [
"revive-linker/riscv-64",
"revive-builtins/riscv-64",
"revive-runtime-api/riscv-64",
"revive-common/riscv-64",
]
[dependencies]
anyhow = { workspace = true }
semver = { workspace = true }
@@ -35,11 +25,12 @@ sha2 = { workspace = true }
sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true }
polkavm-disassembler = { workspace = true }
polkavm-common = { workspace = true }
revive-common = { workspace = true }
revive-runtime-api = { workspace = true }
revive-linker = { workspace = true }
revive-builtins = { workspace = true }
revive-stdlib = { workspace = true }
zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs", branch = "v1.4.1" }
revive-common = { path = "../common" }
pallet-contracts-pvm-llapi = { path = "../pallet-contracts-pvm-llapi" }
revive-linker = { path = "../linker" }
revive-builtins = { path = "../builtins" }
revive-stdlib = { path = "../stdlib" }
revive-extensions = { path = "../extensions" }
@@ -1,6 +1,10 @@
//!
//! The debug IR type.
//!
///
/// The debug IR type.
///
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -11,29 +15,26 @@ pub enum IRType {
EVMLA,
/// Whether to dump the Ethereal IR code.
EthIR,
/// Whether to dump the Vyper LLL IR code.
LLL,
/// Whether to dump the LLVM IR code.
LLVM,
/// Whether to dump the assembly code.
Assembly,
/// Whether to dump the ELF shared object
SO,
/// Whether to jump JSON
#[cfg(debug_assertions)]
JSON,
}
impl IRType {
///
/// Returns the file extension for the specified IR.
///
pub fn file_extension(&self) -> &'static str {
match self {
Self::Yul => revive_common::EXTENSION_YUL,
Self::EthIR => revive_common::EXTENSION_ETHIR,
Self::EVMLA => revive_common::EXTENSION_EVMLA,
Self::LLL => revive_common::EXTENSION_LLL,
Self::LLVM => revive_common::EXTENSION_LLVM_SOURCE,
Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY,
#[cfg(debug_assertions)]
Self::JSON => revive_common::EXTENSION_JSON,
Self::SO => revive_common::EXTENSION_SHARED_OBJECT,
Self::Assembly => revive_common::EXTENSION_ERAVM_ASSEMBLY,
}
}
}
+61 -78
View File
@@ -1,4 +1,6 @@
//!
//! The debug configuration.
//!
pub mod ir_type;
@@ -9,141 +11,122 @@ use serde::Serialize;
use self::ir_type::IRType;
///
/// The debug configuration.
///
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct DebugConfig {
/// The directory to dump the IRs to.
pub output_directory: Option<PathBuf>,
/// Whether debug info should be emitted.
pub emit_debug_info: bool,
pub output_directory: PathBuf,
}
impl DebugConfig {
///
/// A shortcut constructor.
pub const fn new(output_directory: Option<PathBuf>, emit_debug_info: bool) -> Self {
Self {
output_directory,
emit_debug_info,
}
///
pub fn new(output_directory: PathBuf) -> Self {
Self { output_directory }
}
///
/// Dumps the Yul IR.
///
pub fn dump_yul(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the EVM legacy assembly IR.
///
pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the Ethereal IR.
///
pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the LLL IR.
///
pub fn dump_lll(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::LLL);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the unoptimized LLVM IR.
///
pub fn dump_llvm_ir_unoptimized(
&self,
contract_path: &str,
module: &inkwell::module::Module,
) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let llvm_code = module.print_to_string().to_string();
let llvm_code = module.print_to_string().to_string();
let mut file_path = output_directory.to_owned();
let full_file_name =
Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
}
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
Ok(())
}
///
/// Dumps the optimized LLVM IR.
///
pub fn dump_llvm_ir_optimized(
&self,
contract_path: &str,
module: &inkwell::module::Module,
) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let llvm_code = module.print_to_string().to_string();
let llvm_code = module.print_to_string().to_string();
let mut file_path = output_directory.to_owned();
let full_file_name =
Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
}
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
Ok(())
}
///
/// Dumps the assembly.
///
pub fn dump_assembly(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(())
}
/// Dumps the code object.
pub fn dump_object(&self, contract_path: &str, code: &[u8]) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::SO);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(())
}
/// Dumps the stage output as a json file suitable for use with --recursive-process
#[cfg(debug_assertions)]
pub fn dump_stage_output(
&self,
contract_path: &str,
contract_suffix: Option<&str>,
stage_json: &Vec<u8>,
) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, contract_suffix, IRType::JSON);
file_path.push(full_file_name);
std::fs::write(file_path, stage_json)?;
}
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Creates a full file name, given the contract full path, suffix, and extension.
///
fn full_file_name(contract_path: &str, suffix: Option<&str>, ir_type: IRType) -> String {
let mut full_file_name = contract_path.replace('/', "_").replace(':', ".");
if let Some(suffix) = suffix {
+75
View File
@@ -0,0 +1,75 @@
//!
//! The LLVM context constants.
//!
/// The LLVM framework version.
pub const LLVM_VERSION: semver::Version = semver::Version::new(18, 1, 4);
/// The EraVM version.
pub const ZKEVM_VERSION: semver::Version = semver::Version::new(1, 3, 2);
/// The register width sized type
pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
/// The heap memory pointer pointer global variable name.
pub static GLOBAL_HEAP_MEMORY_POINTER: &str = "memory_pointer";
/// The calldata pointer global variable name.
pub static GLOBAL_CALLDATA_POINTER: &str = "ptr_calldata";
/// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The return data pointer global variable name.
pub static GLOBAL_RETURN_DATA_POINTER: &str = "ptr_return_data";
/// The return data size pointer global variable name.
pub static GLOBAL_RETURN_DATA_SIZE: &str = "returndatasize";
/// The call flags global variable name.
pub static GLOBAL_CALL_FLAGS: &str = "call_flags";
/// The extra ABI data global variable name.
pub static GLOBAL_EXTRA_ABI_DATA: &str = "extra_abi_data";
/// The active pointer global variable name.
pub static GLOBAL_ACTIVE_POINTER: &str = "ptr_active";
/// The constant array global variable name prefix.
pub static GLOBAL_CONST_ARRAY_PREFIX: &str = "const_array_";
/// The global verbatim getter identifier prefix.
pub static GLOBAL_VERBATIM_GETTER_PREFIX: &str = "get_global::";
/// The external call data offset in the auxiliary heap.
pub const HEAP_AUX_OFFSET_EXTERNAL_CALL: u64 = 0;
/// The constructor return data offset in the auxiliary heap.
pub const HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA: u64 =
8 * (revive_common::BYTE_LENGTH_FIELD as u64);
/// The number of the extra ABI data arguments.
pub const EXTRA_ABI_DATA_SIZE: usize = 0;
/// The `create` method deployer signature.
pub static DEPLOYER_SIGNATURE_CREATE: &str = "create(bytes32,bytes32,bytes)";
/// The `create2` method deployer signature.
pub static DEPLOYER_SIGNATURE_CREATE2: &str = "create2(bytes32,bytes32,bytes)";
/// The absence of system call bit.
pub const NO_SYSTEM_CALL_BIT: bool = false;
/// The system call bit.
pub const SYSTEM_CALL_BIT: bool = true;
///
/// The deployer call header size that consists of:
/// - selector (4 bytes)
/// - salt (32 bytes)
/// - bytecode hash (32 bytes)
/// - constructor arguments offset (32 bytes)
/// - constructor arguments length (32 bytes)
///
pub const DEPLOYER_CALL_HEADER_SIZE: usize =
revive_common::BYTE_LENGTH_X32 + (revive_common::BYTE_LENGTH_FIELD * 4);
@@ -1,6 +1,10 @@
//!
//! The address space aliases.
//!
///
/// The address space aliases.
///
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressSpace {
/// The stack memory.
@@ -8,7 +12,13 @@ pub enum AddressSpace {
Stack,
/// The heap memory.
Heap,
/// The auxiliary heap memory.
HeapAuxiliary,
/// The generic memory page.
Generic,
/// The code area.
Code,
/// The storage.
Storage,
/// The transient storage.
TransientStorage,
@@ -19,6 +29,9 @@ impl From<AddressSpace> for inkwell::AddressSpace {
match value {
AddressSpace::Stack => Self::from(0),
AddressSpace::Heap => Self::from(1),
AddressSpace::HeapAuxiliary => Self::from(2),
AddressSpace::Generic => Self::from(3),
AddressSpace::Code => Self::from(4),
AddressSpace::Storage => Self::from(5),
AddressSpace::TransientStorage => Self::from(6),
}
@@ -1,6 +1,10 @@
//!
//! The LLVM argument with metadata.
//!
///
/// The LLVM argument with metadata.
///
#[derive(Debug, Clone)]
pub struct Argument<'ctx> {
/// The actual LLVM operand.
@@ -18,7 +22,9 @@ impl<'ctx> Argument<'ctx> {
/// The calldata length argument index.
pub const ARGUMENT_INDEX_CALLDATA_LENGTH: usize = 1;
///
/// A shortcut constructor.
///
pub fn new(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self {
value,
@@ -27,7 +33,9 @@ impl<'ctx> Argument<'ctx> {
}
}
///
/// A shortcut constructor.
///
pub fn new_with_original(
value: inkwell::values::BasicValueEnum<'ctx>,
original: String,
@@ -39,7 +47,9 @@ impl<'ctx> Argument<'ctx> {
}
}
///
/// A shortcut constructor.
///
pub fn new_with_constant(
value: inkwell::values::BasicValueEnum<'ctx>,
constant: num::BigUint,
@@ -51,7 +61,9 @@ impl<'ctx> Argument<'ctx> {
}
}
///
/// Returns the inner LLVM value.
///
pub fn to_llvm(&self) -> inkwell::values::BasicValueEnum<'ctx> {
self.value
}
@@ -4,8 +4,11 @@ use serde::Deserialize;
use serde::Serialize;
/// The LLVM attribute.
///
/// In order to check the real order in a new major version of LLVM, find the `Attributes.inc` file
/// inside of the LLVM build directory. This order is actually generated during the building.
///
/// FIXME: Generate this in build.rs?
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Attribute {
Unused = 0,
@@ -81,7 +84,7 @@ pub enum Attribute {
Writable = 70,
WriteOnly = 71,
ZExt = 72,
// LastEnumAttr = 72,
//LastEnumAttr = 72,
// FirstTypeAttr = 73,
ByRef = 73,
ByVal = 74,
@@ -116,7 +119,12 @@ impl TryFrom<&str> for Attribute {
"OptimizeForSize" => Ok(Attribute::OptimizeForSize),
"NoInline" => Ok(Attribute::NoInline),
"WillReturn" => Ok(Attribute::WillReturn),
"WriteOnly" => Ok(Attribute::WriteOnly),
"ReadNone" => Ok(Attribute::ReadNone),
"ReadOnly" => Ok(Attribute::ReadOnly),
"NoReturn" => Ok(Attribute::NoReturn),
// FIXME: Not in Attributes.inc
//"InaccessibleMemOnly" => Ok(Attribute::InaccessibleMemOnly),
"MustProgress" => Ok(Attribute::MustProgress),
_ => Err(value.to_owned()),
}
@@ -1,30 +1,36 @@
//!
//! The LLVM module build.
//!
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
///
/// The LLVM module build.
///
#[derive(Debug, Serialize, Deserialize)]
pub struct Build {
/// The PolkaVM text assembly.
/// The EraVM text assembly.
pub assembly_text: String,
/// The metadata hash.
pub metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]>,
/// The PolkaVM binary bytecode.
pub metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_FIELD]>,
/// The EraVM binary bytecode.
pub bytecode: Vec<u8>,
/// The PolkaVM bytecode hash.
/// The EraVM bytecode hash.
pub bytecode_hash: String,
/// The hash-to-full-path mapping of the contract factory dependencies.
pub factory_dependencies: BTreeMap<String, String>,
}
impl Build {
///
/// A shortcut constructor.
///
pub fn new(
assembly_text: String,
metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]>,
metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_FIELD]>,
bytecode: Vec<u8>,
bytecode_hash: String,
) -> Self {
@@ -1,8 +1,13 @@
//!
//! The contract code types.
//!
///
/// The contract code types (deploy and runtime).
///
/// They do not represent any entities in the final bytecode, but this separation is always present
/// in the IRs used for translation to the EVM bytecode.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CodeType {
/// The deploy code.
@@ -0,0 +1,109 @@
//!
//! The LLVM debug information.
//!
use inkwell::debug_info::AsDIScope;
use num::Zero;
///
/// The LLVM debug information.
///
pub struct DebugInfo<'ctx> {
/// The compile unit.
compile_unit: inkwell::debug_info::DICompileUnit<'ctx>,
/// The debug info builder.
builder: inkwell::debug_info::DebugInfoBuilder<'ctx>,
}
impl<'ctx> DebugInfo<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(module: &inkwell::module::Module<'ctx>) -> Self {
let (builder, compile_unit) = module.create_debug_info_builder(
true,
inkwell::debug_info::DWARFSourceLanguage::C,
module.get_name().to_string_lossy().as_ref(),
"",
"",
false,
"",
0,
"",
inkwell::debug_info::DWARFEmissionKind::Full,
0,
false,
false,
"",
"",
);
Self {
compile_unit,
builder,
}
}
///
/// Creates a function info.
///
pub fn create_function(
&self,
name: &str,
) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> {
let subroutine_type = self.builder.create_subroutine_type(
self.compile_unit.get_file(),
Some(self.create_type(era_compiler_common::BIT_LENGTH_FIELD)?),
&[],
inkwell::debug_info::DIFlags::zero(),
);
let function = self.builder.create_function(
self.compile_unit.get_file().as_debug_info_scope(),
name,
None,
self.compile_unit.get_file(),
42,
subroutine_type,
true,
false,
1,
inkwell::debug_info::DIFlags::zero(),
false,
);
self.builder.create_lexical_block(
function.as_debug_info_scope(),
self.compile_unit.get_file(),
1,
1,
);
Ok(function)
}
///
/// Creates a primitive type info.
///
pub fn create_type(
&self,
bit_length: usize,
) -> anyhow::Result<inkwell::debug_info::DIType<'ctx>> {
self.builder
.create_basic_type(
"U256",
bit_length as u64,
0,
inkwell::debug_info::DIFlags::zero(),
)
.map(|basic_type| basic_type.as_type())
.map_err(|error| anyhow::anyhow!("Debug info error: {}", error))
}
///
/// Finalizes the builder.
///
pub fn finalize(&self) {
self.builder.finalize();
}
}
@@ -1,9 +1,14 @@
//!
//! The LLVM IR generator EVM legacy assembly data.
//!
use crate::polkavm::context::argument::Argument;
use crate::eravm::context::argument::Argument;
///
/// The LLVM IR generator EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone)]
pub struct EVMLAData<'ctx> {
/// The Solidity compiler version.
@@ -17,7 +22,9 @@ impl<'ctx> EVMLAData<'ctx> {
/// The default stack size.
pub const DEFAULT_STACK_SIZE: usize = 64;
///
/// A shortcut constructor.
///
pub fn new(version: semver::Version) -> Self {
Self {
version,
@@ -1,9 +1,14 @@
//!
//! The LLVM IR generator function block key.
//!
use crate::polkavm::context::code_type::CodeType;
use crate::eravm::context::code_type::CodeType;
///
/// The LLVM IR generator function block key.
///
/// Is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
/// The block code type.
@@ -13,7 +18,9 @@ pub struct Key {
}
impl Key {
///
/// A shortcut constructor.
///
pub fn new(code_type: CodeType, tag: num::BigUint) -> Self {
Self { code_type, tag }
}
@@ -1,9 +1,14 @@
//!
//! The LLVM function block EVM legacy assembly data.
//!
pub mod key;
///
/// The LLVM function block EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone)]
pub struct EVMLAData {
/// The initial hashes of the allowed stack states.
@@ -11,7 +16,9 @@ pub struct EVMLAData {
}
impl EVMLAData {
///
/// A shortcut constructor.
///
pub fn new(stack_hashes: Vec<md5::Digest>) -> Self {
Self { stack_hashes }
}
@@ -1,10 +1,14 @@
//!
//! The LLVM IR generator function block.
//!
pub mod evmla_data;
use self::evmla_data::EVMLAData;
///
/// The LLVM IR generator function block.
///
#[derive(Debug, Clone)]
pub struct Block<'ctx> {
/// The inner block.
@@ -14,7 +18,9 @@ pub struct Block<'ctx> {
}
impl<'ctx> Block<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(inner: inkwell::basic_block::BasicBlock<'ctx>) -> Self {
Self {
inner,
@@ -22,28 +28,38 @@ impl<'ctx> Block<'ctx> {
}
}
///
/// Sets the EVM legacy assembly data.
///
pub fn set_evmla_data(&mut self, data: EVMLAData) {
self.evmla_data = Some(data);
}
///
/// The LLVM object reference.
///
pub fn inner(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.inner
}
///
/// Returns the EVM data reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evm(&self) -> &EVMLAData {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
///
/// Returns the EVM data mutable reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evm_mut(&mut self) -> &mut EVMLAData {
self.evmla_data
.as_mut()
@@ -1,6 +1,10 @@
//!
//! The LLVM function declaration.
//!
///
/// The LLVM function declaration.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Declaration<'ctx> {
/// The function type.
@@ -10,15 +14,13 @@ pub struct Declaration<'ctx> {
}
impl<'ctx> Declaration<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(
r#type: inkwell::types::FunctionType<'ctx>,
value: inkwell::values::FunctionValue<'ctx>,
) -> Self {
Self { r#type, value }
}
pub fn function_value(&self) -> inkwell::values::FunctionValue<'ctx> {
self.value
}
}

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