Compare commits

..

40 Commits

Author SHA1 Message Date
Sebastian Miasojed 3c1a635dc3 Apply comment 2024-08-29 16:00:12 +02:00
Sebastian Miasojed 8d83f6d177 Cleanup 2024-08-29 15:31:52 +02:00
Sebastian Miasojed 00019eeec4 Add resolc docker 2024-08-29 13:57:54 +02:00
Sebastian Miasojed 33cbfe1da5 Add --license arg 2024-08-29 10:43:08 +02:00
Cyrill Leutwiler d763e30b8f Simplify test case declaration (#36)
Signed-off-by: xermicus <cyrill@parity.io>
2024-08-26 15:15:33 +02:00
xermicus 5d742d150d tidy up workspace
Signed-off-by: xermicus <cyrill@parity.io>
2024-08-24 16:09:07 +02:00
Cyrill Leutwiler 7844bbb604 add runner crate (#34)
Signed-off-by: xermicus <cyrill@parity.io>
Co-authored-by: xermicus <cyrill@parity.io>
Co-authored-by: pgherveou <pgherveou@gmail.com>
2024-08-24 03:20:52 +02:00
xermicus 0903718f07 add compiler helpers to solidity test utils
Signed-off-by: xermicus <cyrill@parity.io>
2024-08-24 02:09:56 +02:00
Cyrill Leutwiler 880305dbfb replace deprecated structopt crate with clap (#33)
Signed-off-by: xermicus <cyrill@parity.io>
2024-08-23 18:25:08 +02:00
wpt967 bb4a4dddde [solidity,llvm-context] Improve support for debugging the compiler (#32)
Add option --recursive-process-input <filename> for use with
--recursive-process to specify the name of a file to use instead of
reading from stdin.

If --debug-output-dir is set, dump the file passed to the recursive
invocation of the compiler as a JSON file suitable for use with
--recursive-process-input.

These changes are intended to support debugging the compiler and are
only available with DEBUG builds.
2024-08-23 18:18:07 +02:00
Cyrill Leutwiler 184d40d377 Upgrade inkwell (#31)
Signed-off-by: xermicus <cyrill@parity.io>
2024-08-19 18:40:35 +02:00
wpt967 bd89ebc45a Put llvm and compiler-rt build directories in one place. (#30)
Adjust the llvm build script to put the llvm and compiler-rt build
directories outside the llvm-project source directory. A new build
directory 'build' at the toplevel of the revive directory is used
instead. LLVM is built into 'build/llvm' and compiler-rt into
'build/compiler-rt'.

Adjust .gitignore to ignore the contents of the build directory.

This is intended to keep the build artifacts separate from the upstream
sources and any changes made as part of the revive work.
2024-08-12 16:35:19 +02:00
xermicus 44bc7b94b2 typo
Signed-off-by: xermicus <cyrill@parity.io>
2024-08-05 18:27:38 +02:00
xermicus b002382d76 llvm build script: build compiler-rt for 64bit
Signed-off-by: xermicus <cyrill@parity.io>
2024-07-19 00:06:53 +02:00
xermicus e22eebabad update README.md
Signed-off-by: xermicus <cyrill@parity.io>
2024-07-19 00:06:48 +02:00
dependabot[bot] 16a0cc46e6 Bump braces from 3.0.2 to 3.0.3 in /crates/solidity/src/tests/cli-tests (#25)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 18:07:10 +02:00
xermicus 6834751522 support riscv64 target
Signed-off-by: xermicus <cyrill@parity.io>
2024-07-09 17:57:58 +02:00
xermicus a4c4ad55dc remove extensions crate
Signed-off-by: xermicus <cyrill@parity.io>
2024-06-18 16:08:35 +02:00
xermicus 0d39b289cb init mdbook
Signed-off-by: xermicus <cyrill@parity.io>
2024-06-08 16:41:40 +02:00
xermicus 10c7045e15 Implement CODESIZE
Signed-off-by: xermicus <cyrill@parity.io>
2024-06-06 15:10:21 +02:00
xermicus 39d78179d4 implement transient storage
Signed-off-by: xermicus <cyrill@parity.io>
2024-06-05 17:34:17 +02:00
Cyrill Leutwiler 9e9227d740 Remove vyper and dead code (#23) 2024-06-05 13:29:07 +02:00
Cyrill Leutwiler a04eacabff Support solc v0.8.26 (#22) 2024-06-05 12:03:16 +02:00
xermicus 68ec8be49f Implement the PUSHDEPLOYADDRESS instruction.
Introduction and raison d'être in solc:
https://github.com/ethereum/solidity/pull/3203

TL;DR: Exists to guard libraries from being called directly.

Hence we substitute PUSHDEPLOYADDRESS (the zero address) via a call
to the address API to let the library constructor fetch its own
address. In the zkSync target they do exactly the same.

Signed-off-by: xermicus <cyrill@parity.io>
2024-06-05 10:34:53 +02:00
Cyrill Leutwiler caa1228720 Run all tests on CI (#21)
Run all tests on CI

Signed-off-by: xermicus <cyrill@parity.io>
2024-06-05 00:50:03 +02:00
xermicus d9a304d162 integration: cache contract blob artifacts after compilation
Signed-off-by: xermicus <cyrill@parity.io>
2024-06-04 19:14:52 +02:00
xermicus 2d0a0e2e81 implement BYTE
Signed-off-by: xermicus <cyrill@parity.io>
2024-06-04 18:45:06 +02:00
xermicus 354b1c8d79 do not byte swap storage values
Signed-off-by: xermicus <cyrill@parity.io>
2024-06-03 17:16:49 +02:00
Cyrill Leutwiler 5ff17da695 Implement balance (#20) 2024-06-03 12:21:49 +02:00
Cyrill Leutwiler 1ba806be1f Contract calls (#19) 2024-06-01 20:48:20 +02:00
Cyrill Leutwiler 532721f3be Implement MCOPY (#18)
Signed-off-by: xermicus <cyrill@parity.io>
2024-05-29 21:43:22 +02:00
xermicus 2ea10d0c3e convert todo list into GH issues
Signed-off-by: xermicus <cyrill@parity.io>
2024-05-24 23:25:43 +02:00
xermicus 4f0a109771 update README.md
Signed-off-by: xermicus <cyrill@parity.io>
2024-05-24 21:50:12 +02:00
Cyrill Leutwiler bdaf573f17 integration tests on CI (#12)
Run integration tests on CI
2024-05-24 20:27:28 +02:00
Cyrill Leutwiler 0e90f1fd8c Draf rust CI config 2024-05-24 17:59:38 +02:00
xermicus 5138fe3d06 implement EXTCODESIZE
Signed-off-by: xermicus <cyrill@parity.io>
2024-05-22 22:56:03 +02:00
Cyrill Leutwiler 06aa289d9b Constructors and contract creation (#11)
Implement constructor logic and support create/create2 in the mock runtime

Signed-off-by: xermicus <cyrill@parity.io>
2024-05-22 21:35:32 +02:00
Cyrill Leutwiler 42697edc67 update extensive benchmarks
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2024-05-16 09:58:00 +02:00
Cyrill Leutwiler d8be21f156 update PolkaVM
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2024-05-15 10:12:52 +02:00
xermicus 47d5d6b394 dedicated functions for logging events adds even more code size on 32bit
Signed-off-by: xermicus <cyrill@parity.io>
2024-05-14 11:08:35 +02:00
116 changed files with 11212 additions and 3284 deletions
+52
View File
@@ -0,0 +1,52 @@
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.26/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 apt update && sudo apt install -y libtinfo5
- name: Format
run: make format
- name: Clippy
run: make clippy
- name: Test cargo workspace
run: make test-workspace
- name: Test CLI
run: make test-cli
- uses: actions/upload-artifact@v4
with:
name: ${{ github.job }}-resolc
path: ./target/release/resolc
retention-days: 1
+3 -1
View File
@@ -11,4 +11,6 @@
node_modules
artifacts
tmp
package-lock.json
package-lock.json
/*.html
/build
Generated
+8324 -317
View File
File diff suppressed because it is too large Load Diff
+33 -7
View File
@@ -2,7 +2,30 @@
resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.1.0"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>",
]
license = "MIT/Apache-2.0"
edition = "2021"
repository = "https://github.com/xermicus/revive"
[workspace.dependencies]
revive-benchmarks = { version = "0.1.0", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0", path = "crates/builtins" }
revive-common = { version = "0.1.0", path = "crates/common" }
revive-differential = { version = "0.1.0", path = "crates/differential" }
revive-integration = { version = "0.1.0", path = "crates/integration" }
revive-linker = { version = "0.1.0", path = "crates/linker" }
lld-sys = { version = "0.1.0", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0", path = "crates/llvm-context" }
pallet-contracts-pvm-llapi = { version = "0.1.0", path = "crates/pallet-contracts-pvm-llapi" }
revive-runner = { version = "0.1.0", path = "crates/runner" }
revive-solidity = { version = "0.1.0", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0", path = "crates/stdlib" }
hex = "0.4"
petgraph = "0.6"
cc = "1.0"
@@ -25,25 +48,28 @@ thiserror = "1.0"
which = "5.0"
path-slash = "0.2"
rayon = "1.8"
structopt = { version = "0.3", default-features = false }
clap = { version = "4", default-features = false, features = ["derive"] }
rand = "0.8"
polkavm-common = { git = "https://github.com/koute/polkavm.git", rev = "266658b" }
polkavm-linker = { git = "https://github.com/koute/polkavm.git", rev = "266658b" }
polkavm-disassembler = { git = "https://github.com/koute/polkavm.git", rev = "266658b" }
polkavm = { git = "https://github.com/koute/polkavm.git", rev = "266658b" }
polkavm-common = { git = "https://github.com/koute/polkavm.git", rev = "360029e" }
polkavm-linker = { git = "https://github.com/koute/polkavm.git", rev = "360029e" }
polkavm-disassembler = { git = "https://github.com/koute/polkavm.git", rev = "360029e" }
polkavm = { git = "https://github.com/koute/polkavm.git", rev = "360029e" }
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" }
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 = "559fa1db0594a81d5dbf343613ba2f3fc16708da" }
# Benchmarking against EVM
primitive-types = { version = "0.12", features = ["codec"] }
evm-interpreter = { git = "https://github.com/xermicus/evm.git", branch = "separate-compilation" }
[workspace.dependencies.inkwell]
git = "https://github.com/TheDan64/inkwell.git"
commit = "d916c66"
version = "0.5"
default-features = false
features = ["serde", "llvm18-0", "no-libffi-linking", "target-riscv"]
+8 -2
View File
@@ -1,4 +1,4 @@
.PHONY: install format test test-solidity test-cli test-integration test-workspace clean
.PHONY: install format test test-solidity test-cli test-integration test-workspace clean docs docs-build
install: install-bin install-npm
@@ -11,7 +11,7 @@ install-npm:
format:
cargo fmt --all --check
test: format install test-cli test-workspace
test: format clippy test-cli test-workspace
cargo test --workspace
test-integration: install-bin
@@ -48,6 +48,12 @@ bench: install-bin
clippy:
cargo clippy --all-features --workspace --tests --benches
docs: docs-build
mdbook serve --open docs/
docs-build:
mdbook test docs/ && mdbook build docs/
clean:
cargo clean ; \
rm -rf node_modules ; \
+30 -30
View File
@@ -1,38 +1,38 @@
![CI](https://github.com/xermicus/revive/actions/workflows/rust.yml/badge.svg)
# revive
YUL and EVM bytecode recompiler to LLVM, targetting RISC-V on PolkaVM.
YUL and EVM assembly recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
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`.
[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`.
# Status
## Status
Currently, primary goal of this codebase is to allow for benchmarks comparing performance against ink! and solang artifacts as well as EVM interpreters.
This is experimental software in active development and not ready just yet for production usage.
# TODO
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).
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.
## Installation
- [ ] 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
- [x] Rebranding
`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).
### 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 a runtime similar to [Polkadots `contracts` pallet](https://docs.rs/pallet-contracts/latest/pallet_contracts/api_doc/trait.Current.html).
+60 -38
View File
@@ -3,7 +3,7 @@
set -euo pipefail
INSTALL_DIR="${PWD}/llvm18.0"
mkdir -p $INSTALL_DIR
mkdir -p ${INSTALL_DIR}
# Clone LLVM 18 (any revision after commit bd32aaa is supposed to work)
@@ -13,11 +13,17 @@ fi
# Build LLVM, clang
cd llvm-project
LLVM_SRC_PREFIX=${PWD}/llvm-project
LLVM_SRC_DIR=${LLVM_SRC_PREFIX}/llvm
LLVM_BUILD_DIR=${PWD}/build/llvm
if [ ! -d ${LLVM_BUILD_DIR} ] ; then
mkdir -p ${LLVM_BUILD_DIR}
fi
mkdir -p build
cd build
cmake -G Ninja -DLLVM_ENABLE_ASSERTIONS=On \
cmake -G Ninja \
-S ${LLVM_SRC_DIR} \
-B ${LLVM_BUILD_DIR} \
-DLLVM_ENABLE_ASSERTIONS=On \
-DLLVM_ENABLE_TERMINFO=Off \
-DLLVM_ENABLE_LIBXML2=Off \
-DLLVM_ENABLE_ZLIB=Off \
@@ -25,45 +31,61 @@ cmake -G Ninja -DLLVM_ENABLE_ASSERTIONS=On \
-DLLVM_TARGETS_TO_BUILD='RISCV' \
-DLLVM_ENABLE_ZSTD=Off \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_INSTALL_PREFIX=$INSTALL_DIR \
../llvm
ninja
ninja install
-DCMAKE_INSTALL_PREFIX=${INSTALL_DIR}
cmake --build ${LLVM_BUILD_DIR}
cmake --install ${LLVM_BUILD_DIR}
# Build compiler builtins
cd ../compiler-rt
mkdir -p build
cd build
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
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 \
..
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"
ninja
ninja install
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}
}
build_compiler_rt 32
build_compiler_rt 64
echo ""
echo "success"
-98
View File
@@ -1,98 +0,0 @@
# Benchmarks
## Table of Contents
- [Benchmark Results](#benchmark-results)
- [Baseline](#baseline)
- [OddPorduct](#oddporduct)
- [TriangleNumber](#trianglenumber)
- [FibonacciRecursive](#fibonaccirecursive)
- [FibonacciIterative](#fibonacciiterative)
- [PrepareBaseline](#preparebaseline)
- [PrepareOddProduct](#prepareoddproduct)
- [PrepareTriangleNumber](#preparetrianglenumber)
- [PrepareFibonacciRecursive](#preparefibonaccirecursive)
- [PrepareFibonacciIterative](#preparefibonacciiterative)
## Benchmark Results
### Baseline
| | `EVM` | `PVMInterpreter` | `PVM` |
|:--------|:--------------------------|:---------------------------------|:--------------------------------- |
| **`0`** | `900.78 ns` (✅ **1.00x**) | `715.00 ns` (✅ **1.26x faster**) | `26.22 us` (❌ *29.11x slower*) |
### OddPorduct
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------------|:--------------------------|:---------------------------------|:--------------------------------- |
| **`300000`** | `223.94 ms` (✅ **1.00x**) | `125.43 ms` (✅ **1.79x faster**) | `2.56 ms` (🚀 **87.60x faster**) |
| **`1200000`** | `907.18 ms` (✅ **1.00x**) | `486.46 ms` (🚀 **1.86x faster**) | `9.96 ms` (🚀 **91.10x faster**) |
| **`12000000`** | `9.41 s` (✅ **1.00x**) | `4.96 s` (🚀 **1.90x faster**) | `98.26 ms` (🚀 **95.75x faster**) |
| **`180000000`** | `133.65 s` (✅ **1.00x**) | `73.98 s` (🚀 **1.81x faster**) | `1.48 s` (🚀 **90.04x faster**) |
| **`720000000`** | `543.61 s` (✅ **1.00x**) | `295.27 s` (🚀 **1.84x faster**) | `6.14 s` (🚀 **88.55x faster**) |
### TriangleNumber
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------------|:--------------------------|:---------------------------------|:--------------------------------- |
| **`360000`** | `174.29 ms` (✅ **1.00x**) | `134.93 ms` (✅ **1.29x faster**) | `2.58 ms` (🚀 **67.43x faster**) |
| **`1440000`** | `723.79 ms` (✅ **1.00x**) | `518.44 ms` (✅ **1.40x faster**) | `9.93 ms` (🚀 **72.92x faster**) |
| **`14400000`** | `7.03 s` (✅ **1.00x**) | `5.40 s` (✅ **1.30x faster**) | `99.93 ms` (🚀 **70.40x faster**) |
| **`216000000`** | `108.98 s` (✅ **1.00x**) | `77.85 s` (✅ **1.40x faster**) | `1.44 s` (🚀 **75.78x faster**) |
| **`864000000`** | `423.03 s` (✅ **1.00x**) | `323.22 s` (✅ **1.31x faster**) | `5.99 s` (🚀 **70.61x faster**) |
### FibonacciRecursive
| | `EVM` | `PVMInterpreter` | `PVM` |
|:---------|:--------------------------|:---------------------------------|:---------------------------------- |
| **`24`** | `80.63 ms` (✅ **1.00x**) | `159.96 ms` (❌ *1.98x slower*) | `2.73 ms` (🚀 **29.52x faster**) |
| **`27`** | `331.93 ms` (✅ **1.00x**) | `662.76 ms` (❌ *2.00x slower*) | `10.78 ms` (🚀 **30.79x faster**) |
| **`31`** | `2.35 s` (✅ **1.00x**) | `4.44 s` (❌ *1.88x slower*) | `76.69 ms` (🚀 **30.69x faster**) |
| **`36`** | `26.17 s` (✅ **1.00x**) | `51.08 s` (❌ *1.95x slower*) | `819.30 ms` (🚀 **31.94x faster**) |
| **`39`** | `110.50 s` (✅ **1.00x**) | `220.00 s` (❌ *1.99x slower*) | `3.46 s` (🚀 **31.90x faster**) |
### FibonacciIterative
| | `EVM` | `PVMInterpreter` | `PVM` |
|:----------------|:--------------------------|:---------------------------------|:---------------------------------- |
| **`256`** | `84.27 us` (✅ **1.00x**) | `291.83 us` (❌ *3.46x slower*) | `42.87 us` (🚀 **1.97x faster**) |
| **`162500`** | `53.32 ms` (✅ **1.00x**) | `174.85 ms` (❌ *3.28x slower*) | `2.57 ms` (🚀 **20.78x faster**) |
| **`650000`** | `217.77 ms` (✅ **1.00x**) | `699.77 ms` (❌ *3.21x slower*) | `9.91 ms` (🚀 **21.96x faster**) |
| **`6500000`** | `2.14 s` (✅ **1.00x**) | `6.89 s` (❌ *3.22x slower*) | `100.67 ms` (🚀 **21.22x faster**) |
| **`100000000`** | `31.96 s` (✅ **1.00x**) | `106.46 s` (❌ *3.33x slower*) | `1.50 s` (🚀 **21.28x faster**) |
| **`400000000`** | `128.68 s` (✅ **1.00x**) | `447.34 s` (❌ *3.48x slower*) | `6.19 s` (🚀 **20.77x faster**) |
### PrepareBaseline
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:---------------------------------- |
| **`0`** | `179.68 ns` (✅ **1.00x**) | `506.01 ns` (❌ *2.82x slower*) | `1.70 us` (❌ *9.45x slower*) | `29.44 us` (❌ *163.87x slower*) | `69.01 us` (❌ *384.08x slower*) |
### PrepareOddProduct
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:---------------------------------|:---------------------------------- |
| **`0`** | `509.96 ns` (✅ **1.00x**) | `485.20 ns` (✅ **1.05x faster**) | `1.69 us` (❌ *3.32x slower*) | `29.88 us` (❌ *58.59x slower*) | `70.20 us` (❌ *137.66x slower*) |
### PrepareTriangleNumber
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:---------------------------------|:---------------------------------- |
| **`0`** | `508.44 ns` (✅ **1.00x**) | `528.74 ns` (✅ **1.04x slower**) | `1.83 us` (❌ *3.60x slower*) | `50.81 us` (❌ *99.94x slower*) | `68.37 us` (❌ *134.48x slower*) |
### PrepareFibonacciRecursive
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:---------------------------------- |
| **`0`** | `409.24 ns` (✅ **1.00x**) | `507.67 ns` (❌ *1.24x slower*) | `1.80 us` (❌ *4.40x slower*) | `46.24 us` (❌ *112.99x slower*) | `69.06 us` (❌ *168.76x slower*) |
### PrepareFibonacciIterative
| | `Evm` | `PVMInterpreterCompile` | `PVMInterpreterInstantiate` | `PVMCompile` | `PVMInstantiate` |
|:--------|:--------------------------|:---------------------------------|:-------------------------------------|:----------------------------------|:---------------------------------- |
| **`0`** | `304.00 ns` (✅ **1.00x**) | `524.75 ns` (❌ *1.73x slower*) | `1.88 us` (❌ *6.17x slower*) | `43.50 us` (❌ *143.11x slower*) | `66.82 us` (❌ *219.80x slower*) |
---
Made with [criterion-table](https://github.com/nu11ptr/criterion-table)
+8 -7
View File
@@ -1,10 +1,11 @@
[package]
name = "revive-benchmarks"
version = "0.1.0"
edition = "2021"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
]
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
description = "revive compiler benchmarks"
[features]
default = ["bench-pvm-interpreter"]
@@ -16,8 +17,8 @@ bench-extensive = []
[dependencies]
hex = { workspace = true }
polkavm = { workspace = true }
revive-integration = { path = "../integration" }
revive-differential = { path = "../differential", optional = true }
revive-integration = { workspace = true }
revive-differential = { workspace = true, optional = true }
alloy-primitives = { workspace = true }
[dev-dependencies]
+95 -40
View File
@@ -1,6 +1,4 @@
#[cfg(feature = "bench-extensive")]
use std::time::Duration;
use alloy_primitives::U256;
use criterion::{
criterion_group, criterion_main, measurement::Measurement, BenchmarkGroup, BenchmarkId,
Criterion,
@@ -20,42 +18,86 @@ where
#[cfg(feature = "bench-evm")]
{
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());
revive_differential::execute(revive_differential::prepare(
contract.evm_runtime.clone(),
contract.calldata.clone(),
));
});
});
}
#[cfg(feature = "bench-pvm-interpreter")]
#[cfg(not(feature = "bench-extensive"))]
{
let contract = contract(p.clone());
let (transaction, mut instance, export) = revive_benchmarks::prepare_pvm(
&contract.pvm_runtime,
contract.calldata,
polkavm::BackendKind::Interpreter,
);
group.bench_with_input(BenchmarkId::new("PVMInterpreter", l), p, |b, _| {
b.iter(|| {
transaction.clone().call_on(&mut instance, export);
#[cfg(all(feature = "bench-pvm-interpreter", not(feature = "bench-extensive")))]
{
let contract = contract(p.clone());
let (transaction, mut instance, export) = revive_benchmarks::prepare_pvm(
&contract.pvm_runtime,
contract.calldata,
polkavm::BackendKind::Interpreter,
);
group.bench_with_input(BenchmarkId::new("PVMInterpreter", l), p, |b, _| {
b.iter(|| {
let _ = transaction.clone().call_on(&mut instance, export);
});
});
});
}
}
#[cfg(feature = "bench-pvm")]
{
let contract = contract(p.clone());
let (transaction, mut instance, export) = revive_benchmarks::prepare_pvm(
&contract.pvm_runtime,
contract.calldata,
polkavm::BackendKind::Compiler,
);
group.bench_with_input(BenchmarkId::new("PVM", l), p, |b, _| {
b.iter(|| {
transaction.clone().call_on(&mut instance, export);
#[cfg(all(feature = "bench-pvm", not(feature = "bench-extensive")))]
{
let contract = contract(p.clone());
let (transaction, mut instance, export) = revive_benchmarks::prepare_pvm(
&contract.pvm_runtime,
contract.calldata,
polkavm::BackendKind::Compiler,
);
group.bench_with_input(BenchmarkId::new("PVM", l), p, |b, _| {
b.iter(|| {
let _ = transaction.clone().call_on(&mut instance, export);
});
});
});
}
}
#[cfg(feature = "bench-extensive")]
{
use revive_benchmarks::instantiate_engine;
use revive_integration::mock_runtime::{instantiate_module, recompile_code, State};
#[cfg(feature = "bench-pvm-interpreter")]
{
let contract = contract(p.clone());
let engine = instantiate_engine(polkavm::BackendKind::Interpreter);
let module = recompile_code(&contract.pvm_runtime, &engine);
let transaction = State::default()
.transaction()
.with_default_account(&contract.pvm_runtime)
.calldata(contract.calldata);
group.bench_with_input(BenchmarkId::new("PVMInterpreter", l), p, |b, _| {
b.iter(|| {
let (mut instance, export) = instantiate_module(&module, &engine);
let _ = transaction.clone().call_on(&mut instance, export);
});
});
}
#[cfg(feature = "bench-pvm")]
{
let contract = contract(p.clone());
let engine = instantiate_engine(polkavm::BackendKind::Compiler);
let module = recompile_code(&contract.pvm_runtime, &engine);
let transaction = State::default()
.transaction()
.with_default_account(&contract.pvm_runtime)
.calldata(contract.calldata);
group.bench_with_input(BenchmarkId::new("PVM", l), p, |b, _| {
b.iter(|| {
let (mut instance, export) = instantiate_module(&module, &engine);
let _ = transaction.clone().call_on(&mut instance, export);
});
});
}
}
}
@@ -69,9 +111,7 @@ where
#[cfg(feature = "bench-extensive")]
{
let mut group = c.benchmark_group(group_name);
group
.sample_size(10)
.measurement_time(Duration::from_secs(60));
group.sample_size(10);
group
}
@@ -109,28 +149,43 @@ fn bench_triangle_number(c: &mut Criterion) {
fn bench_fibonacci_recurisve(c: &mut Criterion) {
let group = group(c, "FibonacciRecursive");
#[cfg(feature = "bench-extensive")]
let parameters = &[24, 27, 31, 36, 39];
let parameters = [24, 27, 31, 36, 39]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
#[cfg(not(feature = "bench-extensive"))]
let parameters = &[12, 16, 20];
let parameters = [12, 16, 20]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
bench(group, parameters, parameters, Contract::fib_recursive);
bench(group, &parameters, &parameters, Contract::fib_recursive);
}
fn bench_fibonacci_iterative(c: &mut Criterion) {
let group = group(c, "FibonacciIterative");
#[cfg(feature = "bench-extensive")]
let parameters = &[256, 162500, 650000, 6500000, 100000000, 400000000];
let parameters = [256, 162500, 650000, 6500000, 100000000, 400000000]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
#[cfg(not(feature = "bench-extensive"))]
let parameters = &[64, 128, 256];
let parameters = [64, 128, 256]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
bench(group, parameters, parameters, Contract::fib_iterative);
bench(group, &parameters, &parameters, Contract::fib_iterative);
}
fn bench_fibonacci_binet(c: &mut Criterion) {
let group = group(c, "FibonacciBinet");
let parameters = &[64, 128, 256];
let parameters = [64, 128, 256]
.iter()
.map(|p| U256::from(*p))
.collect::<Vec<_>>();
bench(group, parameters, parameters, Contract::fib_binet);
bench(group, &parameters, &parameters, Contract::fib_binet);
}
fn bench_sha1(c: &mut Criterion) {
+7 -6
View File
@@ -1,3 +1,4 @@
use alloy_primitives::U256;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use revive_integration::cases::Contract;
@@ -117,9 +118,9 @@ fn bench_fibonacci_recursive(c: &mut Criterion) {
c,
"PrepareFibonacciRecursive",
#[cfg(feature = "bench-evm")]
Contract::fib_recursive(0).evm_runtime,
Contract::fib_recursive(U256::ZERO).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::fib_recursive(0).pvm_runtime,
Contract::fib_recursive(U256::ZERO).pvm_runtime,
);
}
@@ -128,9 +129,9 @@ fn bench_fibonacci_iterative(c: &mut Criterion) {
c,
"PrepareFibonacciIterative",
#[cfg(feature = "bench-evm")]
Contract::fib_iterative(0).evm_runtime,
Contract::fib_iterative(U256::ZERO).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::fib_iterative(0).pvm_runtime,
Contract::fib_iterative(U256::ZERO).pvm_runtime,
);
}
@@ -139,9 +140,9 @@ fn bench_fibonacci_binet(c: &mut Criterion) {
c,
"PrepareFibonacciBinet",
#[cfg(feature = "bench-evm")]
Contract::fib_binet(0).evm_runtime,
Contract::fib_binet(U256::ZERO).evm_runtime,
#[cfg(any(feature = "bench-pvm-interpreter", feature = "bench-pvm"))]
Contract::fib_binet(0).pvm_runtime,
Contract::fib_binet(U256::ZERO).pvm_runtime,
);
}
+4 -1
View File
@@ -12,7 +12,10 @@ pub fn prepare_pvm(
config.set_sandbox(Some(SandboxKind::Linux));
let (instance, export_index) = mock_runtime::prepare(code, Some(config));
let transaction = State::default().transaction().calldata(input);
let transaction = State::default()
.transaction()
.with_default_account(code)
.calldata(input);
(transaction, instance, export_index)
}
+8 -3
View File
@@ -1,9 +1,14 @@
[package]
name = "revive-builtins"
version = "0.1.0"
edition = "2021"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
build = "build.rs"
description = "compiler builtins for the revive compiler"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
riscv-64 = []
[dependencies]
+16 -6
View File
@@ -1,7 +1,11 @@
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")
@@ -13,18 +17,24 @@ fn main() {
.read_to_string(&mut llvm_lib_dir)
.expect("llvm-config output should be utf8");
let lib_path = std::path::PathBuf::from(llvm_lib_dir.trim())
let mut lib_path = std::path::PathBuf::from(llvm_lib_dir.trim())
.join("linux")
.join(lib);
let archive = fs::read(lib_path).expect("clang builtins for riscv32 not found");
.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");
let out_dir = env::var_os("OUT_DIR").expect("has OUT_DIR");
let archive_path = Path::new(&out_dir).join(lib);
let archive_path = Path::new(&out_dir).join(BUILTINS_ARCHIVE_FILE);
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!(\"{lib}\");");
let src = format!(
"pub static COMPILER_RT: &[u8; {len}] = include_bytes!(\"{BUILTINS_ARCHIVE_FILE}\");"
);
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.
+7 -3
View File
@@ -1,17 +1,21 @@
[package]
name = "revive-common"
version = "0.1.0"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
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"] }
+3
View File
@@ -7,7 +7,10 @@ pub const BYTE_LENGTH_BYTE: usize = 1;
pub const BYTE_LENGTH_X32: usize = 4;
/// Native stack alignment size in bytes
#[cfg(not(feature = "riscv-64"))]
pub const BYTE_LENGTH_STACK_ALIGN: usize = 4;
#[cfg(feature = "riscv-64")]
pub const BYTE_LENGTH_STACK_ALIGN: usize = 8;
/// The x86_64 word byte-length.
pub const BYTE_LENGTH_X64: usize = 8;
-3
View File
@@ -30,9 +30,6 @@ 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";
+5 -4
View File
@@ -1,9 +1,10 @@
[package]
name = "revive-differential"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
version.workspace = true
license.workspace = true
edition.workspace = true
authors.workspace = true
repository.workspace = true
[dependencies]
evm-interpreter = { workspace = true }
-9
View File
@@ -1,9 +0,0 @@
[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
@@ -1,13 +0,0 @@
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
@@ -1,40 +0,0 @@
use std::{env, fs, path::Path, process::Command};
fn compile(source_path: &str, output_path: &str) {
let output = Command::new("llc")
.args([
"-O3",
"-filetype=asm",
"-mattr=+zbb,+e",
source_path,
"-o",
output_path,
])
.output()
.expect("should be able to invoke llc");
assert!(
output.status.success(),
"failed to compile {}: {:?}",
source_path,
output
);
}
fn main() {
let in_file = "bswap.ll";
let out_file = "bswap.s";
let out_dir = env::var_os("OUT_DIR").expect("env should have $OUT_DIR");
let out_path = Path::new(&out_dir).join(out_file);
compile(
in_file,
out_path.to_str().expect("$OUT_DIR should be UTF-8"),
);
let src_path = Path::new(&out_dir).join("bswap.rs");
let src = format!("pub static ASSEMBLY: &str = include_str!(\"{out_file}\");");
fs::write(src_path, src).expect("should be able to write in $OUT_DIR");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=bswap.ll");
}
-39
View File
@@ -1,39 +0,0 @@
//! 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());
}
}
+11 -8
View File
@@ -1,9 +1,11 @@
[package]
name = "revive-integration"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
description = "revive compiler integration test cases"
[dependencies]
polkavm = { workspace = true }
@@ -11,11 +13,12 @@ alloy-primitives = { workspace = true }
alloy-sol-types = { workspace = true }
hex = { workspace = true }
env_logger = { workspace = true }
log = { workspace = true }
revive-solidity = { path = "../solidity" }
revive-differential = { path = "../differential" }
revive-llvm-context = { path = "../llvm-context" }
revive-common = { path = "../common" }
revive-solidity = { workspace = true }
revive-differential = { workspace = true }
revive-llvm-context = { workspace = true }
revive-common = { workspace = true }
[dev-dependencies]
sha1 = { workspace = true }
+8 -8
View File
@@ -1,10 +1,10 @@
{
"Baseline": 3944,
"Computation": 7444,
"DivisionArithmetics": 42784,
"ERC20": 56199,
"Events": 4784,
"FibonacciIterative": 6019,
"Flipper": 4392,
"SHA1": 36107
"Baseline": 934,
"Computation": 4360,
"DivisionArithmetics": 39824,
"ERC20": 46471,
"Events": 1749,
"FibonacciIterative": 2973,
"Flipper": 3563,
"SHA1": 32543
}
+11
View File
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract Bitwise {
function opByte(uint i, uint x) public payable returns (uint ret) {
assembly {
ret := byte(i, x)
}
}
}
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract Call {
function value_transfer(address payable destination) public payable {
destination.transfer(msg.value);
}
function echo(bytes memory payload) public pure returns (bytes memory) {
return payload;
}
function call(
address callee,
bytes memory payload
) public pure returns (bytes memory) {
return Call(callee).echo(payload);
}
}
+21
View File
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract CreateA {
address creator;
constructor() payable {
creator = msg.sender;
}
}
contract CreateB {
receive() external payable {
new CreateA{value: msg.value}();
}
fallback() external {
new CreateA{salt: hex"01"}();
}
}
+17
View File
@@ -0,0 +1,17 @@
// 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()
}
}
}
+9
View File
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract MCopy {
function memcpy(bytes memory payload) public pure returns (bytes memory) {
return payload;
}
}
+14
View File
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract Storage {
function transient(uint value) public returns (uint ret) {
assembly {
let slot := 123
tstore(slot, value)
let success := call(0, 0, 0, 0, 0, 0, 0)
ret := tload(slot)
}
}
}
+4
View File
@@ -6,4 +6,8 @@ contract Value {
function value() public payable returns (uint ret) {
ret = msg.value;
}
function balance_of(address _address) public view returns (uint ret) {
ret = _address.balance;
}
}
+4
View File
@@ -4,6 +4,10 @@ pragma solidity ^0.8;
contract Flipper {
bool coin;
constructor(bool _coin) {
coin = _coin;
}
function flip() public {
coin = !coin;
}
+126 -229
View File
@@ -1,5 +1,7 @@
use alloy_primitives::{I256, U256};
use alloy_sol_types::{sol, SolCall};
use alloy_primitives::{Address, I256, U256};
use alloy_sol_types::{sol, SolCall, SolConstructor};
use revive_solidity::test_utils::*;
use crate::mock_runtime::{CallOutput, State};
@@ -11,41 +13,90 @@ pub struct Contract {
pub calldata: Vec<u8>,
}
sol!(contract Baseline { function baseline() public payable; });
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)
}
}
};
sol!(contract Flipper { function flip() public; });
// 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: Vec<u8>);
sol!(
interface IERC20 {
contract ERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
@@ -69,6 +120,7 @@ sol!(
event Approval(address indexed owner, address indexed spender, uint value);
}
);
case!("ERC20.sol", ERC20, totalSupplyCall, erc20,);
sol!(
contract Block {
@@ -77,6 +129,8 @@ sol!(
function number() public view returns (uint ret);
}
);
case!("Block.sol", Block, numberCall, block_number,);
case!("Block.sol", Block, timestampCall, block_timestamp,);
sol!(
contract Context {
@@ -85,6 +139,8 @@ sol!(
function caller() public pure returns (address);
}
);
case!("Context.sol", Context, address_thisCall, context_address,);
case!("Context.sol", Context, callerCall, context_caller,);
sol!(
contract DivisionArithmetics {
@@ -97,12 +153,17 @@ sol!(
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 {
@@ -112,6 +173,61 @@ sol!(
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);
}
);
case!("ExtCode.sol", ExtCode, ExtCodeSizeCall, ext_code_size, address: Address);
case!("ExtCode.sol", ExtCode, CodeSizeCall, code_size,);
sol!(
contract MCopy {
function memcpy(bytes memory payload) public pure returns (bytes memory);
}
);
case!("MCopy.sol", MCopy, memcpyCall, memcpy, payload: Vec<u8>);
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: Vec<u8>);
case!("Call.sol", "Call", vec![], call_constructor);
sol!(
contract Value {
function balance_of(address _address) public view returns (uint ret);
}
);
case!("Value.sol", Value, balance_ofCall, value_balance_of, address: Address);
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 {
/// Execute the contract.
@@ -132,231 +248,12 @@ impl Contract {
.call()
}
pub fn baseline() -> Self {
let code = include_str!("../contracts/Baseline.sol");
let name = "Baseline";
fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
Self {
name,
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 {
name,
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 {
name,
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 {
name,
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 {
name,
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 {
name,
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 {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: SHA1::sha1Call::new((pre,)).abi_encode(),
}
}
pub fn flipper() -> Self {
let code = include_str!("../contracts/flipper.sol");
let name = "Flipper";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Flipper::flipCall::new(()).abi_encode(),
}
}
pub fn erc20() -> Self {
let code = include_str!("../contracts/ERC20.sol");
let name = "ERC20";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: IERC20::totalSupplyCall::new(()).abi_encode(),
}
}
pub fn block_number() -> Self {
let code = include_str!("../contracts/Block.sol");
let name = "Block";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Block::numberCall::new(()).abi_encode(),
}
}
pub fn block_timestamp() -> Self {
let code = include_str!("../contracts/Block.sol");
let name = "Block";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Block::timestampCall::new(()).abi_encode(),
}
}
pub fn context_address() -> Self {
let code = include_str!("../contracts/Context.sol");
let name = "Context";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Context::address_thisCall::new(()).abi_encode(),
}
}
pub fn context_caller() -> Self {
let code = include_str!("../contracts/Context.sol");
let name = "Context";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Context::callerCall::new(()).abi_encode(),
}
}
pub fn division_arithmetics_div(n: U256, d: U256) -> Self {
let code = include_str!("../contracts/DivisionArithmetics.sol");
let name = "DivisionArithmetics";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: DivisionArithmetics::divCall::new((n, d)).abi_encode(),
}
}
pub fn division_arithmetics_sdiv(n: I256, d: I256) -> Self {
let code = include_str!("../contracts/DivisionArithmetics.sol");
let name = "DivisionArithmetics";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: DivisionArithmetics::sdivCall::new((n, d)).abi_encode(),
}
}
pub fn division_arithmetics_mod(n: U256, d: U256) -> Self {
let code = include_str!("../contracts/DivisionArithmetics.sol");
let name = "DivisionArithmetics";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: DivisionArithmetics::modCall::new((n, d)).abi_encode(),
}
}
pub fn division_arithmetics_smod(n: I256, d: I256) -> Self {
let code = include_str!("../contracts/DivisionArithmetics.sol");
let name = "DivisionArithmetics";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: DivisionArithmetics::smodCall::new((n, d)).abi_encode(),
}
}
pub fn mstore8(value: U256) -> Self {
let code = include_str!("../contracts/mStore8.sol");
let name = "MStore8";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: MStore8::mStore8Call::new((value,)).abi_encode(),
}
}
pub fn event(topics: U256) -> Self {
let code = include_str!("../contracts/Events.sol");
let name = "Events";
Self {
name,
evm_runtime: crate::compile_evm_bin_runtime(name, code),
pvm_runtime: crate::compile_blob(name, code),
calldata: Events::emitEventCall::new((topics,)).abi_encode(),
evm_runtime: compile_evm_bin_runtime(name, code),
pvm_runtime: compile_blob(name, code),
calldata,
}
}
}
@@ -406,7 +303,7 @@ mod tests {
Contract::baseline as fn() -> Contract,
Contract::flipper as fn() -> Contract,
(|| Contract::odd_product(0)) as fn() -> Contract,
(|| Contract::fib_iterative(0)) as fn() -> Contract,
(|| Contract::fib_iterative(U256::ZERO)) as fn() -> Contract,
Contract::erc20 as fn() -> Contract,
(|| Contract::sha1(Vec::new())) as fn() -> Contract,
(|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract,
-66
View File
@@ -10,72 +10,6 @@ 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,
revive_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")
.bytecode
.as_ref()
.expect("source should produce assembly text")
.object
.as_str();
hex::decode(bytecode).expect("hex encoding should always be valid")
}
pub fn assert_success(contract: &Contract, differential: bool) -> (State, CallOutput) {
let (state, output) = contract.execute();
assert_eq!(output.flags, ReturnFlags::Success);
+414 -58
View File
@@ -10,10 +10,10 @@ use revive_llvm_context::polkavm_const::runtime_api;
/// The mocked blockchain account.
#[derive(Debug, Default, Clone)]
struct Account {
value: U256,
contract: Option<U256>,
storage: HashMap<U256, U256>,
pub struct Account {
pub value: U256,
pub contract: Option<B256>,
pub storage: HashMap<U256, U256>,
}
/// Emitted event data.
@@ -40,7 +40,7 @@ pub struct CallOutput {
enum Export {
#[default]
Call,
Deploy(U256),
Deploy(B256),
}
/// Possible contract call return flags.
@@ -82,6 +82,8 @@ struct Frame {
output: CallOutput,
/// The export to call.
export: Export,
/// The returndata from the last contract call.
returndata: Vec<u8>,
}
impl Default for Frame {
@@ -93,6 +95,7 @@ impl Default for Frame {
input: Default::default(),
output: Default::default(),
export: Default::default(),
returndata: Default::default(),
}
}
}
@@ -103,10 +106,13 @@ impl Default for Frame {
#[derive(Default, Clone, Debug)]
pub struct Transaction {
state: State,
transient_state: State,
stack: Vec<Frame>,
}
impl Transaction {
pub const CALL_STACK_SIZE: usize = 1024;
pub fn default_address() -> Address {
Address::default().create2(B256::default(), keccak256([]).0)
}
@@ -129,10 +135,18 @@ impl Transaction {
.unwrap_or_else(|| panic!("callee has no associated account: {account}"))
}
fn create2(&self, salt: U256, blob_hash: U256) -> Address {
self.top_frame()
.callee
.create2(salt.to_be_bytes(), blob_hash.to_be_bytes())
fn create2(&self, salt: B256, blob_hash: B256) -> Address {
self.top_frame().callee.create2(salt, blob_hash)
}
fn transient_account_mut(&mut self) -> &mut Account {
let address = self.top_frame().callee;
let account = self
.transient_state
.accounts_mut()
.entry(address)
.or_default();
account
}
}
@@ -168,10 +182,26 @@ impl TransactionBuilder {
self
}
/// Set the transaction to deploy code.
pub fn deploy(mut self, code: &[u8]) -> Self {
/// Helper to setup the transaction for deploy code.
/// - Simulate an upload of the `code`
/// - Set the export to `deploy`
/// - Derive address based on the caller and `salt` value
/// - Set the callee to the derived address
/// - Create a new default account at the derived address
pub fn deploy(mut self, code: &[u8], salt: Option<B256>) -> Self {
let blob_hash = self.context.state.upload_code(code);
let address = self
.context
.top_frame()
.caller
.create2(salt.unwrap_or_default(), blob_hash);
self.context.top_frame_mut().export = Export::Deploy(blob_hash);
self.context.top_frame_mut().callee = address;
self.context
.state
.create_account(address, Default::default(), blob_hash);
self
}
@@ -183,7 +213,7 @@ impl TransactionBuilder {
self.context.state.create_account(
Transaction::default_address(),
Default::default(),
keccak256(code).into(),
keccak256(code),
);
self
}
@@ -193,12 +223,11 @@ impl TransactionBuilder {
/// Reverts any state changes if the contract reverts or the exuection traps.
pub fn call(mut self) -> (State, CallOutput) {
let blob_hash = match self.context.top_frame().export {
Export::Call => self.context.top_account_mut().contract.unwrap_or_else(|| {
panic!(
"not a contract account: {}",
self.context.top_frame().callee
)
}),
Export::Call => self
.context
.top_account_mut()
.contract
.expect("balance transfer"),
Export::Deploy(blob_hash) => blob_hash,
};
let code = self
@@ -207,7 +236,12 @@ impl TransactionBuilder {
.blobs
.get(&blob_hash)
.unwrap_or_else(|| panic!("contract code not found: {blob_hash}"));
let (mut instance, export) = prepare(code, None);
let (mut instance, _) = prepare(code, None);
let export = match self.context.top_frame().export {
Export::Call => runtime_api::exports::CALL,
Export::Deploy(_) => runtime_api::exports::DEPLOY,
};
let export = instance.module().lookup_export(export).unwrap();
self.call_on(&mut instance, export)
}
@@ -223,14 +257,6 @@ impl TransactionBuilder {
let mut state_args = polkavm::StateArgs::default();
state_args.set_gas(polkavm::Gas::MAX);
if let Export::Deploy(blob_hash) = self.context.top_frame().export {
self.context.state.create_account(
self.context.create2(Default::default(), blob_hash),
self.context.top_frame().callvalue,
blob_hash,
);
}
let callvalue = self.context.top_frame().callvalue;
self.context.top_account_mut().value += callvalue;
@@ -262,6 +288,7 @@ impl From<State> for TransactionBuilder {
state_before: state.clone(),
context: Transaction {
state,
transient_state: Default::default(),
stack: Default::default(),
},
}
@@ -271,7 +298,7 @@ impl From<State> for TransactionBuilder {
/// The mocked blockchain state.
#[derive(Default, Clone, Debug)]
pub struct State {
blobs: HashMap<U256, Vec<u8>>,
blobs: HashMap<B256, Vec<u8>>,
accounts: HashMap<Address, Account>,
}
@@ -279,18 +306,32 @@ impl State {
pub const BLOCK_NUMBER: u64 = 123;
pub const BLOCK_TIMESTAMP: u64 = 456;
pub fn new_deployed(contract: crate::Contract) -> (Self, Address) {
let (state, output) = State::default()
.transaction()
.deploy(&contract.pvm_runtime, None)
.calldata(contract.calldata)
.call();
assert_eq!(output.flags, ReturnFlags::Success);
let address = *state.accounts().keys().next().unwrap();
(state, address)
}
pub fn transaction(self) -> TransactionBuilder {
TransactionBuilder {
state_before: self.clone(),
context: Transaction {
state: self,
transient_state: Default::default(),
stack: vec![Default::default()],
},
}
}
pub fn upload_code(&mut self, code: &[u8]) -> U256 {
let blob_hash = keccak256(code).into();
pub fn upload_code(&mut self, code: &[u8]) -> B256 {
let blob_hash = keccak256(code);
self.blobs.insert(blob_hash, code.to_vec());
blob_hash
}
@@ -308,7 +349,7 @@ impl State {
);
}
pub fn create_account(&mut self, address: Address, value: U256, blob_hash: U256) {
pub fn create_account(&mut self, address: Address, value: U256, blob_hash: B256) {
self.accounts.insert(
address,
Account {
@@ -318,6 +359,14 @@ impl State {
},
);
}
pub fn accounts(&self) -> &HashMap<Address, Account> {
&self.accounts
}
pub fn accounts_mut(&mut self) -> &mut HashMap<Address, Account> {
&mut self.accounts
}
}
fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
@@ -325,7 +374,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::INPUT,
runtime_api::imports::INPUT,
|caller: Caller<Transaction>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, transaction) = caller.split();
@@ -342,7 +391,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::RETURN,
runtime_api::imports::RETURN,
|caller: Caller<Transaction>,
flags: u32,
data_ptr: u32,
@@ -361,7 +410,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::VALUE_TRANSFERRED,
runtime_api::imports::VALUE_TRANSFERRED,
|caller: Caller<Transaction>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, transaction) = caller.split();
@@ -397,8 +446,9 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::SET_STORAGE,
runtime_api::imports::SET_STORAGE,
|caller: Caller<Transaction>,
transient: u32,
key_ptr: u32,
key_len: u32,
value_ptr: u32,
@@ -420,10 +470,17 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
let value = caller.read_memory_into_vec(value_ptr, value_len)?;
transaction.top_account_mut().storage.insert(
U256::from_be_bytes::<32>(key.try_into().unwrap()),
U256::from_be_bytes::<32>(value.try_into().unwrap()),
);
let key = U256::from_le_bytes::<32>(key.try_into().unwrap());
let value = U256::from_le_bytes::<32>(value.try_into().unwrap());
log::info!("set storage {key} = {value}");
let storage = if transient == 0 {
&mut transaction.top_account_mut().storage
} else {
&mut transaction.transient_account_mut().storage
};
storage.insert(key, value);
Ok(0)
},
@@ -432,8 +489,9 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::GET_STORAGE,
runtime_api::imports::GET_STORAGE,
|caller: Caller<Transaction>,
transient: u32,
key_ptr: u32,
key_len: u32,
out_ptr: u32,
@@ -449,14 +507,18 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
"spurious output buffer size: {out_len}"
);
let value = transaction
.top_account_mut()
.storage
.get(&U256::from_be_bytes::<32>(key.try_into().unwrap()))
.map(U256::to_be_bytes::<32>)
.unwrap_or_default();
let key = U256::from_le_bytes::<32>(key.try_into().unwrap());
caller.write_memory(out_ptr, &value[..])?;
let storage = if transient == 0 {
&transaction.top_account_mut().storage
} else {
&transaction.transient_account_mut().storage
};
let value = storage.get(&key).cloned().unwrap_or_default();
log::info!("get storage {key} = {value}");
caller.write_memory(out_ptr, &value.to_le_bytes::<32>())?;
caller.write_memory(out_len_ptr, &32u32.to_le_bytes())?;
Ok(0)
@@ -466,7 +528,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::HASH_KECCAK_256,
runtime_api::imports::HASH_KECCAK_256,
|caller: Caller<Transaction>,
input_ptr: u32,
input_len: u32,
@@ -487,7 +549,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::NOW,
runtime_api::imports::NOW,
|caller: Caller<Transaction>, out_ptr: u32, out_len_ptr: u32| {
let (mut caller, _) = caller.split();
@@ -508,7 +570,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::BLOCK_NUMBER,
runtime_api::imports::BLOCK_NUMBER,
|caller: Caller<Transaction>, out_ptr: u32, out_len_ptr: u32| {
let (mut caller, _) = caller.split();
@@ -529,7 +591,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::ADDRESS,
runtime_api::imports::ADDRESS,
|caller: Caller<Transaction>, out_ptr: u32, out_len_ptr: u32| {
let (mut caller, transaction) = caller.split();
@@ -551,7 +613,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::CALLER,
runtime_api::imports::CALLER,
|caller: Caller<Transaction>, out_ptr: u32, out_len_ptr: u32| {
let (mut caller, transaction) = caller.split();
@@ -573,7 +635,7 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
linker
.func_wrap(
runtime_api::DEPOSIT_EVENT,
runtime_api::imports::DEPOSIT_EVENT,
|caller: Caller<Transaction>,
topics_ptr: u32,
topics_len: u32,
@@ -608,6 +670,299 @@ fn link_host_functions(engine: &Engine) -> Linker<Transaction> {
)
.unwrap();
linker
.func_wrap(
runtime_api::imports::INSTANTIATE,
|caller: Caller<Transaction>, argument_ptr: u32| {
let (mut caller, transaction) = caller.split();
#[derive(Debug)]
#[repr(packed)]
struct Arguments {
code_hash_ptr: u32,
ref_time_limit: u64,
proof_size_limit: u64,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
address_ptr: u32,
address_len_ptr: u32,
output_ptr: u32,
output_len_ptr: u32,
salt_ptr: u32,
salt_len: u32,
}
let mut buffer = [0; std::mem::size_of::<Arguments>()];
caller.read_memory_into_slice(argument_ptr, &mut buffer)?;
let arguments: Arguments = unsafe { std::mem::transmute(buffer) };
assert_eq!({ arguments.ref_time_limit }, 0);
assert_eq!({ arguments.proof_size_limit }, 0);
assert_eq!({ arguments.deposit_ptr }, u32::MAX);
assert_eq!({ arguments.output_ptr }, u32::MAX);
assert_eq!({ arguments.output_len_ptr }, u32::MAX);
assert_eq!({ arguments.salt_len }, 32);
if transaction.stack.len() >= Transaction::CALL_STACK_SIZE {
log::info!("deployment faild: maximum stack depth reached");
caller.write_memory(arguments.address_ptr, &Address::ZERO.0 .0)?;
return Ok(());
}
let blob_hash = caller.read_memory_into_vec(arguments.code_hash_ptr, 32)?;
let blob_hash = B256::from_slice(&blob_hash);
let value = caller.read_memory_into_vec(arguments.value_ptr, 20)?;
let input_data = caller
.read_memory_into_vec(arguments.input_data_ptr, arguments.input_data_len)?;
let address_len = caller.read_u32(arguments.address_len_ptr)?;
assert_eq!(address_len, 20);
let salt = caller.read_memory_into_vec(arguments.salt_ptr, arguments.salt_len)?;
let salt = B256::from_slice(&salt);
let address = transaction.create2(salt, blob_hash);
if transaction.state.accounts.contains_key(&address) {
log::info!("deployment failed: address {address} already exists");
caller.write_memory(arguments.address_ptr, &Address::ZERO.0 .0)?;
return Ok(());
}
let amount = U256::from_le_slice(&value);
match transaction.top_account_mut().value.checked_sub(amount) {
Some(deducted) => transaction.top_account_mut().value = deducted,
None => {
log::info!("deployment failed: insufficient balance {amount}");
caller.write_memory(arguments.address_ptr, &Address::ZERO.0 .0)?;
return Ok(());
}
}
let (state, output) = transaction
.state
.clone()
.transaction()
.callee(address)
.deploy(transaction.state.blobs.get(&blob_hash).unwrap(), Some(salt))
.callvalue(amount)
.calldata(input_data)
.call();
let result = if output.flags == ReturnFlags::Success {
log::info!("deployment succeeded");
transaction.state = state;
address
} else {
log::info!("deployment failed: callee reverted {:?}", output.flags);
Address::ZERO
};
caller.write_memory(arguments.address_ptr, &result.0 .0)?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
runtime_api::imports::CALL,
|caller: Caller<Transaction>, argument_ptr: u32| -> Result<u32, Trap> {
let (mut caller, transaction) = caller.split();
#[derive(Debug)]
#[repr(packed)]
struct Arguments {
_flags: u32,
address_ptr: u32,
_ref_time_limit: u64,
proof_size_limit: u64,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
output_len_ptr: u32,
}
let mut buffer = [0; std::mem::size_of::<Arguments>()];
caller.read_memory_into_slice(argument_ptr, &mut buffer)?;
let arguments: Arguments = unsafe { std::mem::transmute(buffer) };
assert_eq!({ arguments.proof_size_limit }, 0);
assert_eq!({ arguments.deposit_ptr }, u32::MAX);
let amount = if arguments.value_ptr != u32::MAX {
let value = caller.read_memory_into_vec(arguments.value_ptr, 32)?;
U256::from_le_slice(&value)
} else {
U256::ZERO
};
match transaction.top_account_mut().value.checked_sub(amount) {
Some(deducted) => transaction.top_account_mut().value = deducted,
None => {
log::info!("call failed: insufficient balance {amount}");
return Ok(1);
}
}
let bytes = caller.read_memory_into_vec(arguments.address_ptr, 32)?;
let word = U256::from_le_slice(&bytes);
let address = Address::from_word(word.into());
log::info!("call {address}");
if !transaction.state.accounts.contains_key(&address) {
log::info!(
"balance transfer {amount} from {} to {address}",
transaction.top_frame().callee
);
transaction
.state
.accounts
.entry(address)
.or_insert_with(|| Account {
value: amount,
contract: None,
storage: Default::default(),
});
return Ok(0);
}
if transaction.stack.len() >= Transaction::CALL_STACK_SIZE {
log::info!("deployment faild: maximum stack depth reached");
return Ok(1);
}
let calldata = caller
.read_memory_into_vec(arguments.input_data_ptr, arguments.input_data_len)?;
let (state, output) = transaction
.state
.clone()
.transaction()
.callee(address)
.callvalue(amount)
.calldata(calldata)
.call();
let bytes_to_copy = caller.read_u32(arguments.output_len_ptr)? as usize;
let output_size = output.data.len();
assert!(
bytes_to_copy <= output_size,
"output buffer of {bytes_to_copy}b too small for {output_size}b"
);
transaction.top_frame_mut().returndata = output.data.to_vec();
caller.write_memory(
arguments.output_ptr,
&transaction.top_frame().returndata[..bytes_to_copy],
)?;
caller.write_memory(arguments.output_len_ptr, &output.data.len().to_le_bytes())?;
assert_eq!(
transaction.top_frame().returndata.len(),
caller.read_u32(arguments.output_len_ptr)? as usize
);
let success = if output.flags == ReturnFlags::Success {
log::info!("call succeeded");
transaction.state = state;
0
} else {
log::info!("call failed: callee reverted {:?}", output.flags);
1
};
Ok(success)
},
)
.unwrap();
linker
.func_wrap(
runtime_api::imports::CODE_SIZE,
|caller: Caller<Transaction>, address_ptr: u32| {
let (caller, transaction) = caller.split();
let address = if address_ptr == u32::MAX {
transaction.top_frame().callee
} else {
let bytes = caller.read_memory_into_vec(address_ptr, 32)?;
let word = U256::from_le_slice(&bytes);
Address::from_word(word.into())
};
let code_size = transaction
.state
.accounts
.get(&address)
.and_then(|account| account.contract)
.and_then(|blob_hash| transaction.state.blobs.get(&blob_hash))
.map(|code| code.len())
.unwrap_or_default() as u32;
log::info!("code size of {address} = {code_size}");
Ok(code_size)
},
)
.unwrap();
linker
.func_wrap(
runtime_api::imports::RETURNDATACOPY,
|caller: Caller<Transaction>,
destination_ptr: u32,
offset: u32,
size: u32|
-> Result<(), Trap> {
let (mut caller, transaction) = caller.split();
let offset = offset as usize;
let slice_end = offset
.checked_add(size as usize)
.expect("offset + size overflows");
assert!(
slice_end <= transaction.top_frame().returndata.len(),
"offset + size is larger than RETURNDATASIZE"
);
caller.write_memory(
destination_ptr,
&transaction.top_frame().returndata[offset..slice_end],
)?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
runtime_api::imports::BALANCE,
|caller: Caller<Transaction>, address_ptr: u32, balance_ptr: u32| -> Result<(), Trap> {
let (mut caller, transaction) = caller.split();
let bytes = caller.read_memory_into_vec(address_ptr, 32)?;
let word = U256::from_le_slice(&bytes);
let address = Address::from_word(word.into());
let balance = transaction
.state
.accounts()
.get(&address)
.map(|account| account.value)
.unwrap_or(U256::default());
caller.write_memory(balance_ptr, &balance.to_le_bytes::<32>())?;
log::info!("account {address} balance {balance}");
Ok(())
},
)
.unwrap();
linker
}
@@ -619,14 +974,14 @@ 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()
Module::new(engine, &module_config, code.into()).unwrap()
}
pub fn instantiate_module(
module: &Module,
engine: &Engine,
) -> (Instance<Transaction>, ExportIndex) {
let export = module.lookup_export(runtime_api::CALL).unwrap();
let export = module.lookup_export(runtime_api::imports::CALL).unwrap();
let func = link_host_functions(engine).instantiate_pre(module).unwrap();
let instance = func.instantiate().unwrap();
@@ -634,15 +989,16 @@ pub fn instantiate_module(
}
pub fn prepare(code: &[u8], config: Option<Config>) -> (Instance<Transaction>, ExportIndex) {
let blob = ProgramBlob::parse(code).unwrap();
let blob = ProgramBlob::parse(code.into())
.unwrap_or_else(|err| panic!("{err}\n{}", hex::encode(code)));
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(runtime_api::CALL).unwrap();
let module = Module::from_blob(&engine, &module_config, blob).unwrap();
let export = module.lookup_export(runtime_api::exports::CALL).unwrap();
let func = link_host_functions(&engine)
.instantiate_pre(&module)
.unwrap();
+234 -17
View File
@@ -1,8 +1,12 @@
use alloy_primitives::{keccak256, Address, FixedBytes, I256, U256};
use alloy_sol_types::{sol, SolCall};
use std::str::FromStr;
use alloy_primitives::{keccak256, Address, FixedBytes, B256, I256, U256};
use alloy_sol_types::{sol, SolCall, SolValue};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use sha1::Digest;
use revive_solidity::test_utils::*;
use crate::{
assert_success,
cases::Contract,
@@ -14,9 +18,9 @@ fn fibonacci() {
let parameter = 6;
for contract in [
Contract::fib_recursive(parameter),
Contract::fib_iterative(parameter),
Contract::fib_binet(parameter),
Contract::fib_recursive(U256::from(parameter)),
Contract::fib_iterative(U256::from(parameter)),
Contract::fib_binet(U256::from(parameter)),
] {
let (_, output) = assert_success(&contract, true);
let received = U256::from_be_bytes::<32>(output.data.try_into().unwrap());
@@ -27,16 +31,24 @@ fn fibonacci() {
#[test]
fn flipper() {
let (state, address) = State::new_deployed(Contract::flipper_constructor(true));
let contract = Contract::flipper();
let address = Transaction::default_address();
let (state, output) = contract.execute();
assert_eq!(output.flags, ReturnFlags::Success);
state.assert_storage_key(address, U256::ZERO, U256::from(1));
let (state, output) = state.transaction().calldata(contract.calldata).call();
let (state, output) = state
.transaction()
.calldata(contract.calldata.clone())
.callee(address)
.call();
assert_eq!(output.flags, ReturnFlags::Success);
state.assert_storage_key(address, U256::ZERO, U256::ZERO);
let (state, output) = state
.transaction()
.calldata(contract.calldata)
.callee(address)
.call();
assert_eq!(output.flags, ReturnFlags::Success);
state.assert_storage_key(address, U256::ZERO, U256::from(1));
}
#[test]
@@ -52,7 +64,7 @@ fn hash_keccak_256() {
hash = keccak256(bytes(_pre));
}
}"#;
let code = crate::compile_blob("TestSha3", source);
let code = compile_blob("TestSha3", source);
let param = "hello";
let input = TestSha3::testCall::new((param.to_string(),)).abi_encode();
@@ -72,7 +84,7 @@ fn hash_keccak_256() {
#[test]
fn erc20() {
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
let _ = compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
}
#[test]
@@ -99,7 +111,7 @@ fn msize_plain() {
function mSize() public pure returns (uint);
}
);
let code = crate::compile_blob_with_options(
let code = compile_blob_with_options(
"MSize",
include_str!("../contracts/MSize.sol"),
false,
@@ -128,7 +140,7 @@ fn transferred_value() {
function value() public payable returns (uint);
}
);
let code = crate::compile_blob("Value", include_str!("../contracts/Value.sol"));
let code = compile_blob("Value", include_str!("../contracts/Value.sol"));
let (_, output) = State::default()
.transaction()
@@ -152,7 +164,7 @@ fn msize_non_word_sized_access() {
function mStore100() public pure returns (uint);
}
);
let code = crate::compile_blob_with_options(
let code = compile_blob_with_options(
"MSize",
include_str!("../contracts/MSize.sol"),
false,
@@ -421,3 +433,208 @@ fn events() {
assert_success(&Contract::event(U256::ZERO), true);
assert_success(&Contract::event(U256::from(123)), true);
}
#[test]
fn create2() {
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)
.call();
assert_eq!(output.flags, ReturnFlags::Success);
assert_eq!(state.accounts().len(), 2);
for address in state.accounts().keys() {
if *address != Transaction::default_address() {
let derived_address = Transaction::default_address().create2(
B256::from(U256::from(1)),
keccak256(&contract_a.pvm_runtime).0,
);
assert_eq!(*address, derived_address);
}
}
}
#[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);
}
#[test]
fn create_with_value() {
let mut state = State::default();
state.upload_code(&Contract::create_a().pvm_runtime);
let amount = U256::from(123);
let contract = Contract::create_b();
let (state, output) = state
.transaction()
.with_default_account(&contract.pvm_runtime)
.callvalue(amount)
.call();
assert_eq!(output.flags, ReturnFlags::Success);
assert_eq!(state.accounts().len(), 2);
for (address, account) in state.accounts() {
if *address == Transaction::default_address() {
assert_eq!(account.value, U256::ZERO);
} else {
assert_eq!(account.value, amount);
}
}
}
#[test]
fn ext_code_size() {
let contract = Contract::ext_code_size(Transaction::default_address());
let (_, output) = assert_success(&contract, false);
let received = U256::from_be_slice(&output.data);
let expected = U256::from(contract.pvm_runtime.len());
assert_eq!(received, expected);
let contract = Contract::ext_code_size(Default::default());
let (_, output) = assert_success(&contract, false);
let received = U256::from_be_slice(&output.data);
let expected = U256::ZERO;
assert_eq!(received, expected);
}
#[test]
fn code_size() {
let contract = Contract::code_size();
let (_, output) = assert_success(&contract, false);
let expected = U256::from(contract.pvm_runtime.len());
let received = U256::from_be_slice(&output.data);
assert_eq!(expected, received);
}
#[test]
fn value_transfer() {
// Succeeds in remix (shanghai) but traps the interpreter
let (state, _) = assert_success(&Contract::call_value_transfer(Default::default()), false);
assert_eq!(state.accounts().len(), 2);
assert!(state.accounts().get(&Address::default()).is_some());
}
#[test]
fn echo() {
let (state, address) = State::new_deployed(Contract::call_constructor());
let expected = vec![1, 2, 3, 4, 5];
let contract = Contract::call_call(address, expected.clone());
let (_, output) = state
.transaction()
.with_default_account(&contract.pvm_runtime)
.calldata(contract.calldata)
.call();
assert_eq!(output.flags, ReturnFlags::Success);
let received = alloy_primitives::Bytes::abi_decode(&output.data, true)
.unwrap()
.to_vec();
assert_eq!(expected, received);
}
#[test]
fn mcopy() {
let expected = vec![1, 2, 3];
let (_, output) = assert_success(&Contract::memcpy(expected.clone()), false);
let received = alloy_primitives::Bytes::abi_decode(&output.data, true)
.unwrap()
.to_vec();
assert_eq!(expected, received);
}
#[test]
fn balance() {
let (_, output) = assert_success(&Contract::value_balance_of(Default::default()), false);
let expected = U256::ZERO;
let received = U256::from_be_slice(&output.data);
assert_eq!(expected, received);
let expected = U256::from(54589);
let (mut state, address) = State::new_deployed(Contract::value_balance_of(Default::default()));
state.accounts_mut().get_mut(&address).unwrap().value = expected;
let contract = Contract::value_balance_of(address);
let (_, output) = state
.transaction()
.with_default_account(&contract.pvm_runtime)
.calldata(contract.calldata)
.call();
assert_eq!(ReturnFlags::Success, output.flags);
let received = U256::from_be_slice(&output.data);
assert_eq!(expected, received)
}
#[test]
fn bitwise_byte() {
assert_success(&Contract::bitwise_byte(U256::ZERO, U256::ZERO), true);
assert_success(&Contract::bitwise_byte(U256::ZERO, U256::MAX), true);
assert_success(&Contract::bitwise_byte(U256::MAX, U256::ZERO), true);
assert_success(
&Contract::bitwise_byte(U256::from_str("18446744073709551619").unwrap(), U256::MAX),
true,
);
let de_bruijn_sequence =
hex::decode("4060503824160d0784426150b864361d0f88c4a27148ac5a2f198d46e391d8f4").unwrap();
let value = U256::from_be_bytes::<32>(de_bruijn_sequence.clone().try_into().unwrap());
for (index, byte) in de_bruijn_sequence.iter().enumerate() {
let (_, output) = assert_success(&Contract::bitwise_byte(U256::from(index), value), true);
let expected = U256::from(*byte as i32);
let received = U256::abi_decode(&output.data, true).unwrap();
assert_eq!(expected, received)
}
}
#[test]
fn transient_storage() {
let expected = U256::MAX;
let (state, output) = assert_success(&Contract::storage_transient(expected), false);
let received = U256::abi_decode(&output.data, true).unwrap();
assert_eq!(expected, received);
assert!(state
.accounts()
.values()
.all(|account| account.storage.is_empty()));
}
+10 -5
View File
@@ -1,9 +1,14 @@
[package]
name = "revive-linker"
version = "0.1.0"
edition = "2021"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
description = "revive compiler linker utils"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
riscv-64 = []
[dependencies]
inkwell = { workspace = true }
@@ -13,5 +18,5 @@ polkavm-common = { workspace = true }
libc = { workspace = true }
anyhow = { workspace = true }
revive-builtins = { path = "../builtins" }
lld-sys = { path = "../lld-sys" }
revive-builtins = { workspace = true }
lld-sys = { workspace = true }
+12 -3
View File
@@ -8,6 +8,16 @@ 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()
@@ -24,7 +34,6 @@ fn polkavm_linker<T: AsRef<[u8]>>(code: T) -> anyhow::Result<Vec<u8>> {
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))
}
@@ -33,7 +42,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("libclang_rt.builtins-riscv32.a");
let compiler_rt_path = dir.path().join(BUILTINS_ARCHIVE_FILE);
fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?;
@@ -58,7 +67,7 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
"--library-path",
dir.path().to_str().expect("should be utf8"),
"--library",
"clang_rt.builtins-riscv32",
BUILTINS_LIB_NAME,
linker_script_path.to_str().expect("should be utf8"),
object_path.to_str().expect("should be utf8"),
"-o",
+6 -5
View File
@@ -1,11 +1,12 @@
[package]
name = "lld-sys"
version = "0.1.0"
edition = "2021"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
description = "bindings for ld.lld core"
[dependencies]
libc = { workspace = true }
+4 -1
View File
@@ -27,7 +27,10 @@ fn set_rustc_link_flags() {
}
#[cfg(target_os = "linux")]
println!("cargo:rustc-link-lib=dylib=stdc++");
{
println!("cargo:rustc-link-lib=dylib=stdc++");
println!("cargo:rustc-link-lib=tinfo");
}
}
fn main() {
+10 -10
View File
@@ -1,12 +1,13 @@
[package]
name = "revive-llvm-context"
version = "1.4.1"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors = [
"Oleksandr Zarudnyi <a.zarudnyy@matterlabs.dev>",
"Cyrill Leutwiler <cyrill@parity.io>",
]
license = "MIT OR Apache-2.0"
edition = "2021"
description = "Shared front end code of the revive PolkaVM compilers"
[lib]
@@ -14,6 +15,7 @@ doctest = false
[features]
riscv-zbb = []
riscv-64 = []
[dependencies]
anyhow = { workspace = true }
@@ -31,10 +33,8 @@ inkwell = { workspace = true }
polkavm-disassembler = { workspace = true }
polkavm-common = { 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-common = { workspace = true }
pallet-contracts-pvm-llapi = { workspace = true }
revive-linker = { workspace = true }
revive-builtins = { workspace = true }
revive-stdlib = { workspace = true }
@@ -11,12 +11,13 @@ 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 jump JSON
#[cfg(debug_assertions)]
JSON,
}
impl IRType {
@@ -26,9 +27,10 @@ impl IRType {
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,
}
}
}
+16 -10
View File
@@ -52,16 +52,6 @@ impl DebugConfig {
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,
@@ -104,6 +94,22 @@ impl DebugConfig {
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<()> {
let mut file_path = self.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)?;
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(':', ".");
+3 -3
View File
@@ -28,15 +28,15 @@ pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionRet
pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction;
pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction;
pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction;
pub use self::polkavm::context::function::runtime::Runtime as PolkaVMRuntime;
pub use self::polkavm::context::function::vyper_data::VyperData as PolkaVMFunctionVyperData;
pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode;
pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry;
pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode;
pub use self::polkavm::context::function::yul_data::YulData as PolkaVMFunctionYulData;
pub use self::polkavm::context::function::Function as PolkaVMFunction;
pub use self::polkavm::context::global::Global as PolkaVMGlobal;
pub use self::polkavm::context::pointer::Pointer as PolkaVMPointer;
pub use self::polkavm::context::r#loop::Loop as PolkaVMLoop;
pub use self::polkavm::context::solidity_data::SolidityData as PolkaVMContextSolidityData;
pub use self::polkavm::context::vyper_data::VyperData as PolkaVMContextVyperData;
pub use self::polkavm::context::yul_data::YulData as PolkaVMContextYulData;
pub use self::polkavm::context::Context as PolkaVMContext;
pub use self::polkavm::evm::arithmetic as polkavm_evm_arithmetic;
+1 -9
View File
@@ -33,9 +33,6 @@ 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_";
@@ -74,10 +71,5 @@ pub const NO_SYSTEM_CALL_BIT: bool = false;
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_WORD * 4);
pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD;
@@ -1,52 +1,71 @@
//! Runtime API import and export symbols.
/// The contract deploy export.
pub static CALL: &str = "call";
pub mod exports {
/// The contract deploy export.
pub static CALL: &str = "call";
/// The contract call export.
pub static DEPLOY: &str = "deploy";
/// The contract call export.
pub static DEPLOY: &str = "deploy";
/// All exported symbols.
/// Useful for configuring common attributes and linkage.
pub static EXPORTS: [&str; 2] = [CALL, DEPLOY];
/// All exported symbols.
/// Useful for configuring common attributes and linkage.
pub static EXPORTS: [&str; 2] = [CALL, DEPLOY];
}
pub static ADDRESS: &str = "address";
pub mod imports {
pub static ADDRESS: &str = "address";
pub static BLOCK_NUMBER: &str = "block_number";
pub static BALANCE: &str = "balance";
pub static CALLER: &str = "caller";
pub static BLOCK_NUMBER: &str = "block_number";
pub static DEPOSIT_EVENT: &str = "deposit_event";
pub static CALL: &str = "seal_call";
pub static GET_STORAGE: &str = "get_storage";
pub static CALLER: &str = "caller";
pub static HASH_KECCAK_256: &str = "hash_keccak_256";
pub static CODE_SIZE: &str = "code_size";
pub static INPUT: &str = "input";
pub static DEPOSIT_EVENT: &str = "deposit_event";
pub static NOW: &str = "now";
pub static GET_STORAGE: &str = "get_storage";
pub static RETURN: &str = "seal_return";
pub static HASH_KECCAK_256: &str = "hash_keccak_256";
pub static SET_STORAGE: &str = "set_storage";
pub static INPUT: &str = "input";
pub static VALUE_TRANSFERRED: &str = "value_transferred";
pub static INSTANTIATE: &str = "instantiate";
/// All imported runtime API symbols..
/// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 11] = [
ADDRESS,
BLOCK_NUMBER,
CALLER,
DEPOSIT_EVENT,
GET_STORAGE,
HASH_KECCAK_256,
INPUT,
NOW,
RETURN,
SET_STORAGE,
VALUE_TRANSFERRED,
];
pub static NOW: &str = "now";
pub static RETURN: &str = "seal_return";
pub static RETURNDATACOPY: &str = "returndatacopy";
pub static SET_STORAGE: &str = "set_storage";
pub static VALUE_TRANSFERRED: &str = "value_transferred";
/// All imported runtime API symbols.
/// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 16] = [
ADDRESS,
BALANCE,
BLOCK_NUMBER,
CALL,
CALLER,
CODE_SIZE,
DEPOSIT_EVENT,
GET_STORAGE,
HASH_KECCAK_256,
INPUT,
INSTANTIATE,
NOW,
RETURN,
RETURNDATACOPY,
SET_STORAGE,
VALUE_TRANSFERRED,
];
}
/// PolkaVM __sbrk API symbol to extend the heap memory.
pub static SBRK: &str = "__sbrk";
@@ -8,13 +8,7 @@ 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,
@@ -25,9 +19,6 @@ 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),
}
@@ -2,7 +2,6 @@
use inkwell::types::BasicType;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
/// The LLVM intrinsic functions, implemented in the LLVM back-end.
@@ -11,10 +10,6 @@ use crate::polkavm::context::function::declaration::Declaration as FunctionDecla
pub struct Intrinsics<'ctx> {
/// The trap.
pub trap: FunctionDeclaration<'ctx>,
/// The memory copy within the heap.
pub memory_copy: FunctionDeclaration<'ctx>,
/// The memory copy from a generic page.
pub memory_copy_from_generic: FunctionDeclaration<'ctx>,
/// Performs endianness swaps on i256 values
pub byte_swap_word: FunctionDeclaration<'ctx>,
/// Performs endianness swaps on i160 values
@@ -25,12 +20,6 @@ impl<'ctx> Intrinsics<'ctx> {
/// The corresponding intrinsic function name.
pub const FUNCTION_TRAP: &'static str = "llvm.trap";
/// The corresponding intrinsic function name.
pub const FUNCTION_MEMORY_COPY: &'static str = "llvm.memcpy.p1.p1.i256";
/// The corresponding intrinsic function name.
pub const FUNCTION_MEMORY_COPY_FROM_GENERIC: &'static str = "llvm.memcpy.p3.p1.i256";
/// The corresponding intrinsic function name.
pub const FUNCTION_BYTE_SWAP_WORD: &'static str = "llvm.bswap.i256";
@@ -43,12 +32,8 @@ impl<'ctx> Intrinsics<'ctx> {
module: &inkwell::module::Module<'ctx>,
) -> Self {
let void_type = llvm.void_type();
let bool_type = llvm.bool_type();
let word_type = llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32);
let address_type = llvm.custom_width_int_type(revive_common::BIT_LENGTH_ETH_ADDRESS as u32);
let _stack_field_pointer_type = llvm.ptr_type(AddressSpace::Stack.into());
let heap_field_pointer_type = llvm.ptr_type(AddressSpace::Heap.into());
let generic_byte_pointer_type = llvm.ptr_type(AddressSpace::Generic.into());
let trap = Self::declare(
llvm,
@@ -56,34 +41,6 @@ impl<'ctx> Intrinsics<'ctx> {
Self::FUNCTION_TRAP,
void_type.fn_type(&[], false),
);
let memory_copy = Self::declare(
llvm,
module,
Self::FUNCTION_MEMORY_COPY,
void_type.fn_type(
&[
heap_field_pointer_type.as_basic_type_enum().into(),
heap_field_pointer_type.as_basic_type_enum().into(),
word_type.as_basic_type_enum().into(),
bool_type.as_basic_type_enum().into(),
],
false,
),
);
let memory_copy_from_generic = Self::declare(
llvm,
module,
Self::FUNCTION_MEMORY_COPY_FROM_GENERIC,
void_type.fn_type(
&[
heap_field_pointer_type.as_basic_type_enum().into(),
generic_byte_pointer_type.as_basic_type_enum().into(),
word_type.as_basic_type_enum().into(),
bool_type.as_basic_type_enum().into(),
],
false,
),
);
let byte_swap_word = Self::declare(
llvm,
module,
@@ -99,8 +56,6 @@ impl<'ctx> Intrinsics<'ctx> {
Self {
trap,
memory_copy,
memory_copy_from_generic,
byte_swap_word,
byte_swap_eth_address,
}
@@ -130,20 +85,6 @@ impl<'ctx> Intrinsics<'ctx> {
let word_type = llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32);
match name {
name if name == Self::FUNCTION_MEMORY_COPY => vec![
llvm.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
llvm.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
word_type.as_basic_type_enum(),
],
name if name == Self::FUNCTION_MEMORY_COPY_FROM_GENERIC => vec![
llvm.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
llvm.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
word_type.as_basic_type_enum(),
],
name if name == Self::FUNCTION_BYTE_SWAP_WORD => vec![word_type.as_basic_type_enum()],
name if name == Self::FUNCTION_BYTE_SWAP_ETH_ADDRESS => {
vec![llvm
@@ -11,11 +11,6 @@ use crate::polkavm::context::function::Function;
/// The functions are automatically linked to the LLVM implementations if the signatures match.
#[derive(Debug)]
pub struct LLVMRuntime<'ctx> {
/// The LLVM personality function, used for exception handling.
pub personality: FunctionDeclaration<'ctx>,
/// The LLVM exception throwing function.
pub cxa_throw: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub shl: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
@@ -37,29 +32,6 @@ pub struct LLVMRuntime<'ctx> {
/// The corresponding LLVM runtime function.
pub sha3: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub system_request: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
//pub far_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub far_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub static_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub static_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub delegate_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub delegate_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mimic_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mimic_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub r#return: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
@@ -139,30 +111,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
module: &inkwell::module::Module<'ctx>,
optimizer: &Optimizer,
) -> Self {
let personality = Self::declare(
module,
Self::FUNCTION_PERSONALITY,
llvm.i32_type().fn_type(&[], false),
None,
);
let cxa_throw = Self::declare(
module,
Self::FUNCTION_CXA_THROW,
llvm.void_type().fn_type(
vec![
llvm.ptr_type(AddressSpace::Stack.into())
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_cxa_throw_attributes(llvm, cxa_throw);
let shl = Self::declare(
module,
Self::FUNCTION_SHL,
@@ -288,138 +236,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
false,
);
let system_request = Self::declare(
module,
Self::FUNCTION_SYSTEM_REQUEST,
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into(),
llvm.ptr_type(AddressSpace::Stack.into())
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, system_request, optimizer);
let external_call_arguments: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into();
crate::polkavm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT
+ crate::polkavm::EXTRA_ABI_DATA_SIZE
];
let mut mimic_call_arguments = external_call_arguments.clone();
mimic_call_arguments.push(
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into(),
);
let mut external_call_arguments_by_ref: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into(),
];
external_call_arguments_by_ref.extend::<Vec<inkwell::types::BasicMetadataTypeEnum>>(vec![
llvm.custom_width_int_type(
revive_common::BIT_LENGTH_WORD as u32
)
.as_basic_type_enum()
.into();
crate::polkavm::EXTRA_ABI_DATA_SIZE
]);
let mut mimic_call_arguments_by_ref = external_call_arguments_by_ref.clone();
mimic_call_arguments_by_ref.push(
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into(),
);
let external_call_result_type = llvm
.struct_type(
&[
llvm.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
llvm.bool_type().as_basic_type_enum(),
],
false,
)
.as_basic_type_enum();
//let far_call = Self::declare(
// module,
// Self::FUNCTION_FARCALL,
// external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
// Some(inkwell::module::Linkage::External),
//);
//Function::set_default_attributes(llvm, far_call, optimizer);
let static_call = Self::declare(
module,
Self::FUNCTION_STATICCALL,
external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, static_call, optimizer);
let delegate_call = Self::declare(
module,
Self::FUNCTION_DELEGATECALL,
external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, delegate_call, optimizer);
let mimic_call = Self::declare(
module,
Self::FUNCTION_MIMICCALL,
external_call_result_type.fn_type(mimic_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mimic_call, optimizer);
let far_call_byref = Self::declare(
module,
Self::FUNCTION_FARCALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, far_call_byref, optimizer);
let static_call_byref = Self::declare(
module,
Self::FUNCTION_STATICCALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, static_call_byref, optimizer);
let delegate_call_byref = Self::declare(
module,
Self::FUNCTION_DELEGATECALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, delegate_call_byref, optimizer);
let mimic_call_byref = Self::declare(
module,
Self::FUNCTION_MIMICCALL_BYREF,
external_call_result_type.fn_type(mimic_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mimic_call_byref, optimizer);
let r#return = Self::declare(
module,
Self::FUNCTION_RETURN,
@@ -454,9 +270,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
Function::set_default_attributes(llvm, revert, optimizer);
Self {
personality,
cxa_throw,
shl,
shr,
sar,
@@ -469,18 +282,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
sha3,
system_request,
//far_call,
static_call,
delegate_call,
mimic_call,
far_call_byref,
static_call_byref,
delegate_call_byref,
mimic_call_byref,
r#return,
revert,
}
@@ -506,42 +307,4 @@ impl<'ctx> LLVMRuntime<'ctx> {
value.set_linkage(inkwell::module::Linkage::External);
FunctionDeclaration::new(value.get_type(), value).into()
}
/// Modifies the external call function with `is_byref` and `is_system` modifiers.
pub fn modify(
&self,
function: FunctionDeclaration<'ctx>,
is_byref: bool,
) -> anyhow::Result<FunctionDeclaration<'ctx>> {
let modified = if
/*function == self.far_call {
match is_byref {
false => self.far_call,
true => self.far_call_byref,
}
} else if */
function == self.static_call {
match is_byref {
false => self.static_call,
true => self.static_call_byref,
}
} else if function == self.delegate_call {
match is_byref {
false => self.delegate_call,
true => self.delegate_call_byref,
}
} else if function == self.mimic_call {
match is_byref {
false => self.mimic_call,
true => self.mimic_call_byref,
}
} else {
anyhow::bail!(
"Cannot modify an external call function `{}`",
function.value.get_name().to_string_lossy()
);
};
Ok(modified)
}
}
@@ -7,7 +7,6 @@ pub mod intrinsics;
pub mod llvm_runtime;
pub mod r#return;
pub mod runtime;
pub mod vyper_data;
pub mod yul_data;
use std::collections::HashMap;
@@ -20,8 +19,6 @@ use crate::polkavm::context::pointer::Pointer;
use self::declaration::Declaration;
use self::evmla_data::EVMLAData;
use self::r#return::Return;
use self::runtime::Runtime;
use self::vyper_data::VyperData;
use self::yul_data::YulData;
/// The LLVM IR generator function.
@@ -48,8 +45,6 @@ pub struct Function<'ctx> {
yul_data: Option<YulData>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData<'ctx>>,
/// The Vyper data.
vyper_data: Option<VyperData>,
}
impl<'ctx> Function<'ctx> {
@@ -82,7 +77,6 @@ impl<'ctx> Function<'ctx> {
yul_data: None,
evmla_data: None,
vyper_data: None,
}
}
@@ -95,9 +89,9 @@ impl<'ctx> Function<'ctx> {
pub fn is_name_external(name: &str) -> bool {
name.starts_with("llvm.")
|| (name.starts_with("__")
&& name != Runtime::FUNCTION_ENTRY
&& name != Runtime::FUNCTION_DEPLOY_CODE
&& name != Runtime::FUNCTION_RUNTIME_CODE)
&& name != self::runtime::FUNCTION_ENTRY
&& name != self::runtime::FUNCTION_DEPLOY_CODE
&& name != self::runtime::FUNCTION_RUNTIME_CODE)
}
/// Checks whether the function is related to the near call ABI.
@@ -128,7 +122,7 @@ impl<'ctx> Function<'ctx> {
) {
for attribute_kind in attributes.into_iter() {
match attribute_kind {
Attribute::Memory => todo!("`memory` attributes are not yet implemented"),
Attribute::Memory => unimplemented!("`memory` attributes are not implemented"),
attribute_kind @ Attribute::AlwaysInline if force => {
let is_optimize_none_set = declaration
.value
@@ -330,29 +324,6 @@ impl<'ctx> Function<'ctx> {
.expect("The EVM data must have been initialized")
}
/// Sets the Vyper data.
pub fn set_vyper_data(&mut self, data: VyperData) {
self.vyper_data = Some(data);
}
/// Returns the Vyper data reference.
/// # Panics
/// If the Vyper data has not been initialized.
pub fn vyper(&self) -> &VyperData {
self.vyper_data
.as_ref()
.expect("The Vyper data must have been initialized")
}
/// Returns the Vyper data mutable reference.
/// # Panics
/// If the Vyper data has not been initialized.
pub fn vyper_mut(&mut self) -> &mut VyperData {
self.vyper_data
.as_mut()
.expect("The Vyper data must have been initialized")
}
/// Sets the Yul data.
pub fn set_yul_data(&mut self, data: YulData) {
self.yul_data = Some(data);
@@ -1,244 +0,0 @@
//! The `default_call` function.
use inkwell::types::BasicType;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::function::llvm_runtime::LLVMRuntime;
use crate::polkavm::context::function::Function;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// The `default_call` function.
/// Generates a default contract call, if the `msg.value` is zero.
#[derive(Debug)]
pub struct DefaultCall {
/// The name of the inner function used for the low-level call.
inner_name: String,
/// The function name with the low-level function name as an element.
name: String,
}
#[allow(unused)]
impl DefaultCall {
/// The gas argument index.
pub const ARGUMENT_INDEX_GAS: usize = 0;
/// The address argument index.
pub const ARGUMENT_INDEX_ADDRESS: usize = 1;
/// The input offset argument index.
pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 2;
/// The input length argument index.
pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 3;
/// The output offset argument index.
pub const ARGUMENT_INDEX_OUTPUT_OFFSET: usize = 4;
/// The output length argument index.
pub const ARGUMENT_INDEX_OUTPUT_LENGTH: usize = 5;
/// A shortcut constructor.
pub fn new(call_function: FunctionDeclaration) -> Self {
let inner_name = call_function.value.get_name().to_string_lossy().to_string();
let name = Self::name(call_function);
Self { inner_name, name }
}
/// Returns the function name.
pub fn name(call_function: FunctionDeclaration) -> String {
let suffix = match call_function.value.get_name().to_string_lossy() {
name if name == LLVMRuntime::FUNCTION_FARCALL => "far",
name if name == LLVMRuntime::FUNCTION_STATICCALL => "static",
name if name == LLVMRuntime::FUNCTION_DELEGATECALL => "delegate",
name => panic!("Invalid low-level call inner function `{name}`"),
};
format!("__default_{suffix}_call")
}
/// Returns the low-level call function.
fn inner_function<'ctx, D>(&self, context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
match self.inner_name.as_str() {
//name if name == LLVMRuntime::FUNCTION_FARCALL => context.llvm_runtime().far_call,
name if name == LLVMRuntime::FUNCTION_STATICCALL => context.llvm_runtime().static_call,
name if name == LLVMRuntime::FUNCTION_DELEGATECALL => {
context.llvm_runtime().delegate_call
}
name => panic!("Invalid low-level call inner function `{name}`"),
}
}
}
impl<D> WriteLLVM<D> for DefaultCall
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type = context.function_type(
vec![
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
],
1,
false,
);
let function = context.add_function(
self.name.as_str(),
function_type,
1,
Some(inkwell::module::Linkage::Private),
)?;
Function::set_frontend_runtime_attributes(
context.llvm,
function.borrow().declaration(),
&context.optimizer,
);
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(self.name.as_str())?;
/*
let gas = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_GAS)
.into_int_value();
let address = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_ADDRESS)
.into_int_value();
let input_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET)
.into_int_value();
let input_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH)
.into_int_value();
let output_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_OFFSET)
.into_int_value();
let output_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_LENGTH)
.into_int_value();
*/
context.set_basic_block(context.current_function().borrow().entry_block());
let status_code_result_pointer = context.build_alloca(
context.word_type(),
"contract_call_result_status_code_pointer",
);
/*
context.build_store(status_code_result_pointer, context.field_const(0));
let abi_data = crate::polkavm::utils::abi_data(
context,
input_offset,
input_length,
Some(gas),
AddressSpace::Heap,
false,
)?
.into_int_value();
let result = context
.build_call(
self.inner_function(context),
crate::polkavm::utils::external_call_arguments(
context,
abi_data.as_basic_value_enum(),
address,
vec![],
None,
)
.as_slice(),
"contract_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
result.into_struct_value(),
0,
"contract_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_abi_data_casted = result_abi_data_pointer.cast(context.field_type());
let result_status_code_boolean = context
.builder()
.build_extract_value(
result.into_struct_value(),
1,
"contract_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"contract_call_external_result_status_code",
)?;
context.build_store(status_code_result_pointer, result_status_code);
let source = result_abi_data_casted;
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
output_offset,
"contract_call_destination",
);
context.build_memcpy_return_data(
context.intrinsics().memory_copy_from_generic,
destination,
source,
output_length,
"contract_call_memcpy_from_child",
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::polkavm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::polkavm::GLOBAL_RETURN_DATA_SIZE,
);
*/
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
let status_code_result =
context.build_load(status_code_result_pointer, "contract_call_status_code")?;
context.build_return(Some(&status_code_result));
Ok(())
}
}
@@ -2,10 +2,8 @@
use std::marker::PhantomData;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
@@ -47,7 +45,7 @@ where
let function_type =
context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0, false);
context.add_function(
Runtime::FUNCTION_DEPLOY_CODE,
runtime::FUNCTION_DEPLOY_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
@@ -57,26 +55,10 @@ where
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Runtime::FUNCTION_DEPLOY_CODE)?;
context.set_current_function(runtime::FUNCTION_DEPLOY_CODE)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Deploy);
if let Some(vyper) = context.vyper_data.as_ref() {
for index in 0..vyper.immutables_size() / revive_common::BYTE_LENGTH_WORD {
let offset = (crate::polkavm::r#const::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
as usize)
+ (1 + index) * 2 * revive_common::BYTE_LENGTH_WORD;
let value = index * revive_common::BYTE_LENGTH_WORD;
let pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.word_type(),
context.word_const(offset as u64),
"immutable_index_initializer",
);
context.build_store(pointer, context.word_const(value as u64))?;
}
}
self.inner.into_llvm(context)?;
match context
@@ -1,324 +0,0 @@
//! The `deployer_call` function.
use inkwell::types::BasicType;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::Function;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// The `deployer_call` function.
/// Calls the deployer system contract, which returns the newly deployed contract address or 0.
/// The address is returned in the first 32-byte word of the return data. If it is 0, the 0 is
/// returned. If the entire call has failed, there is also a 0 returned.
#[derive(Debug)]
pub struct DeployerCall {
/// The address space where the calldata is allocated.
/// Solidity uses the ordinary heap. Vyper uses the auxiliary heap.
address_space: AddressSpace,
}
impl DeployerCall {
/// The default function name.
pub const FUNCTION_NAME: &'static str = "__deployer_call";
/// The value argument index.
pub const ARGUMENT_INDEX_VALUE: usize = 0;
/// The input offset argument index.
pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 1;
/// The input length argument index.
pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 2;
/// The signature hash argument index.
pub const ARGUMENT_INDEX_SIGNATURE_HASH: usize = 3;
/// The salt argument index.
pub const ARGUMENT_INDEX_SALT: usize = 4;
/// A shortcut constructor.
pub fn new(address_space: AddressSpace) -> Self {
Self { address_space }
}
}
impl<D> WriteLLVM<D> for DeployerCall
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type = context.function_type(
vec![
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
context.word_type().as_basic_type_enum(),
],
1,
false,
);
let function = context.add_function(
Self::FUNCTION_NAME,
function_type,
1,
Some(inkwell::module::Linkage::External),
)?;
Function::set_frontend_runtime_attributes(
context.llvm,
function.borrow().declaration(),
&context.optimizer,
);
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Self::FUNCTION_NAME)?;
let value = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_VALUE)
.into_int_value();
let input_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET)
.into_int_value();
let input_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH)
.into_int_value();
let signature_hash = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_SIGNATURE_HASH)
.into_int_value();
let salt = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_SALT)
.into_int_value();
let error_block = context.append_basic_block("deployer_call_error_block");
let success_block = context.append_basic_block("deployer_call_success_block");
let value_zero_block = context.append_basic_block("deployer_call_value_zero_block");
let value_non_zero_block = context.append_basic_block("deployer_call_value_non_zero_block");
let value_join_block = context.append_basic_block("deployer_call_value_join_block");
context.set_basic_block(context.current_function().borrow().entry_block());
let _abi_data = crate::polkavm::utils::abi_data(
context,
input_offset,
input_length,
None,
self.address_space,
true,
)?;
let signature_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.word_type(),
input_offset,
"deployer_call_signature_pointer",
);
context.build_store(signature_pointer, signature_hash)?;
let salt_offset = context.builder().build_int_add(
input_offset,
context.word_const(revive_common::BYTE_LENGTH_X32 as u64),
"deployer_call_salt_offset",
)?;
let salt_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.word_type(),
salt_offset,
"deployer_call_salt_pointer",
);
context.build_store(salt_pointer, salt)?;
let arguments_offset_offset = context.builder().build_int_add(
salt_offset,
context.word_const((revive_common::BYTE_LENGTH_WORD * 2) as u64),
"deployer_call_arguments_offset_offset",
)?;
let arguments_offset_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.word_type(),
arguments_offset_offset,
"deployer_call_arguments_offset_pointer",
);
context.build_store(
arguments_offset_pointer,
context.word_const(
(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE
- (revive_common::BYTE_LENGTH_X32 + revive_common::BYTE_LENGTH_WORD))
as u64,
),
)?;
let arguments_length_offset = context.builder().build_int_add(
arguments_offset_offset,
context.word_const(revive_common::BYTE_LENGTH_WORD as u64),
"deployer_call_arguments_length_offset",
)?;
let arguments_length_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.word_type(),
arguments_length_offset,
"deployer_call_arguments_length_pointer",
);
let arguments_length_value = context.builder().build_int_sub(
input_length,
context.word_const(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE as u64),
"deployer_call_arguments_length",
)?;
context.build_store(arguments_length_pointer, arguments_length_value)?;
let result_pointer =
context.build_alloca(context.word_type(), "deployer_call_result_pointer");
context.build_store(result_pointer, context.word_const(0))?;
let deployer_call_result_type = context.structure_type(&[
context
.llvm()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
context.bool_type().as_basic_type_enum(),
]);
let deployer_call_result_pointer =
context.build_alloca(deployer_call_result_type, "deployer_call_result_pointer");
context.build_store(
deployer_call_result_pointer,
deployer_call_result_type.const_zero(),
)?;
let is_value_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
value,
context.word_const(0),
"deployer_call_is_value_zero",
)?;
context.build_conditional_branch(is_value_zero, value_zero_block, value_non_zero_block)?;
context.set_basic_block(value_zero_block);
//let deployer_call_result = context
// .build_call(
// context.llvm_runtime().far_call,
// crate::polkavm::utils::external_call_arguments(
// context,
// abi_data,
// context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()),
// vec![],
// None,
// )
// .as_slice(),
// "deployer_call_ordinary",
// )
// .expect("Always returns a value");
//context.build_store(deployer_call_result_pointer, deployer_call_result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_non_zero_block);
//let deployer_call_result = context
// .build_call(
// context.llvm_runtime().far_call,
// crate::polkavm::utils::external_call_arguments(
// context,
// abi_data.as_basic_value_enum(),
// context.field_const(zkevm_opcode_defs::ADDRESS_MSG_VALUE.into()),
// vec![
// value,
// context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()),
// context.field_const(u64::from(crate::polkavm::r#const::SYSTEM_CALL_BIT)),
// ],
// None,
// )
// .as_slice(),
// "deployer_call_system",
// )
// .expect("Always returns a value");
//context.build_store(deployer_call_result_pointer, deployer_call_result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_join_block);
let result_abi_data_pointer = context.build_gep(
deployer_call_result_pointer,
&[
context.word_const(0),
context
.integer_type(revive_common::BIT_LENGTH_X32)
.const_zero(),
],
context
.llvm()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
"deployer_call_result_abi_data_pointer",
);
let result_abi_data =
context.build_load(result_abi_data_pointer, "deployer_call_result_abi_data")?;
let result_status_code_pointer = context.build_gep(
deployer_call_result_pointer,
&[
context.word_const(0),
context
.integer_type(revive_common::BIT_LENGTH_X32)
.const_int(1, false),
],
context.bool_type().as_basic_type_enum(),
"contract_call_external_result_status_code_pointer",
);
let result_status_code_boolean = context
.build_load(
result_status_code_pointer,
"contract_call_external_result_status_code_boolean",
)?
.into_int_value();
context.build_conditional_branch(result_status_code_boolean, success_block, error_block)?;
context.set_basic_block(success_block);
let result_abi_data_pointer = Pointer::new(
context.word_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let address_or_status_code = context.build_load(
result_abi_data_pointer,
"deployer_call_address_or_status_code",
)?;
context.build_store(result_pointer, address_or_status_code)?;
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(error_block);
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::polkavm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::polkavm::GLOBAL_RETURN_DATA_SIZE,
);
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
let result = context.build_load(result_pointer, "deployer_call_result")?;
context.build_return(Some(&result));
Ok(())
}
}
@@ -1,14 +1,14 @@
//! The entry function.
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::Context;
use crate::polkavm::r#const::*;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
use crate::PolkaVMPointer as Pointer;
/// The entry function.
/// The function is a wrapper managing the runtime and deploy code calling logic.
@@ -23,8 +23,11 @@ impl Entry {
/// The number of mandatory arguments.
pub const MANDATORY_ARGUMENTS_COUNT: usize = 2;
/// Reserve 1kb for calldata.
pub const MAX_CALLDATA_SIZE: usize = 1024;
/// Reserve 1mb for calldata.
pub const MAX_CALLDATA_SIZE: usize = 1024 * 1024;
/// Reserve 1mb for returndata.
pub const MAX_RETURNDATA_SIZE: usize = 1024 * 1024;
/// Initializes the global variables.
/// The pointers are not initialized, because it's not possible to create a null pointer.
@@ -40,9 +43,17 @@ impl Entry {
calldata_type.get_undef(),
);
let returndata_type = context.array_type(context.byte_type(), Self::MAX_RETURNDATA_SIZE);
context.set_global(
crate::polkavm::GLOBAL_RETURN_DATA_POINTER,
returndata_type,
AddressSpace::Stack,
returndata_type.get_undef(),
);
context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER,
context.llvm().ptr_type(AddressSpace::Generic.into()),
context.llvm().ptr_type(AddressSpace::Heap.into()),
AddressSpace::Stack,
context.xlen_type().get_undef(),
);
@@ -61,9 +72,9 @@ impl Entry {
);
context.set_global(
crate::polkavm::GLOBAL_RETURN_DATA_SIZE,
context.word_type(),
context.xlen_type(),
AddressSpace::Stack,
context.word_const(0),
context.xlen_type().const_zero().as_basic_value_enum(),
);
context.set_global(
@@ -142,7 +153,7 @@ impl Entry {
context.integer_const(crate::polkavm::XLEN, Self::MAX_CALLDATA_SIZE as u64),
)?;
context.build_runtime_call(
runtime_api::INPUT,
runtime_api::imports::INPUT,
&[input_pointer_casted.into(), length_pointer_casted.into()],
);
@@ -162,27 +173,6 @@ impl Entry {
calldata_size_casted,
);
// Store calldata end pointer
let input_pointer = Pointer::new(
input_pointer.get_type(),
AddressSpace::Generic,
input_pointer,
);
let calldata_end_pointer = context.build_gep(
input_pointer,
&[calldata_size_casted],
context
.llvm()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
"return_data_abi_initializer",
);
context.write_abi_pointer(
calldata_end_pointer,
crate::polkavm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_pointer(calldata_end_pointer, crate::polkavm::GLOBAL_ACTIVE_POINTER);
Ok(())
}
@@ -215,21 +205,21 @@ impl Entry {
let deploy_code = context
.functions
.get(Runtime::FUNCTION_DEPLOY_CODE)
.get(runtime::FUNCTION_DEPLOY_CODE)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Contract deploy code not found"))?;
let runtime_code = context
.functions
.get(Runtime::FUNCTION_RUNTIME_CODE)
.get(runtime::FUNCTION_RUNTIME_CODE)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Contract runtime code not found"))?;
context.set_basic_block(deploy_code_call_block);
context.build_invoke(deploy_code.borrow().declaration, &[], "deploy_code_call");
context.build_call(deploy_code.borrow().declaration, &[], "deploy_code_call");
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(runtime_code_call_block);
context.build_invoke(runtime_code.borrow().declaration, &[], "runtime_code_call");
context.build_call(runtime_code.borrow().declaration, &[], "runtime_code_call");
context.build_unconditional_branch(context.current_function().borrow().return_block());
Ok(())
@@ -243,9 +233,9 @@ where
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let entry_arguments = vec![context.bool_type().as_basic_type_enum()];
let entry_function_type = context.function_type(entry_arguments, 0, false);
context.add_function(Runtime::FUNCTION_ENTRY, entry_function_type, 0, None)?;
context.add_function(runtime::FUNCTION_ENTRY, entry_function_type, 0, None)?;
for symbol in runtime_api::EXPORTS {
for symbol in runtime_api::exports::EXPORTS {
context.declare_extern_function(symbol)?;
}
@@ -257,7 +247,7 @@ where
/// The `entry` function loads calldata, sets globals and calls the runtime or deploy code.
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
let entry = context
.get_function(Runtime::FUNCTION_ENTRY)
.get_function(runtime::FUNCTION_ENTRY)
.expect("the entry function should already be declared")
.borrow()
.declaration;
@@ -268,7 +258,7 @@ where
true,
);
context.set_current_function(runtime_api::DEPLOY)?;
context.set_current_function(runtime_api::exports::DEPLOY)?;
context.set_basic_block(context.current_function().borrow().entry_block());
assert!(context
@@ -278,7 +268,7 @@ where
context.set_basic_block(context.current_function().borrow().return_block);
context.build_unreachable();
context.set_current_function(runtime_api::CALL)?;
context.set_current_function(runtime_api::exports::CALL)?;
context.set_basic_block(context.current_function().borrow().entry_block());
assert!(context
@@ -288,7 +278,7 @@ where
context.set_basic_block(context.current_function().borrow().return_block);
context.build_unreachable();
context.set_current_function(Runtime::FUNCTION_ENTRY)?;
context.set_current_function(runtime::FUNCTION_ENTRY)?;
context.set_basic_block(context.current_function().borrow().entry_block());
Self::initialize_globals(context)?;
@@ -1,90 +1,14 @@
//! The front-end runtime functions.
pub mod default_call;
pub mod deploy_code;
pub mod deployer_call;
pub mod entry;
pub mod runtime_code;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
/// The main entry function name.
pub const FUNCTION_ENTRY: &str = "__entry";
use self::default_call::DefaultCall;
use self::deployer_call::DeployerCall;
/// The deploy code function name.
pub const FUNCTION_DEPLOY_CODE: &str = "__deploy";
/// The front-end runtime functions.
#[derive(Debug, Clone)]
pub struct Runtime {
/// The address space where the calldata is allocated.
/// Solidity uses the ordinary heap. Vyper uses the auxiliary heap.
address_space: AddressSpace,
}
impl Runtime {
/// The main entry function name.
pub const FUNCTION_ENTRY: &'static str = "__entry";
/// The deploy code function name.
pub const FUNCTION_DEPLOY_CODE: &'static str = "__deploy";
/// The runtime code function name.
pub const FUNCTION_RUNTIME_CODE: &'static str = "__runtime";
/// A shortcut constructor.
pub fn new(address_space: AddressSpace) -> Self {
Self { address_space }
}
/// Returns the corresponding runtime function.
pub fn default_call<'ctx, D>(
context: &Context<'ctx, D>,
call_function: FunctionDeclaration<'ctx>,
) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
context
.get_function(DefaultCall::name(call_function).as_str())
.expect("Always exists")
.borrow()
.declaration()
}
/// Returns the corresponding runtime function.
pub fn deployer_call<'ctx, D>(context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
context
.get_function(DeployerCall::FUNCTION_NAME)
.expect("Always exists")
.borrow()
.declaration()
}
}
impl<D> WriteLLVM<D> for Runtime
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
//DefaultCall::new(context.llvm_runtime().far_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().static_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).declare(context)?;
DeployerCall::new(self.address_space).declare(context)?;
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
//DefaultCall::new(context.llvm_runtime().far_call).into_llvm(context)?;
DefaultCall::new(context.llvm_runtime().static_call).into_llvm(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).into_llvm(context)?;
DeployerCall::new(self.address_space).into_llvm(context)?;
Ok(())
}
}
/// The runtime code function name.
pub const FUNCTION_RUNTIME_CODE: &str = "__runtime";
@@ -3,7 +3,7 @@
use std::marker::PhantomData;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::function::runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
@@ -45,7 +45,7 @@ where
let function_type =
context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0, false);
context.add_function(
Runtime::FUNCTION_RUNTIME_CODE,
runtime::FUNCTION_RUNTIME_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
@@ -55,7 +55,7 @@ where
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Runtime::FUNCTION_RUNTIME_CODE)?;
context.set_current_function(runtime::FUNCTION_RUNTIME_CODE)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Runtime);
@@ -1,41 +0,0 @@
//! The LLVM function Vyper data.
use std::collections::HashMap;
/// The LLVM function Vyper data.
/// Describes some data that is only relevant to Vyper.
#[derive(Debug)]
pub struct VyperData {
/// The block-local variables. They are still allocated at the beginning of the function,
/// but their parent block must be known in order to pass the implicit arguments thereto.
/// Is only used by the Vyper LLL IR compiler.
label_arguments: HashMap<String, Vec<String>>,
}
impl Default for VyperData {
fn default() -> Self {
Self {
label_arguments: HashMap::with_capacity(Self::LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY),
}
}
}
impl VyperData {
/// The label arguments hashmap default capacity.
const LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY: usize = 16;
/// A shortcut constructor.
pub fn new() -> Self {
Self::default()
}
/// Returns the list of a Vyper label arguments.
pub fn label_arguments(&self, label_name: &str) -> Option<Vec<String>> {
self.label_arguments.get(label_name).cloned()
}
/// Inserts arguments for the specified label.
pub fn insert_label_arguments(&mut self, label_name: String, arguments: Vec<String>) {
self.label_arguments.insert(label_name, arguments);
}
}
@@ -42,9 +42,6 @@ impl<'ctx> Global<'ctx> {
.value
.set_visibility(inkwell::GlobalVisibility::Default);
global.value.set_externally_initialized(false);
if let AddressSpace::Code = address_space {
global.value.set_constant(true);
}
if !r#type.is_pointer_type() {
global.value.set_initializer(&initializer);
} else {
+91 -321
View File
@@ -12,7 +12,6 @@ pub mod global;
pub mod r#loop;
pub mod pointer;
pub mod solidity_data;
pub mod vyper_data;
pub mod yul_data;
#[cfg(test)]
@@ -37,7 +36,6 @@ use self::address_space::AddressSpace;
use self::attribute::Attribute;
use self::build::Build;
use self::code_type::CodeType;
// TODO
// use self::debug_info::DebugInfo;
use self::evmla_data::EVMLAData;
use self::function::declaration::Declaration as FunctionDeclaration;
@@ -49,7 +47,6 @@ use self::global::Global;
use self::pointer::Pointer;
use self::r#loop::Loop;
use self::solidity_data::SolidityData;
use self::vyper_data::VyperData;
use self::yul_data::YulData;
/// The LLVM IR generator context.
@@ -99,8 +96,6 @@ where
yul_data: Option<YulData>,
/// The EVM legacy assembly data.
evmla_data: Option<EVMLAData<'ctx>>,
/// The Vyper data.
vyper_data: Option<VyperData>,
}
impl<'ctx, D> Context<'ctx, D>
@@ -136,10 +131,12 @@ where
module: &inkwell::module::Module<'ctx>,
) {
module
.link_in_module(pallet_contracts_pvm_llapi::module(llvm, "polkavm_guest").unwrap())
.link_in_module(
pallet_contracts_pvm_llapi::polkavm_guest::module(llvm, "polkavm_guest").unwrap(),
)
.expect("the PolkaVM guest API module should be linkable");
for export in runtime_api::EXPORTS {
for export in runtime_api::exports::EXPORTS {
module
.get_function(export)
.expect("should be declared")
@@ -149,7 +146,7 @@ where
);
}
for import in runtime_api::IMPORTS {
for import in runtime_api::imports::IMPORTS {
module
.get_function(import)
.expect("should be declared")
@@ -164,7 +161,7 @@ where
size: u32,
) {
module
.link_in_module(pallet_contracts_pvm_llapi::min_stack_size(
.link_in_module(pallet_contracts_pvm_llapi::polkavm_guest::min_stack_size(
llvm,
"polkavm_stack_size",
size,
@@ -222,7 +219,6 @@ where
solidity_data: None,
yul_data: None,
evmla_data: None,
vyper_data: None,
}
}
@@ -431,8 +427,6 @@ where
let entry_block = self.llvm.append_basic_block(value, "entry");
let return_block = self.llvm.append_basic_block(value, "return");
value.set_personality_function(self.llvm_runtime.personality.value);
let r#return = match return_values_length {
0 => FunctionReturn::none(),
1 => {
@@ -522,9 +516,6 @@ where
/// Compiles a contract dependency, if the dependency manager is set.
pub fn compile_dependency(&mut self, name: &str) -> anyhow::Result<String> {
if let Some(vyper_data) = self.vyper_data.as_mut() {
vyper_data.set_is_forwarder_used();
}
self.dependency_manager
.to_owned()
.ok_or_else(|| anyhow::anyhow!("The dependency manager is unset"))
@@ -682,15 +673,13 @@ where
self.build_byte_swap(value)
}
AddressSpace::TransientStorage => todo!(),
AddressSpace::Storage => {
AddressSpace::Storage | AddressSpace::TransientStorage => {
let storage_key_value = self.builder().build_ptr_to_int(
pointer.value,
self.word_type(),
"storage_ptr_to_int",
)?;
let storage_key_pointer =
self.build_alloca(storage_key_value.get_type(), "storage_key");
let storage_key_pointer = self.build_alloca(self.word_type(), "storage_key");
let storage_key_pointer_casted = self.builder().build_ptr_to_int(
storage_key_pointer.value,
self.xlen_type(),
@@ -702,9 +691,12 @@ where
let (storage_value_pointer, storage_value_length_pointer) = self
.build_stack_parameter(revive_common::BIT_LENGTH_WORD, "storage_value_pointer");
let transient = pointer.address_space == AddressSpace::TransientStorage;
self.build_runtime_call(
runtime_api::GET_STORAGE,
runtime_api::imports::GET_STORAGE,
&[
self.xlen_type().const_int(transient as u64, false).into(),
storage_key_pointer_casted.into(),
self.integer_const(crate::polkavm::XLEN, 32).into(),
storage_value_pointer.to_int(self).into(),
@@ -717,13 +709,7 @@ where
// If a key doesn't exist the "zero" value is returned.
self.build_load(storage_value_pointer, "storage_value_load")
.and_then(|value| self.build_byte_swap(value))
}
AddressSpace::Code | AddressSpace::HeapAuxiliary => todo!(),
AddressSpace::Generic => Ok(self.build_byte_swap(self.build_load(
pointer.address_space_cast(self, AddressSpace::Stack, &format!("{}_cast", name))?,
name,
)?)?),
AddressSpace::Stack => {
let value = self
.builder()
@@ -774,27 +760,25 @@ where
.set_alignment(revive_common::BYTE_LENGTH_BYTE as u32)
.expect("Alignment is valid");
}
AddressSpace::TransientStorage => todo!(),
AddressSpace::Storage => {
AddressSpace::Storage | AddressSpace::TransientStorage => {
assert_eq!(
value.as_basic_value_enum().get_type(),
self.word_type().as_basic_type_enum()
);
let storage_key_value = self.builder().build_ptr_to_int(
pointer.value,
self.word_type(),
"storage_ptr_to_int",
)?;
let storage_key_pointer =
self.build_alloca(storage_key_value.get_type(), "storage_key");
let storage_value_value = self
.build_byte_swap(value.as_basic_value_enum())?
.into_int_value();
let storage_value_pointer =
self.build_alloca(storage_value_value.get_type(), "storage_value");
let storage_key_pointer = self.build_alloca(self.word_type(), "storage_key");
let storage_key_pointer_casted = self.builder().build_ptr_to_int(
storage_key_pointer.value,
self.xlen_type(),
"storage_key_pointer_casted",
)?;
let storage_value_pointer = self.build_alloca(self.word_type(), "storage_value");
let storage_value_pointer_casted = self.builder().build_ptr_to_int(
storage_value_pointer.value,
self.xlen_type(),
@@ -804,11 +788,14 @@ where
self.builder()
.build_store(storage_key_pointer.value, storage_key_value)?;
self.builder()
.build_store(storage_value_pointer.value, storage_value_value)?;
.build_store(storage_value_pointer.value, value)?;
let transient = pointer.address_space == AddressSpace::TransientStorage;
self.build_runtime_call(
runtime_api::SET_STORAGE,
runtime_api::imports::SET_STORAGE,
&[
self.xlen_type().const_int(transient as u64, false).into(),
storage_key_pointer_casted.into(),
self.integer_const(crate::polkavm::XLEN, 32).into(),
storage_value_pointer_casted.into(),
@@ -816,11 +803,6 @@ where
],
);
}
AddressSpace::Code | AddressSpace::HeapAuxiliary => {}
AddressSpace::Generic => self.build_store(
pointer.address_space_cast(self, AddressSpace::Stack, "cast")?,
self.build_byte_swap(value.as_basic_value_enum())?,
)?,
AddressSpace::Stack => {
let instruction = self.builder.build_store(pointer.value, value).unwrap();
instruction
@@ -955,166 +937,38 @@ where
call_site_value.try_as_basic_value().left()
}
/// Builds an invoke.
/// Is defaulted to a call if there is no global exception handler.
pub fn build_invoke(
&self,
function: FunctionDeclaration<'ctx>,
arguments: &[inkwell::values::BasicValueEnum<'ctx>],
name: &str,
) -> Option<inkwell::values::BasicValueEnum<'ctx>> {
if !self
.functions
.contains_key(Function::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER)
{
return self.build_call(function, arguments, name);
}
let return_pointer = if let Some(r#type) = function.r#type.get_return_type() {
let pointer = self.build_alloca(r#type, "invoke_return_pointer");
self.build_store(pointer, r#type.const_zero()).unwrap();
Some(pointer)
} else {
None
};
let success_block = self.append_basic_block("invoke_success_block");
let catch_block = self.append_basic_block("invoke_catch_block");
let current_block = self.basic_block();
self.set_basic_block(catch_block);
let landing_pad_type = self.structure_type(&[
self.llvm()
.ptr_type(AddressSpace::Stack.into())
.as_basic_type_enum(),
self.integer_type(revive_common::BIT_LENGTH_X32)
.as_basic_type_enum(),
]);
self.builder
.build_landing_pad(
landing_pad_type,
self.llvm_runtime.personality.value,
&[self
.llvm()
.ptr_type(AddressSpace::Stack.into())
.const_zero()
.as_basic_value_enum()],
false,
"invoke_catch_landing",
)
.unwrap();
crate::polkavm::utils::throw(self);
self.set_basic_block(current_block);
let call_site_value = self
.builder
.build_indirect_invoke(
function.r#type,
function.value.as_global_value().as_pointer_value(),
arguments,
success_block,
catch_block,
name,
)
.unwrap();
self.modify_call_site_value(arguments, call_site_value, function);
self.set_basic_block(success_block);
if let (Some(return_pointer), Some(mut return_value)) =
(return_pointer, call_site_value.try_as_basic_value().left())
{
if let Some(return_type) = function.r#type.get_return_type() {
if return_type.is_pointer_type() {
return_value = self
.builder()
.build_int_to_ptr(
return_value.into_int_value(),
return_type.into_pointer_type(),
format!("{name}_invoke_return_pointer_casted").as_str(),
)
.unwrap()
.as_basic_value_enum();
}
}
self.build_store(return_pointer, return_value).unwrap();
}
return_pointer.map(|pointer| self.build_load(pointer, "invoke_result").unwrap())
}
/// Builds an invoke of local call covered with an exception handler.
/// Yul does not the exception handling, so the user can declare a special handling function
/// called (see constant `ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER`. If the enclosed function
/// panics, the control flow will be transferred to the exception handler.
pub fn build_invoke_near_call_abi(
&self,
_function: FunctionDeclaration<'ctx>,
_arguments: Vec<inkwell::values::BasicValueEnum<'ctx>>,
_name: &str,
) -> Option<inkwell::values::BasicValueEnum<'ctx>> {
unimplemented!()
}
/// Builds a memory copy call.
/// Sets the alignment to `1`, since all non-stack memory pages have such alignment.
pub fn build_memcpy(
&self,
_function: FunctionDeclaration<'ctx>,
destination: Pointer<'ctx>,
source: Pointer<'ctx>,
size: inkwell::values::IntValue<'ctx>,
_name: &str,
) -> anyhow::Result<()> {
let _ = self
.builder()
.build_memcpy(destination.value, 1, source.value, 1, size)?;
Ok(())
}
/// Builds a memory copy call for the return data.
/// Sets the output length to `min(output_length, return_data_size` and calls the default
/// generic page memory copy builder.
pub fn build_memcpy_return_data(
&self,
function: FunctionDeclaration<'ctx>,
destination: Pointer<'ctx>,
source: Pointer<'ctx>,
size: inkwell::values::IntValue<'ctx>,
name: &str,
) -> anyhow::Result<()> {
let pointer_casted = self.builder.build_ptr_to_int(
source.value,
self.word_type(),
format!("{name}_pointer_casted").as_str(),
)?;
let return_data_size_shifted = self.builder.build_right_shift(
pointer_casted,
self.word_const((revive_common::BIT_LENGTH_X32 * 3) as u64),
false,
format!("{name}_return_data_size_shifted").as_str(),
)?;
let return_data_size_truncated = self.builder.build_and(
return_data_size_shifted,
self.word_const(u32::MAX as u64),
format!("{name}_return_data_size_truncated").as_str(),
)?;
let is_return_data_size_lesser = self.builder.build_int_compare(
inkwell::IntPredicate::ULT,
return_data_size_truncated,
size,
format!("{name}_is_return_data_size_lesser").as_str(),
)?;
let min_size = self
.builder
.build_select(
is_return_data_size_lesser,
return_data_size_truncated,
size,
format!("{name}_min_size").as_str(),
)?
.into_int_value();
let size = self.safe_truncate_int_to_xlen(size)?;
self.build_memcpy(function, destination, source, min_size, name)?;
let destination = if destination.address_space == AddressSpace::Heap {
self.build_heap_gep(
self.builder()
.build_ptr_to_int(destination.value, self.xlen_type(), name)?,
size,
)?
} else {
destination
};
let source = if source.address_space == AddressSpace::Heap {
self.build_heap_gep(
self.builder()
.build_ptr_to_int(source.value, self.xlen_type(), name)?,
size,
)?
} else {
source
};
self.builder()
.build_memmove(destination.value, 1, source.value, 1, size)?;
Ok(())
}
@@ -1139,26 +993,13 @@ where
self.builder.build_unreachable().unwrap();
}
/// Builds a long contract exit sequence.
/// The deploy code does not return the runtime code like in EVM. Instead, it returns some
/// additional contract metadata, e.g. the array of immutables.
/// The deploy code uses the auxiliary heap for the return, because otherwise it is not possible
/// to allocate memory together with the Yul allocator safely.
/// Builds a contract exit sequence.
pub fn build_exit(
&self,
flags: inkwell::values::IntValue<'ctx>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> {
// TODO:
//let return_forward_mode = if self.code_type() == Some(CodeType::Deploy)
// && return_function == self.llvm_runtime().r#return
//{
// zkevm_opcode_defs::RetForwardPageType::UseAuxHeap
//} else {
// zkevm_opcode_defs::RetForwardPageType::UseHeap
//};
let offset_truncated = self.safe_truncate_int_to_xlen(offset)?;
let length_truncated = self.safe_truncate_int_to_xlen(length)?;
let offset_into_heap = self.build_heap_gep(offset_truncated, length_truncated)?;
@@ -1171,7 +1012,7 @@ where
)?;
self.build_runtime_call(
runtime_api::RETURN,
runtime_api::imports::RETURN,
&[flags.into(), offset_pointer.into(), length_pointer.into()],
);
self.build_unreachable();
@@ -1185,6 +1026,12 @@ where
/// However, we still protect against this possibility here. Heap index
/// offsets are generally untrusted and potentially represent valid
/// (but wrong) pointers when truncated.
///
/// TODO: Splitting up into a dedicated function
/// could potentially decrease code sizes (LLVM can still decide to inline).
/// However, passing i256 parameters is counter productive and
/// I've found that splitting it up actualy increases code size.
/// Should be reviewed after 64bit support.
pub fn safe_truncate_int_to_xlen(
&self,
value: inkwell::values::IntValue<'ctx>,
@@ -1198,61 +1045,29 @@ where
"expected XLEN or WORD sized int type for memory offset",
);
let name = "__safe_trunc_xlen";
let function = self.module().get_function(name).unwrap_or_else(|| {
let position = self.basic_block();
let truncated =
self.builder()
.build_int_truncate(value, self.xlen_type(), "offset_truncated")?;
let extended =
self.builder()
.build_int_z_extend(truncated, self.word_type(), "offset_extended")?;
let is_overflow = self.builder().build_int_compare(
inkwell::IntPredicate::NE,
value,
extended,
"compare_truncated_extended",
)?;
let function = self.module().add_function(
name,
self.xlen_type().fn_type(&[self.word_type().into()], false),
None,
);
let block_entry = self.llvm().append_basic_block(function, "entry");
self.set_basic_block(block_entry);
let block_continue = self.append_basic_block("offset_pointer_ok");
let block_trap = self.append_basic_block("offset_pointer_overflow");
self.build_conditional_branch(is_overflow, block_trap, block_continue)?;
let value = function.get_first_param().unwrap().into_int_value();
let truncated = self
.builder()
.build_int_truncate(value, self.xlen_type(), "offset_truncated")
.unwrap();
let extended = self
.builder()
.build_int_z_extend(truncated, self.word_type(), "offset_extended")
.unwrap();
let is_overflow = self
.builder()
.build_int_compare(
inkwell::IntPredicate::NE,
value,
extended,
"compare_truncated_extended",
)
.unwrap();
self.set_basic_block(block_trap);
self.build_call(self.intrinsics().trap, &[], "invalid_trap");
self.build_unreachable();
let block_continue = self.llvm().append_basic_block(function, "offset_ok");
let block_trap = self.llvm().append_basic_block(function, "offset_overflow");
self.build_conditional_branch(is_overflow, block_trap, block_continue)
.unwrap();
self.set_basic_block(block_trap);
self.build_call(self.intrinsics().trap, &[], "invalid_trap");
self.build_unreachable();
self.set_basic_block(block_continue);
self.builder().build_return(Some(&truncated)).unwrap();
self.set_basic_block(position);
function
});
Ok(self
.builder()
.build_direct_call(function, &[value.into()], name)?
.try_as_basic_value()
.left()
.expect("returns a value")
.into_int_value())
self.set_basic_block(block_continue);
Ok(truncated)
}
/// Build a call to PolkaVM `sbrk` for extending the heap by `size`.
@@ -1355,49 +1170,6 @@ where
))
}
/// Writes the ABI pointer to the global variable.
pub fn write_abi_pointer(&mut self, pointer: Pointer<'ctx>, global_name: &str) {
self.set_global(
global_name,
self.llvm().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
pointer.value,
);
}
/// Writes the ABI data size to the global variable.
pub fn write_abi_data_size(&mut self, _pointer: Pointer<'ctx>, _global_name: &str) {
/*
let abi_pointer_value = self
.builder()
.build_ptr_to_int(pointer.value, self.field_type(), "abi_pointer_value")
.unwrap();
let abi_pointer_value_shifted = self
.builder()
.build_right_shift(
abi_pointer_value,
self.field_const((revive_common::BIT_LENGTH_X32 * 3) as u64),
false,
"abi_pointer_value_shifted",
)
.unwrap();
let abi_length_value = self
.builder()
.build_and(
abi_pointer_value_shifted,
self.field_const(u32::MAX as u64),
"abi_length_value",
)
.unwrap();
self.set_global(
global_name,
self.field_type(),
AddressSpace::Stack,
abi_length_value,
);
*/
}
/// Returns a boolean type constant.
pub fn bool_const(&self, value: bool) -> inkwell::values::IntValue<'ctx> {
self.bool_type().const_int(u64::from(value), false)
@@ -1461,6 +1233,20 @@ where
self.llvm.custom_width_int_type(crate::polkavm::XLEN as u32)
}
/// Returns the register witdh sized type.
pub fn sentinel_pointer(&self) -> Pointer<'ctx> {
let sentinel_pointer = self
.xlen_type()
.const_all_ones()
.const_to_pointer(self.llvm().ptr_type(Default::default()));
Pointer::new(
sentinel_pointer.get_type(),
AddressSpace::Stack,
sentinel_pointer,
)
}
/// Returns the runtime value width sized type.
pub fn value_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
@@ -1700,28 +1486,12 @@ where
.expect("The EVMLA data must have been initialized")
}
/// Sets the EVM legacy assembly data.
pub fn set_vyper_data(&mut self, data: VyperData) {
self.vyper_data = Some(data);
}
/// Returns the Vyper data reference.
/// # Panics
/// If the Vyper data has not been initialized.
pub fn vyper(&self) -> &VyperData {
self.vyper_data
.as_ref()
.expect("The Solidity data must have been initialized")
}
/// Returns the current number of immutables values in the contract.
/// If the size is set manually, then it is returned. Otherwise, the number of elements in
/// the identifier-to-offset mapping tree is returned.
pub fn immutables_size(&self) -> anyhow::Result<usize> {
if let Some(solidity) = self.solidity_data.as_ref() {
Ok(solidity.immutables_size())
} else if let Some(vyper) = self.vyper_data.as_ref() {
Ok(vyper.immutables_size())
} else {
anyhow::bail!("The immutable size data is not available");
}
@@ -1,37 +0,0 @@
//! The LLVM IR generator Vyper data.
/// The LLVM IR generator Vyper data.
/// Describes some data that is only relevant to Vyper.
#[derive(Debug)]
pub struct VyperData {
/// The immutables size tracker. Stores the size in bytes.
/// Does not take into account the size of the indexes.
immutables_size: usize,
/// Whether the contract forwarder has been used.
is_forwarder_used: bool,
}
impl VyperData {
/// A shortcut constructor.
pub fn new(immutables_size: usize, is_forwarder_used: bool) -> Self {
Self {
immutables_size,
is_forwarder_used,
}
}
/// Returns the size of the immutables data of the contract.
pub fn immutables_size(&self) -> usize {
self.immutables_size
}
/// Sets the forwarder usage flag.
pub fn set_is_forwarder_used(&mut self) {
self.is_forwarder_used = true;
}
/// Returns the forwarder usage flag.
pub fn is_forwarder_used(&self) -> bool {
self.is_forwarder_used
}
}
+65 -11
View File
@@ -195,7 +195,14 @@ where
context.build_load(result_pointer, "shift_right_arithmetic_result")
}
/// Translates the `byte` instruction.
/// Translates the `byte` instruction, extracting the byte of `operand_2`
/// found at index `operand_1`, starting from the most significant bit.
///
/// Builds a logical `and` with a corresponding bit mask.
///
/// Because this opcode returns zero on overflows, the index `operand_1`
/// is checked for overflow. On overflow, the mask will be all zeros,
/// resulting in a branchless implementation.
pub fn byte<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
@@ -204,14 +211,61 @@ pub fn byte<'ctx, D>(
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().byte,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"byte_call",
)
.expect("Always exists"))
const MAX_INDEX_BYTES: u64 = 31;
let is_overflow_bit = context.builder().build_int_compare(
inkwell::IntPredicate::ULE,
operand_1,
context.word_const(MAX_INDEX_BYTES),
"is_overflow_bit",
)?;
let is_overflow_byte = context.builder().build_int_z_extend(
is_overflow_bit,
context.byte_type(),
"is_overflow_byte",
)?;
let mask_byte = context.builder().build_int_mul(
context.byte_type().const_all_ones(),
is_overflow_byte,
"mask_byte",
)?;
let mask_byte_word =
context
.builder()
.build_int_z_extend(mask_byte, context.word_type(), "mask_byte_word")?;
let index_truncated =
context
.builder()
.build_int_truncate(operand_1, context.byte_type(), "index_truncated")?;
let index_in_bits = context.builder().build_int_mul(
index_truncated,
context
.byte_type()
.const_int(revive_common::BIT_LENGTH_BYTE as u64, false),
"index_in_bits",
)?;
let index_from_most_significant_bit = context.builder().build_int_sub(
context.byte_type().const_int(
MAX_INDEX_BYTES * revive_common::BIT_LENGTH_BYTE as u64,
false,
),
index_in_bits,
"index_from_msb",
)?;
let index_extended = context.builder().build_int_z_extend(
index_from_most_significant_bit,
context.word_type(),
"index",
)?;
let mask = context
.builder()
.build_left_shift(mask_byte_word, index_extended, "mask")?;
let masked_value = context.builder().build_and(operand_2, mask, "masked")?;
let byte = context
.builder()
.build_right_shift(masked_value, index_extended, false, "byte")?;
Ok(byte.as_basic_value_enum())
}
+93 -67
View File
@@ -3,17 +3,104 @@
use inkwell::values::BasicValue;
use crate::polkavm::context::argument::Argument;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
static STATIC_CALL_FLAG: u32 = 0b0001_0000;
/// Translates a contract call.
/// If the `simulation_address` is specified, the call is substituted with another instruction
/// according to the specification.
///
/// If the `simulation_address` is specified, the call is
/// substituted with another instruction according to the specification.
#[allow(clippy::too_many_arguments)]
pub fn default<'ctx, D>(
pub fn call<'ctx, D>(
context: &mut Context<'ctx, D>,
gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>,
value: Option<inkwell::values::IntValue<'ctx>>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
output_offset: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
_constants: Vec<Option<num::BigUint>>,
static_call: bool,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let address_pointer = context.build_alloca(context.word_type(), "address_ptr");
context.build_store(address_pointer, address)?;
let value_pointer = if let Some(value) = value {
let value_pointer = context.build_alloca(context.value_type(), "value");
context.build_store(value_pointer, value)?;
value_pointer
} else {
context.sentinel_pointer()
};
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
let gas = context
.builder()
.build_int_truncate(gas, context.integer_type(64), "gas")?;
let flags = if static_call { STATIC_CALL_FLAG } else { 0 };
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
let output_length_pointer = context.get_global(crate::polkavm::GLOBAL_RETURN_DATA_SIZE)?;
context.build_store(output_length_pointer.into(), output_length)?;
let argument_pointer = pallet_contracts_pvm_llapi::calling_convention::Spill::new(
context.builder(),
pallet_contracts_pvm_llapi::calling_convention::call(context.llvm()),
"call_arguments",
)?
.next(context.xlen_type().const_int(flags as u64, false))?
.next(address_pointer.value)?
.next(gas)?
.skip()
.next(context.sentinel_pointer().value)?
.next(value_pointer.value)?
.next(input_pointer.value)?
.next(input_length)?
.next(output_pointer.value)?
.next(output_length_pointer.value)?
.done();
let name = runtime_api::imports::CALL;
let arguments = context.builder().build_ptr_to_int(
argument_pointer,
context.xlen_type(),
"argument_pointer",
)?;
let success = context
.build_runtime_call(name, &[arguments.into()])
.unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value();
let is_success = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
success,
context.xlen_type().const_zero(),
"is_success",
)?;
Ok(context
.builder()
.build_int_z_extend(is_success, context.word_type(), "success")?
.as_basic_value_enum())
}
#[allow(clippy::too_many_arguments)]
pub fn delegate_call<'ctx, D>(
_context: &mut Context<'ctx, D>,
_function: FunctionDeclaration<'ctx>,
_gas: inkwell::values::IntValue<'ctx>,
_address: inkwell::values::IntValue<'ctx>,
_value: Option<inkwell::values::IntValue<'ctx>>,
@@ -26,68 +113,7 @@ pub fn default<'ctx, D>(
where
D: Dependency + Clone,
{
todo!();
/*
let ordinary_block = context.append_basic_block("contract_call_ordinary_block");
let join_block = context.append_basic_block("contract_call_join_block");
let result_pointer = context.build_alloca(context.field_type(), "contract_call_result_pointer");
context.build_store(result_pointer, context.field_const(0));
context.builder().build_switch(
address,
ordinary_block,
&[(
context.field_const(zkevm_opcode_defs::ADDRESS_IDENTITY.into()),
identity_block,
)],
)?;
{
context.set_basic_block(identity_block);
let result = identity(context, output_offset, input_offset, output_length)?;
context.build_store(result_pointer, result);
context.build_unconditional_branch(join_block);
}
context.set_basic_block(ordinary_block);
let result = if let Some(value) = value {
default_wrapped(
context,
function,
gas,
value,
address,
input_offset,
input_length,
output_offset,
output_length,
)?
} else {
let function = Runtime::default_call(context, function);
context
.build_call(
function,
&[
gas.as_basic_value_enum(),
address.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_offset.as_basic_value_enum(),
output_length.as_basic_value_enum(),
],
"default_call",
)
.expect("Always exists")
};
context.build_store(result_pointer, result);
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context.build_load(result_pointer, "contract_call_result");
Ok(result)
*/
todo!()
}
/// Translates the Yul `linkersymbol` instruction.
@@ -66,11 +66,5 @@ where
"calldata_pointer_with_offset",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"calldata_copy_memcpy_from_child",
)
context.build_memcpy(destination, source, size, "calldata_copy_memcpy_from_child")
}
@@ -58,7 +58,7 @@ where
"block_timestamp_output",
);
context.build_runtime_call(
runtime_api::BLOCK_NUMBER,
runtime_api::imports::BLOCK_NUMBER,
&[
output_pointer.to_int(context).into(),
output_length_pointer.to_int(context).into(),
@@ -83,7 +83,7 @@ where
"block_timestamp_output",
);
context.build_runtime_call(
runtime_api::NOW,
runtime_api::imports::NOW,
&[
output_pointer.to_int(context).into(),
output_length_pointer.to_int(context).into(),
@@ -174,7 +174,7 @@ where
let (output_pointer, output_length_pointer) =
context.build_stack_parameter(revive_common::BIT_LENGTH_ETH_ADDRESS, "address_output");
context.build_runtime_call(
runtime_api::ADDRESS,
runtime_api::imports::ADDRESS,
&[
output_pointer.to_int(context).into(),
output_length_pointer.to_int(context).into(),
@@ -197,7 +197,7 @@ where
let (output_pointer, output_length_pointer) =
context.build_stack_parameter(revive_common::BIT_LENGTH_ETH_ADDRESS, "caller_output");
context.build_runtime_call(
runtime_api::CALLER,
runtime_api::imports::CALLER,
&[
output_pointer.to_int(context).into(),
output_length_pointer.to_int(context).into(),
+62 -52
View File
@@ -5,9 +5,9 @@ use num::Zero;
use crate::polkavm::context::argument::Argument;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
/// Translates the contract `create` instruction.
/// The instruction is simulated by a call to a system contract.
@@ -20,32 +20,10 @@ pub fn create<'ctx, D>(
where
D: Dependency + Clone,
{
let signature_hash_string =
crate::polkavm::utils::keccak256(crate::polkavm::DEPLOYER_SIGNATURE_CREATE.as_bytes());
let signature_hash = context.word_const_str_hex(signature_hash_string.as_str());
let salt = context.word_const(0);
let function = Runtime::deployer_call(context);
let result = context
.build_call(
function,
&[
value.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
signature_hash.as_basic_value_enum(),
salt.as_basic_value_enum(),
],
"create_deployer_call",
)
.expect("Always exists");
Ok(result)
self::create2(context, value, input_offset, input_length, None)
}
/// Translates the contract `create2` instruction.
/// The instruction is simulated by a call to a system contract.
pub fn create2<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
@@ -56,28 +34,67 @@ pub fn create2<'ctx, D>(
where
D: Dependency + Clone,
{
let signature_hash_string =
crate::polkavm::utils::keccak256(crate::polkavm::DEPLOYER_SIGNATURE_CREATE2.as_bytes());
let signature_hash = context.word_const_str_hex(signature_hash_string.as_str());
let input_offset = context.safe_truncate_int_to_xlen(input_offset)?;
let input_length = context.safe_truncate_int_to_xlen(input_length)?;
let salt = salt.unwrap_or_else(|| context.word_const(0));
let value_pointer = context.build_alloca(context.value_type(), "value");
context.build_store(value_pointer, value)?;
let function = Runtime::deployer_call(context);
let result = context
.build_call(
function,
&[
value.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
signature_hash.as_basic_value_enum(),
salt.as_basic_value_enum(),
],
"create2_deployer_call",
)
.expect("Always exists");
let code_hash_pointer = context.build_heap_gep(input_offset, input_length)?;
Ok(result)
let input_data_pointer = context.build_gep(
code_hash_pointer,
&[context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false)],
context.byte_type(),
"value_ptr_parameter_offset",
);
let salt_pointer = context.build_alloca(context.word_type(), "salt");
context.build_store(salt_pointer, salt.unwrap_or_else(|| context.word_const(0)))?;
let (address_pointer, address_length_pointer) =
context.build_stack_parameter(revive_common::BIT_LENGTH_ETH_ADDRESS, "address_pointer");
context.build_store(address_pointer, context.word_const(0))?;
let argument_pointer = pallet_contracts_pvm_llapi::calling_convention::Spill::new(
context.builder(),
pallet_contracts_pvm_llapi::calling_convention::instantiate(context.llvm()),
"create2_arguments",
)?
.next(code_hash_pointer.value)?
.skip()
.skip()
.next(context.sentinel_pointer().value)?
.next(value_pointer.value)?
.next(input_data_pointer.value)?
.next(input_length)?
.next(address_pointer.value)?
.next(address_length_pointer.value)?
.next(context.sentinel_pointer().value)?
.next(context.sentinel_pointer().value)?
.next(salt_pointer.value)?
.next(
context
.xlen_type()
.const_int(revive_common::BYTE_LENGTH_WORD as u64, false),
)?
.done();
context.build_runtime_call(
runtime_api::imports::INSTANTIATE,
&[context
.builder()
.build_ptr_to_int(argument_pointer, context.xlen_type(), "argument_pointer")?
.into()],
);
context.build_load_word(
address_pointer,
revive_common::BIT_LENGTH_ETH_ADDRESS,
"address",
)
}
/// Translates the contract hash instruction, which is actually used to set the hash of the contract
@@ -121,15 +138,8 @@ where
Ok(Argument::new_with_original(hash_value, hash_string))
}
/// Translates the deployer call header size instruction, Usually, the header consists of:
/// - the deployer contract method signature
/// - the salt if the call is `create2`, or zero if the call is `create1`
/// - the hash of the bytecode of the contract whose instance is being created
/// - the offset of the constructor arguments
/// - the length of the constructor arguments
/// If the call is `create1`, the space for the salt is still allocated, because the memory for the
/// header is allocated by the Yul or EVM legacy assembly before it is known which version of
/// `create` is going to be used.
/// Translates the deploy call header size instruction. the header consists of
/// the hash of the bytecode of the contract whose instance is being created.
/// Represents `datasize` in Yul and `PUSH #[$]` in the EVM legacy assembly.
pub fn header_size<'ctx, D>(
context: &mut Context<'ctx, D>,
@@ -19,7 +19,7 @@ where
let output_pointer = context.build_alloca(context.word_type(), "output_pointer");
context.build_runtime_call(
runtime_api::HASH_KECCAK_256,
runtime_api::imports::HASH_KECCAK_256,
&[
input_pointer.to_int(context).into(),
length_casted.into(),
@@ -26,7 +26,7 @@ where
let (output_pointer, output_length_pointer) =
context.build_stack_parameter(revive_common::BIT_LENGTH_VALUE, "value_transferred_output");
context.build_runtime_call(
runtime_api::VALUE_TRANSFERRED,
runtime_api::imports::VALUE_TRANSFERRED,
&[
output_pointer.to_int(context).into(),
output_length_pointer.to_int(context).into(),
@@ -41,11 +41,31 @@ where
/// Translates the `balance` instructions.
pub fn balance<'ctx, D>(
_context: &mut Context<'ctx, D>,
_address: inkwell::values::IntValue<'ctx>,
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
let balance_pointer = context.build_alloca(context.word_type(), "balance_pointer");
let address_pointer = context.build_alloca(context.word_type(), "address_pointer");
context.build_store(address_pointer, address)?;
let balance = context.builder().build_ptr_to_int(
balance_pointer.value,
context.xlen_type(),
"balance",
)?;
let address = context.builder().build_ptr_to_int(
address_pointer.value,
context.xlen_type(),
"address",
)?;
context.build_runtime_call(
runtime_api::imports::BALANCE,
&[address.into(), balance.into()],
);
context.build_load(balance_pointer, "balance")
}
+29 -81
View File
@@ -7,6 +7,12 @@ use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
/// Translates a log or event call.
///
/// TODO: Splitting up into dedicated functions (log0..log4)
/// could potentially decrease code sizes (LLVM can still decide to inline).
/// However, passing i256 parameters is counter productive and
/// I've found that splitting it up actualy increases code size.
/// Should be reviewed after 64bit support.
pub fn log<'ctx, D>(
context: &mut Context<'ctx, D>,
input_offset: inkwell::values::IntValue<'ctx>,
@@ -24,48 +30,14 @@ where
"event_input_offset",
)?;
if topics.is_empty() {
let _ = context.build_runtime_call(
runtime_api::DEPOSIT_EVENT,
&[
context.xlen_type().const_zero().as_basic_value_enum(),
context.xlen_type().const_zero().as_basic_value_enum(),
input_pointer.as_basic_value_enum(),
input_length.as_basic_value_enum(),
],
);
return Ok(());
}
let name = match topics.len() {
1 => "__log_1",
2 => "__log_2",
3 => "__log_3",
4 => "__log_4",
_ => unreachable!(),
};
let function = context.module().get_function(name).unwrap_or_else(|| {
let position = context.basic_block();
let mut parameters = vec![context.xlen_type().into(), context.xlen_type().into()];
parameters.extend_from_slice(
&topics
.iter()
.map(|_| context.word_type().into())
.collect::<Vec<_>>(),
);
let function = context.module().add_function(
name,
context.void_type().fn_type(&parameters, false),
None,
);
let block_entry = context.llvm().append_basic_block(function, "entry");
context.set_basic_block(block_entry);
let parameters = function.get_param_iter().collect::<Vec<_>>();
let topics = &parameters[2..];
let input_offset = parameters.first().unwrap();
let input_length = parameters.get(1).unwrap();
let arguments = if topics.is_empty() {
[
context.xlen_type().const_zero().as_basic_value_enum(),
context.xlen_type().const_zero().as_basic_value_enum(),
input_pointer.as_basic_value_enum(),
input_length.as_basic_value_enum(),
]
} else {
let topics_buffer_size = topics.len() * revive_common::BYTE_LENGTH_WORD;
let topics_buffer_pointer = context.build_alloca(
context.byte_type().array_type(topics_buffer_size as u32),
@@ -75,59 +47,35 @@ where
let topic_buffer_offset = context
.xlen_type()
.const_int((n * revive_common::BYTE_LENGTH_WORD) as u64, false);
context
.build_store(
context.build_gep(
topics_buffer_pointer,
&[context.xlen_type().const_zero(), topic_buffer_offset],
context.byte_type(),
"topic_buffer_gep",
),
context
.build_byte_swap(topic.as_basic_value_enum())
.unwrap(),
)
.unwrap();
context.build_store(
context.build_gep(
topics_buffer_pointer,
&[context.xlen_type().const_zero(), topic_buffer_offset],
context.byte_type(),
"topic_buffer_gep",
),
context.build_byte_swap(topic.as_basic_value_enum())?,
)?;
}
let arguments = [
[
context
.builder()
.build_ptr_to_int(
topics_buffer_pointer.value,
context.xlen_type(),
"event_topics_offset",
)
.unwrap()
)?
.as_basic_value_enum(),
context
.xlen_type()
.const_int(topics_buffer_size as u64, false)
.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_pointer.as_basic_value_enum(),
input_length.as_basic_value_enum(),
];
]
};
let _ = context.build_runtime_call(runtime_api::DEPOSIT_EVENT, &arguments);
context.builder().build_return(None).unwrap();
context.set_basic_block(position);
function
});
let mut arguments = vec![
input_pointer.as_basic_value_enum().into(),
input_length.as_basic_value_enum().into(),
];
arguments.extend_from_slice(
&topics
.iter()
.map(|value| value.as_basic_value_enum().into())
.collect::<Vec<_>>(),
);
let _ = context
.builder()
.build_direct_call(function, &arguments[..], "call_log");
let _ = context.build_runtime_call(runtime_api::imports::DEPOSIT_EVENT, &arguments);
Ok(())
}
@@ -1,17 +1,46 @@
//! Translates the external code operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
/// Translates the `extcodesize` instruction.
/// Translates the `extcodesize` instruction if `address` is `Some`.
/// Otherwise, translates the `codesize` instruction.
pub fn size<'ctx, D>(
_context: &mut Context<'ctx, D>,
_address: inkwell::values::IntValue<'ctx>,
context: &mut Context<'ctx, D>,
address: Option<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
let address_pointer = match address {
Some(address) => {
let address_pointer = context.build_alloca(context.word_type(), "value");
context.build_store(address_pointer, address)?;
address_pointer
}
None => context.sentinel_pointer(),
};
let address_pointer_casted = context.builder().build_ptr_to_int(
address_pointer.value,
context.xlen_type(),
"address_pointer",
)?;
let value = context
.build_runtime_call(
runtime_api::imports::CODE_SIZE,
&[address_pointer_casted.into()],
)
.unwrap_or_else(|| panic!("{} should return a value", runtime_api::imports::CODE_SIZE))
.into_int_value();
Ok(context
.builder()
.build_int_z_extend(value, context.word_type(), "extcodesize")?
.as_basic_value_enum())
}
/// Translates the `extcodehash` instruction.
@@ -36,7 +36,7 @@ where
)?;
let immutable_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
AddressSpace::default(),
context.word_type(),
offset_absolute,
"immutable_pointer",
@@ -81,7 +81,7 @@ where
)?;
let index_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
AddressSpace::default(),
context.word_type(),
index_offset_absolute,
"immutable_index_pointer",
@@ -95,7 +95,7 @@ where
)?;
let value_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
AddressSpace::default(),
context.word_type(),
value_offset_absolute,
"immutable_value_pointer",
+7 -62
View File
@@ -1,13 +1,9 @@
//! Translates the transaction return operations.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
/// Translates the `return` instruction.
/// Unlike in EVM, zkSync constructors return the array of contract immutables.
pub fn r#return<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
@@ -16,66 +12,15 @@ pub fn r#return<'ctx, D>(
where
D: Dependency + Clone,
{
match context.code_type() {
None => {
anyhow::bail!("Return is not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
let immutables_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.word_type(),
context.word_const(crate::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA),
"immutables_offset_pointer",
);
context.build_store(
immutables_offset_pointer,
context.word_const(revive_common::BYTE_LENGTH_WORD as u64),
)?;
let immutables_number_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.word_type(),
context.word_const(
crate::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (revive_common::BYTE_LENGTH_WORD as u64),
),
"immutables_number_pointer",
);
let immutable_values_size = context.immutables_size()?;
context.build_store(
immutables_number_pointer,
context
.word_const((immutable_values_size / revive_common::BYTE_LENGTH_WORD) as u64),
)?;
let immutables_size = context.builder().build_int_mul(
context.word_const(immutable_values_size as u64),
context.word_const(2),
"immutables_size",
)?;
let return_data_length = context.builder().build_int_add(
immutables_size,
context.word_const((revive_common::BYTE_LENGTH_WORD * 2) as u64),
"return_data_length",
)?;
context.build_exit(
context.integer_const(crate::polkavm::XLEN, 0),
context.word_const(crate::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA),
return_data_length,
)?;
}
Some(CodeType::Runtime) => {
context.build_exit(
context.integer_const(crate::polkavm::XLEN, 0),
offset,
length,
)?;
}
if context.code_type().is_none() {
anyhow::bail!("Return is not available if the contract part is undefined");
}
Ok(())
context.build_exit(
context.integer_const(crate::polkavm::XLEN, 0),
offset,
length,
)
}
/// Translates the `revert` instruction.
@@ -1,12 +1,10 @@
//! Translates the return data instructions.
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm_const::runtime_api;
/// Translates the return data size.
pub fn size<'ctx, D>(
@@ -15,13 +13,19 @@ pub fn size<'ctx, D>(
where
D: Dependency + Clone,
{
match context.get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE) {
Ok(global) => Ok(global),
Err(_error) => Ok(context.word_const(0).as_basic_value_enum()),
}
let value = context
.get_global_value(crate::polkavm::GLOBAL_RETURN_DATA_SIZE)?
.into_int_value();
Ok(context
.builder()
.build_int_z_extend(value, context.word_type(), "calldatasize_extended")?
.as_basic_value_enum())
}
/// Translates the return data copy.
/// Translates the return data copy, trapping if
/// - Destination, offset or size exceed the VM register size (XLEN)
/// - `source_offset + size` overflows (in XLEN)
/// - `source_offset + size` is beyond `RETURNDATASIZE`
pub fn copy<'ctx, D>(
context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>,
@@ -31,57 +35,20 @@ pub fn copy<'ctx, D>(
where
D: Dependency + Clone,
{
let error_block = context.append_basic_block("return_data_copy_error_block");
let join_block = context.append_basic_block("return_data_copy_join_block");
let source_offset = context.safe_truncate_int_to_xlen(source_offset)?;
let destination_offset = context.safe_truncate_int_to_xlen(destination_offset)?;
let size = context.safe_truncate_int_to_xlen(size)?;
let return_data_size = self::size(context)?.into_int_value();
let copy_slice_end =
context
.builder()
.build_int_add(source_offset, size, "return_data_copy_slice_end")?;
let is_copy_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
copy_slice_end,
return_data_size,
"return_data_copy_is_out_of_bounds",
)?;
context.build_conditional_branch(is_copy_out_of_bounds, error_block, join_block)?;
context.set_basic_block(error_block);
crate::polkavm::evm::r#return::revert(context, context.word_const(0), context.word_const(0))?;
context.set_basic_block(join_block);
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination_offset,
"return_data_copy_destination_pointer",
);
let return_data_pointer_global =
context.get_global(crate::polkavm::GLOBAL_RETURN_DATA_POINTER)?;
let return_data_pointer_pointer = return_data_pointer_global.into();
let return_data_pointer =
context.build_load(return_data_pointer_pointer, "return_data_pointer")?;
let source = context.build_gep(
Pointer::new(
context.byte_type(),
return_data_pointer_pointer.address_space,
return_data_pointer.into_pointer_value(),
),
&[source_offset],
context.byte_type().as_basic_type_enum(),
"return_data_source_pointer",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"return_data_copy_memcpy_from_return_data",
let destination_offset = context.builder().build_ptr_to_int(
context.build_heap_gep(destination_offset, size)?.value,
context.xlen_type(),
"destination_offset",
)?;
todo!("Build heap GEP to allocate if necessary")
context.build_runtime_call(
runtime_api::imports::RETURNDATACOPY,
&[destination_offset.into(), source_offset.into(), size.into()],
);
Ok(())
}
+3 -2
View File
@@ -7,6 +7,7 @@ pub mod metadata_hash;
pub mod utils;
pub use self::r#const::*;
use self::utils::keccak256;
use crate::debug_config::DebugConfig;
use crate::optimizer::settings::Settings as OptimizerSettings;
@@ -30,7 +31,7 @@ pub fn build_assembly_text(
metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_WORD]>,
debug_config: Option<&DebugConfig>,
) -> anyhow::Result<Build> {
let program_blob = ProgramBlob::parse(bytecode)
let program_blob = ProgramBlob::parse(bytecode.into())
.map_err(anyhow::Error::msg)
.with_context(|| format!("Failed to parse program blob for contract: {contract_path}"))?;
@@ -56,7 +57,7 @@ pub fn build_assembly_text(
assembly_text.to_owned(),
metadata_hash,
bytecode.to_owned(),
Default::default(),
keccak256(bytecode),
))
}
-158
View File
@@ -1,9 +1,5 @@
//! Some LLVM IR generator utilies.
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::llvm_runtime::LLVMRuntime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
@@ -40,160 +36,6 @@ where
Ok(result.into_int_value())
}
/// Generates an exception.
pub fn throw<D>(context: &Context<D>)
where
D: Dependency + Clone,
{
context.build_call(
context.llvm_runtime().cxa_throw,
&[context
.llvm()
.ptr_type(AddressSpace::Stack.into())
.get_undef()
.as_basic_value_enum(); 3],
LLVMRuntime::FUNCTION_CXA_THROW,
);
context.build_unreachable();
}
/// Returns the full list of arguments for an external call.
/// Performs the extra ABI data padding and adds the mimic call extra argument.
pub fn external_call_arguments<'ctx, D>(
_context: &Context<'ctx, D>,
abi_data: inkwell::values::BasicValueEnum<'ctx>,
address: inkwell::values::IntValue<'ctx>,
_extra_abi_data: Vec<inkwell::values::IntValue<'ctx>>,
mimic: Option<inkwell::values::IntValue<'ctx>>,
) -> Vec<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let mut result = Vec::with_capacity(
crate::polkavm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT
+ crate::polkavm::EXTRA_ABI_DATA_SIZE
+ usize::from(mimic.is_some()),
);
result.push(abi_data);
result.push(address.as_basic_value_enum());
//result.extend(
// pad_extra_abi_data(context, extra_abi_data)
// .into_iter()
// .map(|value| value.as_basic_value_enum()),
//);
if let Some(mimic) = mimic {
result.push(mimic.as_basic_value_enum());
}
result
}
/// Generates an ABI data for an external call.
/// If `gas` is `None`, it is fetched from the contract context.
pub fn abi_data<'ctx, D>(
context: &mut Context<'ctx, D>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
gas: Option<inkwell::values::IntValue<'ctx>>,
address_space: AddressSpace,
is_system_call: bool,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let input_offset = crate::polkavm::utils::clamp(
context,
input_offset,
context.word_const(u32::MAX as u64),
"abi_data_input_offset",
)?;
let input_length = crate::polkavm::utils::clamp(
context,
input_length,
context.word_const(u32::MAX as u64),
"abi_data_input_length",
)?;
let gas = match gas {
Some(gas) => gas,
None => crate::polkavm::evm::ether_gas::gas(context)?.into_int_value(),
};
let gas = crate::polkavm::utils::clamp(
context,
gas,
context.word_const(u32::MAX as u64),
"abi_data_gas",
)?;
let input_offset_shifted = context.builder().build_left_shift(
input_offset,
context.word_const((revive_common::BIT_LENGTH_X32 * 2) as u64),
"abi_data_input_offset_shifted",
)?;
let input_length_shifted = context.builder().build_left_shift(
input_length,
context.word_const((revive_common::BIT_LENGTH_X32 * 3) as u64),
"abi_data_input_length_shifted",
)?;
let gas_shifted = context.builder().build_left_shift(
gas,
context.word_const((revive_common::BIT_LENGTH_X32 * 6) as u64),
"abi_data_gas_shifted",
)?;
let mut abi_data = context.builder().build_int_add(
input_offset_shifted,
input_length_shifted,
"abi_data_offset_and_length",
)?;
abi_data = context
.builder()
.build_int_add(abi_data, gas_shifted, "abi_data_add_gas")?;
if let AddressSpace::HeapAuxiliary = address_space {
let auxiliary_heap_marker_shifted = context.builder().build_left_shift(
context.word_const(zkevm_opcode_defs::FarCallForwardPageType::UseAuxHeap as u64),
context.word_const((revive_common::BIT_LENGTH_X32 * 7) as u64),
"abi_data_auxiliary_heap_marker_shifted",
)?;
abi_data = context.builder().build_int_add(
abi_data,
auxiliary_heap_marker_shifted,
"abi_data_add_heap_auxiliary_marker",
)?;
}
if is_system_call {
let auxiliary_heap_marker_shifted = context.builder().build_left_shift(
context.word_const(zkevm_opcode_defs::FarCallForwardPageType::UseAuxHeap as u64),
context.word_const(
((revive_common::BIT_LENGTH_X32 * 7) + (revive_common::BIT_LENGTH_BYTE * 3)) as u64,
),
"abi_data_system_call_marker_shifted",
)?;
abi_data = context.builder().build_int_add(
abi_data,
auxiliary_heap_marker_shifted,
"abi_data_add_system_call_marker",
)?;
}
Ok(abi_data.as_basic_value_enum())
}
/// Pads the extra ABI data with `i256::undef`, so it always consists of 10 values.
pub fn pad_extra_abi_data<'ctx, D>(
context: &Context<'ctx, D>,
initial_data: Vec<inkwell::values::IntValue<'ctx>>,
) -> [inkwell::values::IntValue<'ctx>; crate::polkavm::EXTRA_ABI_DATA_SIZE]
where
D: Dependency + Clone,
{
let mut padded_data = initial_data;
padded_data.extend(vec![
context.word_undef();
crate::polkavm::EXTRA_ABI_DATA_SIZE - padded_data.len()
]);
padded_data.try_into().expect("Always valid")
}
/// Computes the `keccak256` hash for `preimage`.
pub fn keccak256(preimage: &[u8]) -> String {
use sha3::Digest;
+13 -1
View File
@@ -20,10 +20,22 @@ pub struct TargetMachine {
impl TargetMachine {
/// The LLVM target name.
#[cfg(not(feature = "riscv-64"))]
pub const VM_TARGET_NAME: &'static str = "riscv32";
#[cfg(feature = "riscv-64")]
pub const VM_TARGET_NAME: &'static str = "riscv64";
/// The LLVM target triple.
#[cfg(not(feature = "riscv-64"))]
pub const VM_TARGET_TRIPLE: &'static str = "riscv32-unknown-unknown-elf";
#[cfg(feature = "riscv-64")]
pub const VM_TARGET_TRIPLE: &'static str = "riscv64-unknown-unknown-elf";
/// The LLVM target cpu
#[cfg(not(feature = "riscv-64"))]
pub const VM_TARGET_CPU: &'static str = "generic-rv32";
#[cfg(feature = "riscv-64")]
pub const VM_TARGET_CPU: &'static str = "generic-rv64";
/// LLVM target features.
#[cfg(feature = "riscv-zbb")]
@@ -38,7 +50,7 @@ impl TargetMachine {
.ok_or_else(|| anyhow::anyhow!("LLVM target machine `{}` not found", target.name()))?
.create_target_machine(
&inkwell::targets::TargetTriple::create(target.triple()),
"generic-rv32",
Self::VM_TARGET_CPU,
Self::VM_FEATURES,
optimizer_settings.level_back_end,
inkwell::targets::RelocMode::PIC,
@@ -13,14 +13,20 @@ impl Target {
/// Returns the target name.
pub fn name(&self) -> &str {
match self {
#[cfg(not(feature = "riscv-64"))]
Self::PVM => "riscv32",
#[cfg(feature = "riscv-64")]
Self::PVM => "riscv64",
}
}
/// Returns the target triple.
pub fn triple(&self) -> &str {
match self {
#[cfg(not(feature = "riscv-64"))]
Self::PVM => "riscv32-unknown-unknown-elf",
#[cfg(feature = "riscv-64")]
Self::PVM => "riscv64-unknown-unknown-elf",
}
}
@@ -37,7 +43,10 @@ impl FromStr for Target {
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
#[cfg(not(feature = "riscv-64"))]
"riscv32" => Ok(Self::PVM),
#[cfg(feature = "riscv-64")]
"riscv64" => Ok(Self::PVM),
_ => Err(anyhow::anyhow!(
"Unknown target `{}`. Supported targets: {:?}",
string,
@@ -50,7 +59,10 @@ impl FromStr for Target {
impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(not(feature = "riscv-64"))]
Target::PVM => write!(f, "riscv32"),
#[cfg(feature = "riscv-64")]
Target::PVM => write!(f, "riscv64"),
}
}
}
+10 -2
View File
@@ -1,7 +1,15 @@
[package]
name = "pallet-contracts-pvm-llapi"
version = "0.1.0"
edition = "2021"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
repository.authors = true
descritption = "Implements the low level runtime API bindings with pallet contracts"
[features]
riscv-64 = []
[dependencies]
anyhow = { workspace = true }
inkwell = { workspace = true, features = ["target-riscv", "no-libffi-linking", "llvm18-0"] }
+24 -4
View File
@@ -1,13 +1,33 @@
use std::{env, fs, path::Path, process::Command};
#[cfg(not(feature = "riscv-64"))]
const TARGET_TRIPLE_FLAG: &str = "-triple=riscv32-unknown-unknown-elf";
#[cfg(feature = "riscv-64")]
const TARGET_TRIPLE_FLAG: &str = "-triple=riscv64-unknown-unknown-elf";
#[cfg(not(feature = "riscv-64"))]
const TARGET_FLAG: &str = "--target=riscv32";
#[cfg(feature = "riscv-64")]
const TARGET_FLAG: &str = "--target=riscv64";
#[cfg(not(feature = "riscv-64"))]
const TARGET_ARCH_FLAG: &str = "-march=rv32em";
#[cfg(feature = "riscv-64")]
const TARGET_ARCH_FLAG: &str = "-march=rv64em";
#[cfg(not(feature = "riscv-64"))]
const TARGET_ABI_FLAG: &str = "-mabi=ilp32e";
#[cfg(feature = "riscv-64")]
const TARGET_ABI_FLAG: &str = "-mabi=lp64e";
fn compile(bitcode_path: &str) {
let output = Command::new("clang")
.args([
"--target=riscv32",
TARGET_FLAG,
"-Xclang",
"-triple=riscv32-unknown-unknown-elf",
"-march=rv32em",
"-mabi=ilp32e",
TARGET_TRIPLE_FLAG,
TARGET_ARCH_FLAG,
TARGET_ABI_FLAG,
"-fno-exceptions",
"-ffreestanding",
"-Wall",
@@ -0,0 +1,118 @@
use inkwell::{
builder::Builder,
context::Context,
types::{BasicType, StructType},
values::{BasicValue, PointerValue},
};
pub struct Spill<'a, 'ctx> {
pointer: PointerValue<'ctx>,
builder: &'a Builder<'ctx>,
r#type: StructType<'ctx>,
current_field: u32,
}
impl<'a, 'ctx> Spill<'a, 'ctx> {
pub fn new(
builder: &'a Builder<'ctx>,
r#type: StructType<'ctx>,
name: &str,
) -> anyhow::Result<Self> {
Ok(Self {
pointer: builder.build_alloca(r#type, name)?,
builder,
r#type,
current_field: 0,
})
}
pub fn next<V: BasicValue<'ctx>>(mut self, value: V) -> anyhow::Result<Self> {
let field_pointer = self.builder.build_struct_gep(
self.r#type,
self.pointer,
self.current_field,
&format!("spill_parameter_{}", self.current_field),
)?;
self.builder.build_store(field_pointer, value)?;
self.current_field += 1;
Ok(self)
}
pub fn skip(mut self) -> Self {
self.current_field += 1;
self
}
pub fn done(self) -> PointerValue<'ctx> {
assert!(
self.r#type
.get_field_type_at_index(self.current_field)
.is_none(),
"there must not be any missing parameters"
);
self.pointer
}
}
pub fn instantiate(context: &Context) -> StructType {
context.struct_type(
&[
// code_hash_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// ref_time_limit: u64,
context.i64_type().as_basic_type_enum(),
// proof_size_limit: u64,
context.i64_type().as_basic_type_enum(),
// deposit_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// value_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// input_data_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// address_len_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// output_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// output_len_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// salt_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// salt_len: u32
context.i32_type().as_basic_type_enum(),
],
true,
)
}
pub fn call(context: &Context) -> StructType {
context.struct_type(
&[
// flags: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr:
context.ptr_type(Default::default()).as_basic_type_enum(),
// ref_time_limit: u64,
context.i64_type().as_basic_type_enum(),
// proof_size_limit: u64,
context.i64_type().as_basic_type_enum(),
// deposit_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// value_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// input_data_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
// output_len_ptr: u32,
context.ptr_type(Default::default()).as_basic_type_enum(),
],
true,
)
}
+2 -52
View File
@@ -1,52 +1,2 @@
//! This crate vendors the [PolkaVM][0] C API and provides a LLVM module for interacting
//! with the `pallet-contracts` runtime API.
//! At present, the contracts pallet requires blobs to export `call` and `deploy`,
//! and offers a bunch of [runtime API methods][1]. The provided [module] implements
//! those exports and imports.
//! [0]: [https://crates.io/crates/polkavm]
//! [1]: [https://docs.rs/pallet-contracts/26.0.0/pallet_contracts/api_doc/index.html]
use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, support::LLVMString};
include!(concat!(env!("OUT_DIR"), "/polkavm_guest.rs"));
/// Creates a LLVM module from the [BITCODE].
/// The module does:
/// - Export the `call` and `deploy` functions (which are named thereafter).
/// - Import (most) `pallet-contracts` runtime API functions.
/// Returns `Error` if the bitcode fails to parse, which should never happen.
pub fn module<'context>(
context: &'context Context,
module_name: &str,
) -> Result<Module<'context>, LLVMString> {
let buf = MemoryBuffer::create_from_memory_range(BITCODE, module_name);
Module::parse_bitcode_from_buffer(&buf, context)
}
/// Creates a module that sets the PolkaVM minimum stack size to [`size`] if linked in.
pub fn min_stack_size<'context>(
context: &'context Context,
module_name: &str,
size: u32,
) -> Module<'context> {
let module = context.create_module(module_name);
module.set_inline_assembly(&format!(
".pushsection .polkavm_min_stack_size,\"\",@progbits
.word {size}
.popsection"
));
module
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
inkwell::targets::Target::initialize_riscv(&Default::default());
let context = inkwell::context::Context::create();
let module = crate::module(&context, "polkavm_guest").unwrap();
assert!(module.get_function("call").is_some());
assert!(module.get_function("deploy").is_some());
}
}
pub mod calling_convention;
pub mod polkavm_guest;
@@ -21,6 +21,22 @@ void * memcpy(void *dst, const void *_src, size_t len) {
return dst;
}
void * memmove(void *dst, const void *src, size_t n) {
char *d = dst;
const char *s = src;
if (d==s) return d;
if ((uintptr_t)s-(uintptr_t)d-n <= -2*n) return memcpy(d, s, n);
if (d<s) {
for (; n; n--) *d++ = *s++;
} else {
while (n) n--, d[n] = s[n];
}
return dst;
}
void * __sbrk(uint32_t size) {
uint32_t address;
__asm__ __volatile__(
@@ -47,11 +63,13 @@ POLKAVM_IMPORT(void, input, uint32_t, uint32_t)
POLKAVM_IMPORT(void, seal_return, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(void, returndatacopy, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(void, value_transferred, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, set_storage, uint32_t, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, set_storage, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, get_storage, uint32_t, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, get_storage, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, clear_storage, uint32_t, uint32_t)
@@ -73,6 +91,8 @@ POLKAVM_IMPORT(uint32_t, is_contract, uint32_t)
POLKAVM_IMPORT(uint32_t, code_hash, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, code_size, uint32_t)
POLKAVM_IMPORT(void, own_code_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(uint32_t, caller_is_origin)
@@ -0,0 +1,54 @@
//! This crate vendors the [PolkaVM][0] C API and provides a LLVM module for interacting
//! with the `pallet-contracts` runtime API.
//! At present, the contracts pallet requires blobs to export `call` and `deploy`,
//! and offers a bunch of [runtime API methods][1]. The provided [module] implements
//! those exports and imports.
//! [0]: [https://crates.io/crates/polkavm]
//! [1]: [https://docs.rs/pallet-contracts/26.0.0/pallet_contracts/api_doc/index.html]
use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, support::LLVMString};
include!(concat!(env!("OUT_DIR"), "/polkavm_guest.rs"));
/// Creates a LLVM module from the [BITCODE].
/// The module does:
/// - Export the `call` and `deploy` functions (which are named thereafter).
/// - Import (most) `pallet-contracts` runtime API functions.
/// Returns `Error` if the bitcode fails to parse, which should never happen.
pub fn module<'context>(
context: &'context Context,
module_name: &str,
) -> Result<Module<'context>, LLVMString> {
let buf = MemoryBuffer::create_from_memory_range(BITCODE, module_name);
Module::parse_bitcode_from_buffer(&buf, context)
}
/// Creates a module that sets the PolkaVM minimum stack size to [`size`] if linked in.
pub fn min_stack_size<'context>(
context: &'context Context,
module_name: &str,
size: u32,
) -> Module<'context> {
let module = context.create_module(module_name);
module.set_inline_assembly(&format!(
".pushsection .polkavm_min_stack_size,\"\",@progbits
.word {size}
.popsection"
));
module
}
#[cfg(test)]
mod tests {
use crate::polkavm_guest;
#[test]
fn it_works() {
inkwell::targets::Target::initialize_riscv(&Default::default());
let context = inkwell::context::Context::create();
let module = polkavm_guest::module(&context, "polkavm_guest").unwrap();
assert!(module.get_function("call").is_some());
assert!(module.get_function("deploy").is_some());
}
}
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "revive-runner"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
repository.authors = true
descritption = "Execute revive contracts in a simulated blockchain runtime"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
codec = { workspace = true, default-features = false }
scale-info = { workspace = true, default-features = false }
polkadot-sdk = { workspace = true, features = ["experimental", "runtime"] }
revive-solidity = { workspace = true }
Binary file not shown.
+393
View File
@@ -0,0 +1,393 @@
//! Experimental test runner for testing [pallet-revive](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/revive) contracts.
//! The crate exposes a single function [`run_tests`] that takes a [`Specs`] that defines in a declarative way:
//! - The Genesis configuration
//! - A list of [`SpecsAction`] that will be executed in order.
//!
//! ## Example
//! ```rust
//! use revive_runner::*;
//! use SpecsAction::*;
//! run_test(Specs {
//! balances: vec![(ALICE, 1_000_000_000)],
//! actions: vec![Instantiate {
//! origin: ALICE,
//! value: 0,
//! gas_limit: Some(GAS_LIMIT),
//! storage_deposit_limit: Some(DEPOSIT_LIMIT),
//! code: Code::Bytes(include_bytes!("../fixtures/Baseline.pvm").to_vec()),
//! data: vec![],
//! salt: vec![],
//! }],
//! })
//! ```
use polkadot_sdk::*;
use polkadot_sdk::{
pallet_revive::{CollectEvents, ContractExecResult, ContractInstantiateResult, DebugInfo},
polkadot_runtime_common::BuildStorage,
polkadot_sdk_frame::testing_prelude::*,
sp_keystore::{testing::MemoryKeystore, KeystoreExt},
sp_runtime::AccountId32,
};
use serde::{Deserialize, Serialize};
mod runtime;
use crate::runtime::*;
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
pub const BOB: AccountId32 = AccountId32::new([2u8; 32]);
pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]);
/// Externalities builder
#[derive(Default)]
pub struct ExtBuilder {
/// List of endowments at genesis
balance_genesis_config: Vec<(AccountId, Balance)>,
}
impl ExtBuilder {
/// Set the balance of an account at genesis
fn balance_genesis_config(mut self, value: Vec<(AccountId, Balance)>) -> Self {
self.balance_genesis_config = value;
self
}
/// Build the externalities
pub fn build(self) -> sp_io::TestExternalities {
sp_tracing::try_init_simple();
let mut t = frame_system::GenesisConfig::<Runtime>::default()
.build_storage()
.unwrap();
pallet_balances::GenesisConfig::<Runtime> {
balances: self.balance_genesis_config,
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.register_extension(KeystoreExt::new(MemoryKeystore::new()));
ext.execute_with(|| System::set_block_number(1));
ext
}
}
/// Default gas limit
pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024);
/// Default deposit limit
pub const DEPOSIT_LIMIT: Balance = 10_000_000;
/// Expectation for a call
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifyCallExpectation {
/// When provided, the expected gas consumed
gas_consumed: Option<Weight>,
/// When provided, the expected output
output: Option<Vec<u8>>,
///Expected call result
success: bool,
}
impl Default for VerifyCallExpectation {
fn default() -> Self {
Self {
gas_consumed: None,
output: None,
success: true,
}
}
}
impl VerifyCallExpectation {
/// Verify that the expectations are met
fn verify(self, result: CallResult) {
dbg!(&result);
assert_eq!(self.success, result.is_ok());
if let Some(gas_consumed) = self.gas_consumed {
assert_eq!(gas_consumed, result.gas_consumed());
}
if let Some(output) = self.output {
assert_eq!(output, result.output());
}
}
}
/// Result of a call
#[derive(Debug)]
enum CallResult {
Exec(ContractExecResult<Balance, EventRecord>),
Instantiate(ContractInstantiateResult<AccountId, Balance, EventRecord>),
}
impl CallResult {
/// Check if the call was successful
fn is_ok(&self) -> bool {
match self {
Self::Exec(res) => res.result.is_ok(),
Self::Instantiate(res) => res.result.is_ok(),
}
}
/// Get the output of the call
fn output(&self) -> Vec<u8> {
match self {
Self::Exec(res) => res
.result
.as_ref()
.map(|r| r.data.clone())
.unwrap_or_default(),
Self::Instantiate(res) => res
.result
.as_ref()
.map(|r| r.result.data.clone())
.unwrap_or_default(),
}
}
/// Get the gas consumed by the call
fn gas_consumed(&self) -> Weight {
match self {
Self::Exec(res) => res.gas_consumed,
Self::Instantiate(res) => res.gas_consumed,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Code {
/// Compile a single solidity source and use the blob of `contract`
Solidity {
path: std::path::PathBuf,
contract: String,
},
/// Read the contract blob from disk
Path(std::path::PathBuf),
/// A contract blob
Bytes(Vec<u8>),
/// Pre-existing contract hash
Hash(Hash),
}
impl From<Code> for pallet_revive::Code<Hash> {
fn from(val: Code) -> Self {
match val {
Code::Solidity { path, contract } => {
pallet_revive::Code::Upload(revive_solidity::test_utils::compile_blob(
contract.as_str(),
std::fs::read_to_string(path).unwrap().as_str(),
))
}
Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()),
Code::Bytes(bytes) => pallet_revive::Code::Upload(bytes),
Code::Hash(hash) => pallet_revive::Code::Existing(hash),
}
}
}
/// An action to perform in a contract test
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SpecsAction {
/// Instantiate a contract
Instantiate {
origin: AccountId,
#[serde(default)]
value: Balance,
#[serde(default)]
gas_limit: Option<Weight>,
#[serde(default)]
storage_deposit_limit: Option<Balance>,
code: Code,
#[serde(default)]
data: Vec<u8>,
#[serde(default)]
salt: Vec<u8>,
},
/// Call a contract
Call {
origin: AccountId,
dest: AccountId,
#[serde(default)]
value: Balance,
#[serde(default)]
gas_limit: Option<Weight>,
#[serde(default)]
storage_deposit_limit: Option<Balance>,
#[serde(default)]
data: Vec<u8>,
},
/// Verify the result of the last call, omitting this will simply ensure the last call was successful
VerifyCall(VerifyCallExpectation),
/// Verify the balance of an account
VerifyBalance {
origin: AccountId,
expected: Balance,
},
/// Verify the storage of a contract
VerifyStorage {
contract: AccountId,
key: Vec<u8>,
expected: Option<Vec<u8>>,
},
}
/// Specs for a contract test
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Specs {
/// List of endowments at genesis
pub balances: Vec<(AccountId, Balance)>,
/// List of actions to perform
pub actions: Vec<SpecsAction>,
}
impl Specs {
/// Get the list of actions to perform
/// A default [`SpecAction::VerifyCall`] is injected after each Instantiate or Call action when
/// missing
fn actions(&self) -> Vec<SpecsAction> {
self.actions
.iter()
.enumerate()
.flat_map(|(index, item)| {
let next_item = self.actions.get(index + 1);
if matches!(
item,
SpecsAction::Instantiate { .. } | SpecsAction::Call { .. }
) && !matches!(next_item, Some(SpecsAction::VerifyCall(_)))
{
return vec![
item.clone(),
SpecsAction::VerifyCall(VerifyCallExpectation::default()),
];
}
vec![item.clone()]
})
.collect()
}
}
/// Run a contract test
/// The test takes a [`Specs`] and executes the actions in order
pub fn run_test(specs: Specs) {
ExtBuilder::default()
.balance_genesis_config(specs.balances.clone())
.build()
.execute_with(|| {
use SpecsAction::*;
let mut res: Option<CallResult> = None;
let actions = specs.actions();
for action in actions {
match action {
Instantiate {
origin,
value,
gas_limit,
storage_deposit_limit,
code,
data,
salt,
} => {
res = Some(CallResult::Instantiate(Contracts::bare_instantiate(
RuntimeOrigin::signed(origin),
value,
gas_limit.unwrap_or(GAS_LIMIT),
storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT),
code.into(),
data,
salt,
DebugInfo::Skip,
CollectEvents::Skip,
)));
}
Call {
origin,
dest,
value,
gas_limit,
storage_deposit_limit,
data,
} => {
res = Some(CallResult::Exec(Contracts::bare_call(
RuntimeOrigin::signed(origin),
dest,
value,
gas_limit.unwrap_or(GAS_LIMIT),
storage_deposit_limit.unwrap_or(DEPOSIT_LIMIT),
data,
DebugInfo::Skip,
CollectEvents::Skip,
)));
}
VerifyCall(expectation) => {
if let Some(res) = res.take() {
expectation.verify(res);
} else {
panic!("No call to verify");
}
}
VerifyBalance { origin, expected } => {
assert_eq!(Balances::free_balance(&origin), expected);
}
VerifyStorage {
contract,
key,
expected,
} => {
let Ok(storage) = Contracts::get_storage(contract, key) else {
panic!("Error reading storage");
};
assert_eq!(storage, expected);
}
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn instantiate_works() {
use SpecsAction::*;
run_test(Specs {
balances: vec![(ALICE, 1_000_000_000)],
actions: vec![Instantiate {
origin: ALICE,
value: 0,
gas_limit: Some(GAS_LIMIT),
storage_deposit_limit: Some(DEPOSIT_LIMIT),
code: Code::Bytes(include_bytes!("../fixtures/Baseline.pvm").to_vec()),
data: vec![],
salt: vec![],
}],
})
}
#[test]
fn instantiate_with_json() {
let specs = serde_json::from_str::<Specs>(
r#"
{
"balances": [
[ "5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT", 1000000000 ]
],
"actions": [
{
"Instantiate": {
"origin": "5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT",
"value": 0,
"code": {
"Path": "fixtures/Baseline.pvm"
}
}
}
]
}
"#,
)
.unwrap();
run_test(specs);
}
}
+84
View File
@@ -0,0 +1,84 @@
use frame_support::runtime;
use polkadot_sdk::*;
use polkadot_sdk::{
polkadot_sdk_frame::{log, runtime::prelude::*},
sp_runtime::{AccountId32, Perbill},
};
pub type Balance = u128;
pub type AccountId = AccountId32;
pub type Block = frame_system::mocking::MockBlock<Runtime>;
pub type Hash = <Runtime as frame_system::Config>::Hash;
pub type EventRecord =
frame_system::EventRecord<<Runtime as frame_system::Config>::RuntimeEvent, Hash>;
#[runtime]
mod runtime {
#[runtime::runtime]
#[runtime::derive(
RuntimeCall,
RuntimeEvent,
RuntimeError,
RuntimeOrigin,
RuntimeFreezeReason,
RuntimeHoldReason,
RuntimeSlashReason,
RuntimeLockId,
RuntimeTask
)]
pub struct Runtime;
#[runtime::pallet_index(0)]
pub type System = frame_system;
#[runtime::pallet_index(1)]
pub type Timestamp = pallet_timestamp;
#[runtime::pallet_index(2)]
pub type Balances = pallet_balances;
#[runtime::pallet_index(3)]
pub type Contracts = pallet_revive;
}
#[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = Block;
type AccountId = AccountId;
type AccountData = pallet_balances::AccountData<<Runtime as pallet_balances::Config>::Balance>;
}
#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
impl pallet_balances::Config for Runtime {
type AccountStore = System;
type Balance = Balance;
type ExistentialDeposit = ConstU128<1_000>;
}
#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)]
impl pallet_timestamp::Config for Runtime {}
parameter_types! {
pub const UnstableInterface: bool = true;
pub const DepositPerByte: Balance = 1;
pub const DepositPerItem: Balance = 2;
pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0);
}
#[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)]
impl pallet_revive::Config for Runtime {
type Time = Timestamp;
type Currency = Balances;
type CallFilter = ();
type ChainExtension = ();
type DepositPerByte = DepositPerByte;
type DepositPerItem = DepositPerItem;
type AddressGenerator = pallet_revive::DefaultAddressGenerator;
type UnsafeUnstableInterface = UnstableInterface;
type UploadOrigin = EnsureSigned<AccountId>;
type InstantiateOrigin = EnsureSigned<AccountId>;
type Migrations = ();
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type Debug = ();
}
+8 -7
View File
@@ -1,13 +1,14 @@
[package]
name = "revive-solidity"
version = "1.4.0"
version.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
authors = [
"Oleksandr Zarudnyi <a.zarudnyy@matterlabs.dev>",
"Cyrill Leutwiler <cyrill@parity.io>",
]
license = "MIT OR Apache-2.0"
edition = "2021"
description = "PolkaVM Solidity frontend"
description = "Solidity frontend for the revive compiler"
[[bin]]
name = "resolc"
@@ -17,7 +18,7 @@ path = "src/resolc/main.rs"
doctest = false
[dependencies]
structopt = { workspace = true }
clap = { workspace = true }
colored = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }
@@ -37,8 +38,8 @@ sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true }
revive-common = { path = "../common" }
revive-llvm-context = { path = "../llvm-context" }
revive-common = { workspace = true }
revive-llvm-context = { workspace = true }
[target.'cfg(target_env = "musl")'.dependencies]
@@ -191,11 +191,6 @@ where
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
entry.declare(context)?;
let mut runtime = revive_llvm_context::PolkaVMRuntime::new(
revive_llvm_context::PolkaVMAddressSpace::Heap,
);
runtime.declare(context)?;
revive_llvm_context::PolkaVMDeployCodeFunction::new(
revive_llvm_context::PolkaVMDummyLLVMWritable::default(),
)
@@ -207,8 +202,6 @@ where
entry.into_llvm(context)?;
runtime.into_llvm(context)?;
Ok(())
}
@@ -37,7 +37,7 @@ where
.integer_type(revive_common::BIT_LENGTH_BOOLEAN)
.const_int(0, false),
};
context.build_invoke(
context.build_call(
target,
&[is_deploy_code.as_basic_value_enum()],
format!("call_link_{}", EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME).as_str(),
@@ -168,7 +168,9 @@ where
crate::evmla::assembly::instruction::stack::push(context, value).map(Some)
}
}
InstructionName::PUSHDEPLOYADDRESS => todo!(),
InstructionName::PUSHDEPLOYADDRESS => {
revive_llvm_context::polkavm_evm_contract_context::address(context).map(Some)
}
InstructionName::DUP1 => crate::evmla::assembly::instruction::stack::dup(
context,
@@ -714,7 +716,6 @@ where
);
context.build_memcpy(
context.intrinsics().memory_copy,
destination,
source,
arguments[2].into_int_value(),
@@ -741,12 +742,21 @@ where
.map(|_| None)
}
InstructionName::TLOAD => {
let _arguments = self.pop_arguments_llvm(context);
anyhow::bail!("The `TLOAD` instruction is not supported until zkVM v1.5.0");
let arguments = self.pop_arguments_llvm(context);
revive_llvm_context::polkavm_evm_storage::transient_load(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
InstructionName::TSTORE => {
let _arguments = self.pop_arguments_llvm(context);
anyhow::bail!("The `TSTORE` instruction is not supported until zkVM v1.5.0");
let arguments = self.pop_arguments_llvm(context);
revive_llvm_context::polkavm_evm_storage::transient_store(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
)
.map(|_| None)
}
InstructionName::PUSHIMMUTABLE => {
let key = self
@@ -847,7 +857,7 @@ where
revive_llvm_context::polkavm_evm_calldata::size(context).map(Some)
}
revive_llvm_context::PolkaVMCodeType::Runtime => {
todo!()
revive_llvm_context::polkavm_evm_ext_code::size(context, None).map(Some)
}
}
}
@@ -920,7 +930,7 @@ where
let arguments = self.pop_arguments_llvm(context);
revive_llvm_context::polkavm_evm_ext_code::size(
context,
arguments[0].into_int_value(),
Some(arguments[0].into_int_value()),
)
.map(Some)
}
@@ -1027,19 +1037,16 @@ where
InstructionName::CALL => {
let mut arguments = self.pop_arguments_llvm(context);
let _gas = arguments.remove(0).into_int_value();
let _address = arguments.remove(0).into_int_value();
let _value = arguments.remove(0).into_int_value();
let _input_offset = arguments.remove(0).into_int_value();
let _input_size = arguments.remove(0).into_int_value();
let _output_offset = arguments.remove(0).into_int_value();
let _output_size = arguments.remove(0).into_int_value();
let gas = arguments.remove(0).into_int_value();
let address = arguments.remove(0).into_int_value();
let value = arguments.remove(0).into_int_value();
let input_offset = arguments.remove(0).into_int_value();
let input_size = arguments.remove(0).into_int_value();
let output_offset = arguments.remove(0).into_int_value();
let output_size = arguments.remove(0).into_int_value();
todo!()
/*
revive_llvm_context::polkavm_evm_call::default(
revive_llvm_context::polkavm_evm_call::call(
context,
context.llvm_runtime().far_call,
gas,
address,
Some(value),
@@ -1048,9 +1055,9 @@ where
output_offset,
output_size,
vec![],
false,
)
.map(Some)
*/
}
InstructionName::STATICCALL => {
let mut arguments = self.pop_arguments_llvm(context);
@@ -1062,9 +1069,8 @@ where
let output_offset = arguments.remove(0).into_int_value();
let output_size = arguments.remove(0).into_int_value();
revive_llvm_context::polkavm_evm_call::default(
revive_llvm_context::polkavm_evm_call::call(
context,
context.llvm_runtime().static_call,
gas,
address,
None,
@@ -1073,6 +1079,7 @@ where
output_offset,
output_size,
vec![],
true,
)
.map(Some)
}
@@ -1086,9 +1093,8 @@ where
let output_offset = arguments.remove(0).into_int_value();
let output_size = arguments.remove(0).into_int_value();
revive_llvm_context::polkavm_evm_call::default(
revive_llvm_context::polkavm_evm_call::delegate_call(
context,
context.llvm_runtime().delegate_call,
gas,
address,
None,
+29 -2
View File
@@ -17,13 +17,27 @@ use self::output::Output;
pub static EXECUTABLE: OnceCell<PathBuf> = OnceCell::new();
/// Read input from `stdin`, compile a contract, and write the output to `stdout`.
pub fn run() -> anyhow::Result<()> {
pub fn run(input_file: Option<&mut std::fs::File>) -> anyhow::Result<()> {
let mut stdin = std::io::stdin();
let mut stdout = std::io::stdout();
let mut stderr = std::io::stderr();
let mut buffer = Vec::with_capacity(16384);
stdin.read_to_end(&mut buffer).expect("Stdin reading error");
match input_file {
Some(ins) => {
if let Err(error) = ins.read_to_end(&mut buffer) {
anyhow::bail!("Failed to read recursive process input file: {:?}", error);
}
}
None => {
if let Err(error) = stdin.read_to_end(&mut buffer) {
anyhow::bail!(
"Failed to read recursive process input from stdin: {:?}",
error
)
}
}
}
let input: Input = revive_common::deserialize_from_slice(buffer.as_slice())?;
if input.enable_test_encoding {
@@ -74,6 +88,19 @@ pub fn call(input: Input) -> anyhow::Result<Output> {
anyhow::anyhow!("{:?} subprocess spawning error: {:?}", executable, error)
})?;
#[cfg(debug_assertions)]
if let Some(dbg_config) = &input.debug_config {
let _ = dbg_config
.dump_stage_output(&input.contract.path, Some("stage"), &input_json)
.map_err(|error| {
anyhow::anyhow!(
"{:?} failed to log the recursive process output: {:?}",
executable,
error,
)
})?;
}
process
.stdin
.as_ref()
+30 -6
View File
@@ -4,21 +4,25 @@ use std::collections::BTreeSet;
use std::path::Path;
use std::path::PathBuf;
use clap::Parser;
use path_slash::PathExt;
use structopt::StructOpt;
/// Compiles the provided Solidity input files (or use the standard input if no files
/// are given or "-" is specified as a file name). Outputs the components based on the
/// chosen options, either to the standard output or to files within the designated
/// output directory.
/// Example: resolc ERC20.sol -O3 --bin --output-dir './build/'
#[derive(Debug, StructOpt)]
#[derive(Debug, Parser)]
#[structopt(name = "The PolkaVM Solidity compiler")]
pub struct Arguments {
/// Print the version and exit.
#[structopt(long = "version")]
pub version: bool,
/// Print the licence and exit.
#[structopt(long = "license")]
pub license: bool,
/// Specify the input paths and remappings.
/// If an argument contains a '=', it is considered a remapping.
/// Multiple Solidity files can be passed in the default Solidity mode.
@@ -42,7 +46,7 @@ pub struct Arguments {
pub allow_paths: Option<String>,
/// Create one file per component and contract/file at the specified directory, if given.
#[structopt(short = "o", long = "output-dir")]
#[structopt(short = 'o', long = "output-dir")]
pub output_directory: Option<PathBuf>,
/// Overwrite existing files (used together with -o).
@@ -51,7 +55,7 @@ pub struct Arguments {
/// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Use `3` for best performance and `z` for minimal size.
#[structopt(short = "O", long = "optimization")]
#[structopt(short = 'O', long = "optimization")]
pub optimization: Option<char>,
/// Try to recompile with -Oz if the bytecode is too large.
@@ -81,7 +85,7 @@ pub struct Arguments {
/// Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`.
/// Addresses are interpreted as hexadecimal strings prefixed with `0x`.
#[structopt(short = "l", long = "libraries")]
#[structopt(short = 'l', long = "libraries")]
pub libraries: Vec<String>,
/// Output a single JSON document containing the specified information.
@@ -164,6 +168,12 @@ pub struct Arguments {
/// Only for usage from within the compiler.
#[structopt(long = "recursive-process")]
pub recursive_process: bool,
/// Specify the input file to use instead of stdin when --recursive-process is given.
/// This is only intended for use when developing the compiler.
#[cfg(debug_assertions)]
#[structopt(long = "recursive-process-input")]
pub recursive_process_input: Option<String>,
}
impl Default for Arguments {
@@ -175,7 +185,7 @@ impl Default for Arguments {
impl Arguments {
/// A shortcut constructor.
pub fn new() -> Self {
Self::from_args()
Self::parse()
}
/// Validates the arguments.
@@ -185,6 +195,20 @@ impl Arguments {
anyhow::bail!("No other options are allowed while getting the compiler version.");
}
#[cfg(debug_assertions)]
if self.recursive_process_input != None && !self.recursive_process {
anyhow::bail!("--process-input can be only used when --recursive-process is given");
}
#[cfg(debug_assertions)]
if self.recursive_process
&& ((self.recursive_process_input == None && std::env::args().count() > 2)
|| (self.recursive_process_input != None && std::env::args().count() > 4))
{
anyhow::bail!("No other options are allowed in recursive mode.");
}
#[cfg(not(debug_assertions))]
if self.recursive_process && std::env::args().count() > 2 {
anyhow::bail!("No other options are allowed in recursive mode.");
}
+15 -1
View File
@@ -39,6 +39,14 @@ fn main_inner() -> anyhow::Result<()> {
return Ok(());
}
if arguments.license {
let license_mit = include_str!("../../../../LICENSE-MIT");
let license_apache = include_str!("../../../../LICENSE-APACHE");
println!("{}\n{}\n", license_mit, license_apache);
return Ok(());
}
rayon::ThreadPoolBuilder::new()
.stack_size(RAYON_WORKER_STACK_SIZE)
.build_global()
@@ -47,7 +55,13 @@ fn main_inner() -> anyhow::Result<()> {
revive_llvm_context::initialize_target(revive_llvm_context::Target::PVM); // TODO: pass from CLI
if arguments.recursive_process {
return revive_solidity::run_process();
#[cfg(debug_assertions)]
if let Some(fname) = arguments.recursive_process_input {
let mut infile = std::fs::File::open(fname)?;
return revive_solidity::run_process(Some(&mut infile));
}
return revive_solidity::run_process(None);
}
let debug_config = match arguments.debug_output_directory {
+1 -1
View File
@@ -37,7 +37,7 @@ impl Compiler {
pub const FIRST_VIA_IR_VERSION: semver::Version = semver::Version::new(0, 8, 13);
/// The last supported version of `solc`.
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 25);
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 26);
/// A shortcut constructor.
/// Different tools may use different `executable` names. For example, the integration tester
@@ -87,9 +87,9 @@ impl Error {
│ usually needed in the following cases: │
│ 1. To detect whether an address belongs to a smart contract. │
│ 2. To detect whether the deploy code execution has finished. │
zkSync Era comes with native account abstraction support (so accounts are smart contracts,
including private-key controlled EOAs), and you should avoid differentiating between contracts
│ and non-contract addresses.
Polkadot comes with native account abstraction support (so smart contracts are just accounts
coverned by code), and you should avoid differentiating between contracts and non-contract |
| addresses.
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned();
+102
View File
@@ -1,8 +1,12 @@
//! Common utility used for in frontend and integration tests.
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Mutex;
use once_cell::sync::Lazy;
use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
@@ -14,6 +18,16 @@ use crate::solc::standard_json::output::Output as SolcStandardJsonOutput;
use crate::solc::Compiler as SolcCompiler;
use crate::warning::Warning;
static PVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default);
static EVM_BLOB_CACHE: Lazy<Mutex<HashMap<CachedBlob, Vec<u8>>>> = Lazy::new(Default::default);
#[derive(Hash, PartialEq, Eq)]
struct CachedBlob {
contract_name: String,
solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
}
/// Checks if the required executables are present in `${PATH}`.
fn check_dependencies() {
for executable in [
@@ -260,3 +274,91 @@ pub fn check_solidity_warning(
Ok(contains_warning)
}
/// 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, 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 pipeline = SolcPipeline::Yul;
let solc_optimizer_enabled = true;
let id = CachedBlob {
contract_name: contract_name.to_owned(),
pipeline,
solc_optimizer_enabled,
};
if let Some(blob) = EVM_BLOB_CACHE.lock().unwrap().get(&id) {
return blob.clone();
}
let file_name = "contract.sol";
let contracts = build_solidity_with_options_evm(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
pipeline,
solc_optimizer_enabled,
)
.expect("source should compile");
let bin_runtime = &contracts
.get(contract_name)
.unwrap_or_else(|| panic!("contract '{}' didn't produce bin-runtime", contract_name))
.object;
let blob = hex::decode(bin_runtime).expect("bin-runtime shold be hex encoded");
EVM_BLOB_CACHE.lock().unwrap().insert(id, blob.clone());
blob
}
/// 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: SolcPipeline,
) -> Vec<u8> {
let id = CachedBlob {
contract_name: contract_name.to_owned(),
solc_optimizer_enabled,
pipeline,
};
if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) {
return blob.clone();
}
let file_name = "contract.sol";
let contracts = build_solidity_with_options(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
pipeline,
revive_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")
.bytecode
.as_ref()
.expect("source should produce assembly text")
.object
.as_str();
let blob = hex::decode(bytecode).expect("hex encoding should always be valid");
PVM_BLOB_CACHE.lock().unwrap().insert(id, blob.clone());
blob
}
+8 -8
View File
@@ -7,7 +7,7 @@
"": {
"name": "cli-tests",
"version": "1.0.0",
"license": "ISC",
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.5.11",
"@types/shelljs": "^0.8.15",
@@ -1352,12 +1352,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -1787,9 +1787,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@@ -155,7 +155,7 @@ impl FunctionCall {
);
}
let return_value = context.build_invoke(
let return_value = context.build_call(
function.borrow().declaration(),
values.as_slice(),
format!("{name}_call").as_str(),
@@ -459,7 +459,6 @@ impl FunctionCall {
);
context.build_memcpy(
context.intrinsics().memory_copy,
destination,
source,
arguments[2].into_int_value(),
@@ -486,18 +485,21 @@ impl FunctionCall {
.map(|_| None)
}
Name::TLoad => {
let _arguments = self.pop_arguments_llvm::<D, 1>(context)?;
anyhow::bail!(
"{} The `TLOAD` instruction is not supported until zkVM v1.5.0",
location
);
let arguments = self.pop_arguments_llvm::<D, 1>(context)?;
revive_llvm_context::polkavm_evm_storage::transient_load(
context,
arguments[0].into_int_value(),
)
.map(Some)
}
Name::TStore => {
let _arguments = self.pop_arguments_llvm::<D, 2>(context)?;
anyhow::bail!(
"{} The `TSTORE` instruction is not supported until zkVM v1.5.0",
location
);
let arguments = self.pop_arguments_llvm::<D, 2>(context)?;
revive_llvm_context::polkavm_evm_storage::transient_store(
context,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
)
.map(|_| None)
}
Name::LoadImmutable => todo!(),
Name::SetImmutable => {
@@ -589,7 +591,7 @@ impl FunctionCall {
revive_llvm_context::polkavm_evm_calldata::size(context).map(Some)
}
revive_llvm_context::PolkaVMCodeType::Runtime => {
todo!()
revive_llvm_context::polkavm_evm_ext_code::size(context, None).map(Some)
}
}
}
@@ -630,7 +632,7 @@ impl FunctionCall {
let arguments = self.pop_arguments_llvm::<D, 1>(context)?;
revive_llvm_context::polkavm_evm_ext_code::size(
context,
arguments[0].into_int_value(),
Some(arguments[0].into_int_value()),
)
.map(Some)
}
@@ -732,24 +734,21 @@ impl FunctionCall {
Name::Call => {
let arguments = self.pop_arguments::<D, 7>(context)?;
let _gas = arguments[0].value.into_int_value();
let _address = arguments[1].value.into_int_value();
let _value = arguments[2].value.into_int_value();
let _input_offset = arguments[3].value.into_int_value();
let _input_size = arguments[4].value.into_int_value();
let _output_offset = arguments[5].value.into_int_value();
let _output_size = arguments[6].value.into_int_value();
let gas = arguments[0].value.into_int_value();
let address = arguments[1].value.into_int_value();
let value = arguments[2].value.into_int_value();
let input_offset = arguments[3].value.into_int_value();
let input_size = arguments[4].value.into_int_value();
let output_offset = arguments[5].value.into_int_value();
let output_size = arguments[6].value.into_int_value();
let _simulation_address: Vec<Option<num::BigUint>> = arguments
let simulation_address: Vec<Option<num::BigUint>> = arguments
.into_iter()
.map(|mut argument| argument.constant.take())
.collect();
todo!()
/*
revive_llvm_context::polkavm_evm_call::default(
revive_llvm_context::polkavm_evm_call::call(
context,
context.llvm_runtime().far_call,
gas,
address,
Some(value),
@@ -758,9 +757,9 @@ impl FunctionCall {
output_offset,
output_size,
simulation_address,
false,
)
.map(Some)
*/
}
Name::StaticCall => {
let arguments = self.pop_arguments::<D, 6>(context)?;
@@ -777,9 +776,8 @@ impl FunctionCall {
.map(|mut argument| argument.constant.take())
.collect();
revive_llvm_context::polkavm_evm_call::default(
revive_llvm_context::polkavm_evm_call::call(
context,
context.llvm_runtime().static_call,
gas,
address,
None,
@@ -788,6 +786,7 @@ impl FunctionCall {
output_offset,
output_size,
simulation_address,
true,
)
.map(Some)
}
@@ -806,9 +805,8 @@ impl FunctionCall {
.map(|mut argument| argument.constant.take())
.collect();
revive_llvm_context::polkavm_evm_call::default(
revive_llvm_context::polkavm_evm_call::delegate_call(
context,
context.llvm_runtime().delegate_call,
gas,
address,
None,
@@ -875,16 +873,9 @@ impl FunctionCall {
}
Name::DataCopy => {
let arguments = self.pop_arguments_llvm::<D, 3>(context)?;
let offset = context.builder().build_int_add(
arguments[0].into_int_value(),
context.word_const(
(revive_common::BYTE_LENGTH_X32 + revive_common::BYTE_LENGTH_WORD) as u64,
),
"datacopy_contract_hash_offset",
)?;
revive_llvm_context::polkavm_evm_memory::store(
context,
offset,
arguments[0].into_int_value(),
arguments[1].into_int_value(),
)
.map(|_| None)

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