mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-14 15:41:04 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c1a635dc3 | |||
| 8d83f6d177 | |||
| 00019eeec4 | |||
| 33cbfe1da5 | |||
| d763e30b8f | |||
| 5d742d150d | |||
| 7844bbb604 | |||
| 0903718f07 | |||
| 880305dbfb | |||
| bb4a4dddde | |||
| 184d40d377 | |||
| bd89ebc45a | |||
| 44bc7b94b2 | |||
| b002382d76 | |||
| e22eebabad | |||
| 16a0cc46e6 | |||
| 6834751522 | |||
| a4c4ad55dc | |||
| 0d39b289cb | |||
| 10c7045e15 | |||
| 39d78179d4 | |||
| 9e9227d740 | |||
| a04eacabff | |||
| 68ec8be49f | |||
| caa1228720 | |||
| d9a304d162 | |||
| 2d0a0e2e81 | |||
| 354b1c8d79 | |||
| 5ff17da695 | |||
| 1ba806be1f | |||
| 532721f3be | |||
| 2ea10d0c3e | |||
| 4f0a109771 | |||
| bdaf573f17 | |||
| 0e90f1fd8c | |||
| 5138fe3d06 | |||
| 06aa289d9b | |||
| 42697edc67 | |||
| d8be21f156 | |||
| 47d5d6b394 |
@@ -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
@@ -11,4 +11,6 @@
|
||||
node_modules
|
||||
artifacts
|
||||
tmp
|
||||
package-lock.json
|
||||
package-lock.json
|
||||
/*.html
|
||||
/build
|
||||
|
||||
Generated
+8324
-317
File diff suppressed because it is too large
Load Diff
+33
-7
@@ -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"]
|
||||
|
||||
|
||||
@@ -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 ; \
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||

|
||||
|
||||
# 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
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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, ¶meters, ¶meters, 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, ¶meters, ¶meters, 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, ¶meters, ¶meters, Contract::fib_binet);
|
||||
}
|
||||
|
||||
fn bench_sha1(c: &mut Criterion) {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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.
@@ -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"] }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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"] }
|
||||
@@ -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) }
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"}();
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,6 +4,10 @@ pragma solidity ^0.8;
|
||||
contract Flipper {
|
||||
bool coin;
|
||||
|
||||
constructor(bool _coin) {
|
||||
coin = _coin;
|
||||
}
|
||||
|
||||
function flip() public {
|
||||
coin = !coin;
|
||||
}
|
||||
|
||||
+126
-229
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(':', ".");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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(¶meters, 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 = ¶meters[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",
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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.
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 = ();
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user