Compare commits

..

9 Commits

Author SHA1 Message Date
Sebastian Miasojed ff6bb5593d Fix package creation issue 2025-01-24 16:02:01 +01:00
Sebastian Miasojed 3035542a1c Add minification for resolc_packed.js 2025-01-24 15:37:49 +01:00
Sebastian Miasojed e9d3ec2079 Rollback soljson cleaning 2025-01-23 17:20:11 +01:00
Sebastian Miasojed e2ccdaae00 Add Wasm compression 2025-01-23 16:26:20 +01:00
Sebastian Miasojed a4e29b3f3e Apply revive comments 2025-01-23 15:32:54 +01:00
Sebastian Miasojed 66534f4e8c Allow GC to do the cleanup 2025-01-23 14:46:04 +01:00
Sebastian Miasojed 66975af7bc Upload resolc_packed.js from GHA 2025-01-23 12:12:16 +01:00
Sebastian Miasojed 82f83c910a Add again resolc.wasm link 2025-01-23 12:05:56 +01:00
Sebastian Miasojed 8a18f08aff Pack resolc.wasm and resolc.js to resolc_packed.js 2025-01-23 11:59:50 +01:00
121 changed files with 7506 additions and 3001 deletions
+3 -2
View File
@@ -12,6 +12,7 @@ rustflags = [
"-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/embed/pre.js",
"-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0"
"-Clink-arg=-sNODEJS_CATCH_EXIT=0",
"-Clink-arg=-sDISABLE_EXCEPTION_CATCHING=0",
"-Copt-level=3"
]
+17 -1
View File
@@ -9,6 +9,7 @@ on:
env:
CARGO_TERM_COLOR: always
REVIVE_WASM_INSTALL_DIR: ${{ github.workspace }}/target/wasm32-unknown-emscripten/release
BUN_VERSION: 1.1.43
jobs:
build-revive-wasm:
@@ -86,7 +87,7 @@ jobs:
path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_web.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_packed.js
retention-days: 1
test-revive-wasm:
@@ -112,6 +113,21 @@ jobs:
with:
node-version: "20"
- name: Install Bun on Windows
if: runner.os == 'Windows'
run: |
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
iex (new-object net.webclient).downloadstring('https://get.scoop.sh')
scoop install bun@${{ env.BUN_VERSION }}
scoop install wget
Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH
- name: Install Bun on macOS and Linux
if: runner.os != 'Windows'
run: |
curl -fsSL https://bun.sh/install | bash -s bun-v${{ env.BUN_VERSION }}
echo "$HOME/.bun/bin" >> $GITHUB_PATH
- name: Install packages
run: npm install
-36
View File
@@ -1,36 +0,0 @@
module.exports = async ({
octokit,
context,
releasePrefix,
artifactSuffix,
}) => {
let page = 1;
while (true) {
const res = await octokit.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
page,
});
if (res.data.length === 0) {
throw new Error(
`No LLVM releases with '${artifactSuffix}' atifacts found! Please release LLVM before running this workflow.`,
);
}
for (let release of res.data) {
if (release.tag_name.startsWith(releasePrefix)) {
for (let asset of release.assets) {
if (asset.name.includes(artifactSuffix)) {
return asset.browser_download_url;
}
}
console.warn(
`LLVM release ${release.tag_name} doesn't have a '${artifactSuffix}' artifact; searching for older releases...`,
);
}
}
page++;
}
};
-167
View File
@@ -1,167 +0,0 @@
name: Release LLVM
on:
workflow_dispatch:
inputs:
llvm_version:
type: string
required: true
description: llvm version in "x.x.x" format, e.g. "18.1.8"
env:
CARGO_TERM_COLOR: always
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version: ${{ steps.resolve-version.outputs.version }}
steps:
- id: resolve-version
run: |
echo "version=llvm-${{ inputs.llvm_version }}-revive.${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
- name: create release
uses: softprops/action-gh-release@v2
with:
name: "LLVM binaries release: ${{ steps.resolve-version.outputs.version }}"
body: "This release includes binaries of LLVM, used to compile revive itself"
make_latest: "false"
tag_name: ${{ steps.resolve-version.outputs.version }}
build-macos:
strategy:
matrix:
os: [macos-14, macos-13]
include:
- os: macos-13
arch: x64
- os: macos-14
arch: arm64
needs: create-release
runs-on: ${{ matrix.os }}
name: "build-macos-${{ matrix.arch }}"
env:
RUST_LOG: trace
permissions:
contents: write # for uploading assets to release
steps:
- uses: actions/checkout@v4
- name: install macos deps
run: |
brew install ninja
- name: versions
run: |
rustup show
cargo --version
cmake --version
echo "bash:" && bash --version
echo "ninja:" && ninja --version
echo "clang:" && clang --version
- name: Build LLVM
run: |
make install-llvm
- name: clean
# check removed files
run: |
cd target-llvm/gnu/target-final/bin/
rm diagtool llvm-libtool-darwin llvm-lipo llvm-pdbutil llvm-dwarfdump llvm-nm llvm-readobj llvm-cfi-verify \
sancov llvm-debuginfo-analyzer llvm-objdump llvm-profgen llvm-extract llvm-jitlink llvm-c-test llvm-gsymutil llvm-dwp \
dsymutil llvm-dwarfutil llvm-exegesis lli clang-rename bugpoint clang-extdef-mapping clang-refactor c-index-test \
llvm-reduce llvm-lto clang-linker-wrapper llc llvm-lto2
- name: package artifacts
run: |
tar -czf "${{ needs.create-release.outputs.version }}-macos-${{ matrix.arch }}.tar.gz" target-llvm/gnu/target-final
- name: upload archive to release
uses: softprops/action-gh-release@v2
with:
make_latest: "false"
tag_name: ${{ needs.create-release.outputs.version }}
files: |
${{ needs.create-release.outputs.version }}-macos-${{ matrix.arch }}.tar.gz
build-linux-all:
needs: create-release
runs-on: parity-large
env:
RUST_LOG: trace
permissions:
contents: write # for uploading assets to release
steps:
- uses: actions/checkout@v4
- name: install linux deps
run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build curl git libssl-dev pkg-config clang lld musl
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: rust-src
target: wasm32-unknown-emscripten
rustflags: ""
- name: versions
run: |
rustup show
cargo --version
cmake --version
echo "bash:" && bash --version
echo "ninja:" && ninja --version
echo "clang:" && clang --version
- name: Build host LLVM
run: |
make install-llvm
- name: Build gnu LLVM
run: |
revive-llvm clone
revive-llvm build --llvm-projects lld --llvm-projects clang
- name: Build musl LLVM
run: |
revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang
- name: Build emscripten LLVM
run: |
revive-llvm --target-env emscripten clone
source emsdk/emsdk_env.sh
revive-llvm --target-env emscripten build --llvm-projects lld
- name: clean
# check removed files
run: |
for target in gnu emscripten musl; do
cd target-llvm/${target}/target-final/bin/
rm -rf diagtool llvm-libtool-darwin llvm-lipo llvm-pdbutil llvm-dwarfdump llvm-nm llvm-readobj llvm-cfi-verify \
sancov llvm-debuginfo-analyzer llvm-objdump llvm-profgen llvm-extract llvm-jitlink llvm-c-test llvm-gsymutil llvm-dwp \
dsymutil llvm-dwarfutil llvm-exegesis lli clang-rename bugpoint clang-extdef-mapping clang-refactor c-index-test \
llvm-reduce llvm-lto clang-linker-wrapper llc llvm-lto2 llvm-otool llvm-readelf
cd -
done
- name: package artifacts
run: |
tar -czf "${{ needs.create-release.outputs.version }}-x86_64-linux-gnu-linux.tar.gz" target-llvm/gnu/target-final
tar -czf "${{ needs.create-release.outputs.version }}-x86_64-linux-musl.tar.gz" target-llvm/musl/target-final
tar -czf "${{ needs.create-release.outputs.version }}-wasm32-unknown-emscripten.tar.gz" target-llvm/emscripten/target-final
- name: upload archive to release
uses: softprops/action-gh-release@v2
with:
make_latest: "false"
tag_name: ${{ needs.create-release.outputs.version }}
files: |
${{ needs.create-release.outputs.version }}-x86_64-linux-gnu-linux.tar.gz
${{ needs.create-release.outputs.version }}-x86_64-linux-musl.tar.gz
${{ needs.create-release.outputs.version }}-wasm32-unknown-emscripten.tar.gz
-366
View File
@@ -1,366 +0,0 @@
name: Release
run-name: Release ${{ github.ref_name }}
on:
push:
branches:
- "main"
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
env:
#rust-musl-cross:x86_64-musl
RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:68b86bc7cb2867259e6b233415a665ff4469c28b57763e78c3bfea1c68091561
RUST_LOG: trace
jobs:
tag:
runs-on: ubuntu-24.04
permissions:
contents: write
outputs:
TAG: ${{ steps.versions.outputs.TAG }}
PKG_VER: ${{ steps.versions.outputs.PKG_VER }}
RELEASE_NOTES: ${{ steps.versions.outputs.RELEASE_NOTES }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-tags: "true"
fetch-depth: 0
- name: Versions
id: versions
run: |
export CURRENT_TAG=$(git describe --tags --abbrev=0 --exclude "llvm-*")
export PKG_VER=v$(cat Cargo.toml | grep -A 5 package] | grep version | cut -d '=' -f 2 | tr -d '"' | tr -d " ")
echo "Current tag $CURRENT_TAG"
echo "Package version $PKG_VER"
#
echo "PKG_VER=$PKG_VER" >> $GITHUB_OUTPUT
if [[ $CURRENT_TAG == $PKG_VER ]];
then
echo "Tag is up to date. Nothing to do.";
export TAG=old;
else
echo "Tag was updated.";
export TAG=new;
fi
echo "TAG=$TAG" >> $GITHUB_OUTPUT
# Generating release notes early, in order to avoid checkout at the last step
export RELEASE_NOTES="$(sed '/^## '${PKG_VER}'/,/^## v/!d' CHANGELOG.md | sed -e '1d' -e '$d')"
echo "Release notes:"
echo "$RELEASE_NOTES"
echo 'RELEASE_NOTES<<EOF' >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo 'EOF' >> $GITHUB_OUTPUT
build-macos:
strategy:
matrix:
os: [macos-14, macos-13]
include:
- os: macos-13
arch: x64
- os: macos-14
arch: arm64
if: ${{ needs.tag.outputs.TAG == 'new' }}
runs-on: ${{ matrix.os }}
name: "build-macos-${{ matrix.arch }}"
needs: [tag]
steps:
- uses: actions/checkout@v4
- name: Get latest macos ${{ matrix.arch }} LLVM release artifact
id: get-llvm-artifact
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const getReleaseArtifact = require("./.github/workflows/get-release-artifact.js");
return await getReleaseArtifact({
octokit: github,
context,
releasePrefix: "llvm-",
artifactSuffix: "-macos-${{ matrix.arch }}"
});
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: rust-src
target: wasm32-unknown-emscripten
rustflags: ""
- name: install macos deps
run: |
brew install ninja
- name: versions
run: |
rustup show
cargo --version
cmake --version
echo "bash:" && bash --version
echo "ninja:" && ninja --version
echo "clang:" && clang --version
- name: download llvm
run: |
curl -L -o llvm.tar.gz "${{ steps.get-llvm-artifact.outputs.result }}"
tar -xvf llvm.tar.gz
- name: build revive
run: |
export LLVM_SYS_181_PREFIX=$PWD/target-llvm/gnu/target-final
make install-bin
cp ./target/release/resolc ./target/release/resolc-${{ matrix.arch }}
- name: check revive
run: |
mkdir solc
curl -sSLo solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.28/solc-macos
chmod +x solc/solc
PATH=$PWD/solc:$PATH
result=$(./target/release/resolc-${{ matrix.arch }} --bin crates/integration/contracts/flipper.sol)
echo $result
if [[ $result == *'0x50564d'* ]]; then exit 0; else exit 1; fi
- uses: actions/upload-artifact@v4
with:
name: "revive-macos-${{ matrix.arch }}"
path: |
./target/release/resolc-${{ matrix.arch }}
retention-days: 1
macos-universal-binary:
runs-on: macos-14
needs: [build-macos]
steps:
- uses: actions/download-artifact@v4
with:
pattern: revive-macos-*
path: revive-macos
- name: run lipo
run: |
lipo revive-macos/revive-macos-arm64/resolc-arm64 revive-macos/revive-macos-x64/resolc-x64 -create -output resolc-macos
- name: compress macos artifact
run: |
tar -czf resolc-macos.tar.gz ./resolc-macos
- uses: actions/upload-artifact@v4
with:
name: revive-macos
path: |
resolc-macos.tar.gz
retention-days: 1
build-linux-all:
if: ${{ needs.tag.outputs.TAG == 'new' }}
runs-on: parity-large
needs: [tag]
steps:
- uses: actions/checkout@v4
- name: Get latest linux LLVM release artifact
id: get-llvm-musl-artifact
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const getReleaseArtifact = require("./.github/workflows/get-release-artifact.js")
return await getReleaseArtifact({
octokit: github,
context,
releasePrefix: "llvm-",
artifactSuffix: "-x86_64-linux-musl"
})
- name: install linux deps
run: |
sudo apt-get update && sudo apt-get install -y cmake ninja-build \
curl git libssl-dev pkg-config clang lld musl
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: rust-src
target: wasm32-unknown-emscripten
rustflags: ""
- name: versions
run: |
rustup show
cargo --version
cmake --version
echo "bash:" && bash --version
echo "ninja:" && ninja --version
echo "clang:" && clang --version
- name: download llvm
run: |
curl -L -o llvm.tar.gz "${{ steps.get-llvm-musl-artifact.outputs.result }}"
tar -xvf llvm.tar.gz
# Build revive
- name: build musl
run: |
mkdir resolc-out
docker run -v $PWD:/opt/revive $RUST_MUSL_CROSS_IMAGE /bin/bash -c "
cd /opt/revive
apt update && apt upgrade -y && apt install -y pkg-config
export LLVM_SYS_181_PREFIX=/opt/revive/target-llvm/musl/target-final
make install-bin
cp /root/.cargo/bin/resolc /opt/revive/resolc-out/resolc-static-linux
"
- name: check musl
run: |
mkdir solc
curl -sSLo solc/solc https://github.com/ethereum/solidity/releases/download/v0.8.28/solc-static-linux
chmod +x solc/solc
PATH=$PWD/solc:$PATH
result=$(./resolc-out/resolc-static-linux --bin crates/integration/contracts/flipper.sol)
echo $result
if [[ $result == *'0x50564d'* ]]; then exit 0; else exit 1; fi
- name: compress musl artifact
run: |
tar --strip-components 1 -czf resolc-static-linux.tar.gz ./resolc-out/resolc-static-linux
- uses: actions/upload-artifact@v4
with:
name: revive-linux
path: |
./resolc-static-linux.tar.gz
retention-days: 1
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: Get latest emscripten LLVM release artifact
id: get-llvm-emscripten-artifact
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const getReleaseArtifact = require("./.github/workflows/get-release-artifact.js")
return await getReleaseArtifact({
octokit: github,
context,
releasePrefix: "llvm-",
artifactSuffix: "-wasm32-unknown-emscripten"
})
- name: download llvm
run: |
curl -L -o llvm.tar.gz "${{ steps.get-llvm-emscripten-artifact.outputs.result }}"
tar -xvf llvm.tar.gz
- name: build wasm
run: |
make install-llvm-builder
revive-llvm --target-env emscripten clone
export LLVM_SYS_181_PREFIX=$PWD/target-llvm/musl/target-final
export REVIVE_LLVM_TARGET_PREFIX=$PWD/target-llvm/emscripten/target-final
source emsdk/emsdk_env.sh
rustup target add wasm32-unknown-emscripten
make install-wasm
- name: check wasm
run: |
curl -sSLo solc/soljson.js https://github.com/ethereum/solidity/releases/download/v0.8.28/soljson.js
node -e "
const soljson = require('solc/soljson');
const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js');
const compiler = createRevive();
compiler.soljson = soljson;
const standardJsonInput =
{
language: 'Solidity',
sources: {
'MyContract.sol': {
content: 'pragma solidity ^0.8.0; contract MyContract { function greet() public pure returns (string memory) { return \'Hello\'; } }',
},
},
settings: { optimizer: { enabled: false } }
};
compiler.writeToStdin(JSON.stringify(standardJsonInput));
compiler.callMain(['--standard-json']);
// Collect output
const stdout = compiler.readFromStdout();
const stderr = compiler.readFromStderr();
if (stderr) { console.error(stderr); process.exit(1); }
let out = JSON.parse(stdout);
let bytecode = out.contracts['MyContract.sol']['MyContract'].evm.bytecode.object
console.log(bytecode);
if(!bytecode.startsWith('50564d')) { process.exit(1); }
"
- name: compress wasm artifact
run: |
tar --strip-components 3 -czf resolc-wasm.tar.gz \
./target/wasm32-unknown-emscripten/release/resolc.js \
./target/wasm32-unknown-emscripten/release/resolc.wasm \
./target/wasm32-unknown-emscripten/release/resolc_web.js
- uses: actions/upload-artifact@v4
with:
name: revive-wasm
path: |
resolc-wasm.tar.gz
retention-days: 1
create-release:
needs: [tag, build-linux-all, macos-universal-binary]
runs-on: ubuntu-24.04
permissions:
contents: write
outputs:
upload_url: ${{ steps.create_release.outputs.result }}
steps:
- name: Download revive-wasm
uses: actions/download-artifact@v4
with:
name: revive-wasm
path: resolc-wasm/
- name: Download revive-linux
uses: actions/download-artifact@v4
with:
name: revive-linux
path: resolc-linux/
- name: Download revive-macos
uses: actions/download-artifact@v4
with:
name: revive-macos
path: resolc-macos/
- name: create-release
uses: softprops/action-gh-release@v2
with:
body: ${{ needs.tag.outputs.RELEASE_NOTES }}
tag_name: ${{ needs.tag.outputs.PKG_VER }}
name: ${{ needs.tag.outputs.PKG_VER }}
draft: true
files: |
./resolc-linux/resolc-static-linux.tar.gz
./resolc-macos/resolc-macos.tar.gz
./resolc-wasm/resolc-wasm.tar.gz
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
tar Jxf llvm.tar.xz -C llvm18/
echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm18" >> $GITHUB_ENV
- name: Install geth
- name: Install apt dependencies
run: |
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
-56
View File
@@ -2,62 +2,6 @@
## Unreleased
This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
## v0.1.0-dev.11
This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added
### Changed
### Fixed
- A bug causing incorrect loads from the emulated EVM linear memory.
- A missing integer truncate after switching to 64bit.
## v0.1.0-dev.10
This is a development pre-release.
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
### Added
- Support for the `coinbase` opcode.
- The resolc web JS version.
### Changed
- Missing the `--overwrite` flag emits an error instead of a warning.
- The `resolc` executable prints the help by default.
- Removed support for legacy EVM assembly (EVMLA) translation.
- integration: identify cached code blobs on source code to fix potential confusions.
- Setting base, include or allow paths in emscripten is now a hard error.
- Employ a heuristic to detect `address.transfer` and `address.send` calls.
If detected, the re-entrant call flag is not set and 0 deposit limit is endowed.
### Fixed
- Solidity: Add the solc `--libraries` files to sources.
- A data race in tests.
- Fix `broken pipe` errors.
- llvm-builder: Allow warnings.
- solidity: Fix the custom compiler warning messages.
## v0.1.0-dev.9
This is a development pre-release.
### Added
### Changed
- Syscalls with more than 6 arguments now pack them into registers.
### Fixed
- Remove reloading of the resolc.js file (fix issue with relative path in web worker)
## v0.1.0-dev.8
This is a development pre-release.
Generated
+680 -815
View File
File diff suppressed because it is too large Load Diff
+34 -33
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"]
[workspace.package]
version = "0.1.0-dev.11"
version = "0.1.0-dev.8"
authors = [
"Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>",
@@ -14,47 +14,48 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0"
[workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.11", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.11", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.11", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.11", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.11", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.11", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.11", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.11", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.11", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.11", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.11", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.11", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.11", path = "crates/build-utils" }
revive-benchmarks = { version = "0.1.0-dev.8", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.8", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.8", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.8", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.8", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.8", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.8", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.8", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.8", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.8", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.8", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.8", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.8", path = "crates/build-utils" }
hex = "0.4.3"
cc = "1.2"
cc = "1.0"
libc = "0.2.169"
tempfile = "3.17"
tempfile = "3.8"
anyhow = "1.0"
semver = { version = "1.0", features = ["serde"] }
semver = { version = "1.0", features = [ "serde" ] }
itertools = "0.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
serde = { version = "1.0", features = [ "derive" ] }
serde_json = { version = "1.0", features = [ "arbitrary_precision" ] }
regex = "1.10"
once_cell = "1.20"
once_cell = "1.19"
num = "0.4.3"
sha1 = "0.10"
sha3 = "0.10"
md5 = "0.7.0"
thiserror = "2.0"
which = "7.0"
path-slash = "0.2"
rayon = "1.8"
clap = { version = "4", default-features = false, features = ["derive"] }
polkavm-common = "0.21.0"
polkavm-linker = "0.21.0"
polkavm-disassembler = "0.21.0"
polkavm = "0.21.0"
alloy-primitives = { version = "0.8.21", features = ["serde"] }
alloy-sol-types = "0.8.21"
alloy-genesis = "0.11.1"
alloy-serde = "0.11.1"
polkavm-common = "0.19.0"
polkavm-linker = "0.19.0"
polkavm-disassembler = "0.19.0"
polkavm = "0.19.0"
alloy-primitives = { version = "0.8.19", features = ["serde"] }
alloy-sol-types = "0.8.19"
alloy-genesis = "0.9.2"
alloy-serde = "0.9.2"
env_logger = { version = "0.11.6", default-features = false }
serde_stacker = "0.1.11"
criterion = { version = "0.5.1", features = ["html_reports"] }
@@ -64,15 +65,15 @@ downloader = "0.2.8"
flate2 = "1.0.35"
fs_extra = "1.3.0"
num_cpus = "1"
tar = "0.4"
toml = "0.8"
assert_cmd = "2.0"
assert_fs = "1.1"
tar = "0.4.43"
toml = "0.8.19"
assert_cmd = "2.0.16"
assert_fs = "1.1.2"
# polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "274a781e8ca1a9432c7ec87593bd93214abbff50" }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "d62a90c8c729acd98c7e9a5cab9803b8b211ffc5" }
# llvm
[workspace.dependencies.inkwell]
+1 -1
View File
@@ -43,7 +43,7 @@ format:
cargo fmt --all --check
clippy:
cargo clippy --all-features --workspace --tests --benches -- --deny warnings
cargo clippy --all-features --workspace --tests --benches -- --deny warnings --allow dead_code
machete:
cargo install cargo-machete
+17 -43
View File
@@ -1,9 +1,9 @@
![CI](https://github.com/paritytech/revive/actions/workflows/rust.yml/badge.svg)
[![Docs](https://img.shields.io/badge/Docs-contracts.polkadot.io-brightgreen.svg)](https://contracts.polkadot.io/revive_compiler/)
[![Docs](https://img.shields.io/badge/Docs-contracts.polkadot.io-brightgreen.svg)](https://contracts.polkadot.io)
# revive
YUL recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
YUL and EVM assembly recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm).
Visit [contracts.polkadot.io](https://contracts.polkadot.io) to learn more about contracts on Polkadot!
@@ -14,7 +14,10 @@ This is experimental software in active development and not ready just yet for p
Discussion around the development is hosted on the [Polkadot Forum](https://forum.polkadot.network/t/contracts-update-solidity-on-polkavm/6949#a-new-solidity-compiler-1).
## Installation
Please consult [the documentation](https://contracts.polkadot.io/revive_compiler/installation) for installation instructions.
`resolc` depends on the [solc](https://github.com/ethereum/solidity) binary installed on your system.
Download and install the `resolc` frontend executable for your platform from our [releases](https://github.com/paritytech/revive/releases).
## Building from source
@@ -22,46 +25,22 @@ Building revive requires a [stable Rust installation](https://rustup.rs/) and a
### LLVM
`revive` depends on a custom build of LLVM `v18.1.8` with the RISC-V _embedded_ target, including the `compiler-rt` builtins. You can either download a build from our releases (recommended for older hardware) or build it from source.
<details>
<summary>Download from our LLVM releases</summary>
Download the [latest LLVM build](https://github.com/paritytech/revive/releases?q=LLVM+binaries+release&expanded=true) from our releases.
> **MacOS** users need to clear the `downloaded` attribute from all binaries after extracting the archive:
> ```sh
> xattr -rc </path/to/the/extracted/archive>/target-llvm/gnu/target-final/bin/*
> ```
After extracting the archive, point `$LLVM_SYS_181_PREFIX` to it:
```sh
export LLVM_SYS_181_PREFIX=</path/to/the/extracted/archive>/target-llvm/gnu/target-final
```
</details>
<details>
<summary>Building from source</summary>
Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards.
`revive` depends on a custom build of LLVM `v18.1.8` with the RISC-V _embedded_ target, including the `compiler-rt` builtins. Use the provided [revive-llvm](crates/llvm-builder/README.md) utility to compile a compatible LLVM build locally and point `$LLVM_SYS_181_PREFIX` to the installation afterwards.
The `Makefile` provides a shortcut target to obtain a compatible LLVM build:
```sh
```bash
make install-llvm
export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final
```
</details>
### The `resolc` Solidity frontend
To build the `resolc` Solidity frontend executable, make sure you have obtained a compatible LLVM build and did export the `LLVM_SYS_181_PREFIX` environment variable pointing to it (see [above](#LLVM)).
To build the `resolc` Solidity frontend executable, make sure you have obtained a compatible LLVM build using [revive-llvm](crates/llvm-builder/README.md) and did export the `LLVM_SYS_181_PREFIX` environment variable pointing to it (see [above](#LLVM)).
To install the `resolc` Solidity frontend executable:
```sh
```bash
make install-bin
resolc --version
```
@@ -70,10 +49,7 @@ resolc --version
Cross-compile the `resolc.js` frontend executable to Wasm for running it in a Node.js or browser environment. The `REVIVE_LLVM_TARGET_PREFIX` environment variable is used to control the target environment LLVM dependency.
<details>
<summary>Instructions for cross-compilation to wasm32-unknown-emscripten</summary>
```sh
```bash
# Build the host LLVM dependency with PolkaVM target support
make install-llvm
export LLVM_SYS_181_PREFIX=${PWD}/target-llvm/gnu/target-final
@@ -89,23 +65,21 @@ make install-wasm
make test-wasm
```
</details>
## Development
### 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
See the [relevant section in our documentation](https://contracts.polkadot.io/revive_compiler/architecture) to learn more about how the compiler works.
## Design overview
[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` (the project started as a fork of the era compiler).
`revive` uses [solc](https://github.com/ethereum/solidity/), the Ethereum Solidity compiler, as the [Solidity frontend](crates/solidity/src/lib.rs) to process smart contracts written in Solidity. The YUL IR code (or legacy EVM assembly as a fallback for older `solc` versions) emitted by `solc` is then translated to LLVM IR, targetting [Polkadots `revive` pallet](https://docs.rs/pallet-revive/latest/pallet_revive/trait.SyscallDoc.html).
[Frontend](https://github.com/matter-labs/era-compiler-solidity) and [code generator](https://github.com/matter-labs/era-compiler-llvm-context) are based of ZKSync `zksolc`.
### Tests
## Tests
Before running the tests, ensure that Geth (Go Ethereum) is installed on your system. Follow the installation guide here: [Installing Geth](https://geth.ethereum.org/docs/getting-started/installing-geth).
Once Geth is installed, you can run the tests using the following command:
```sh
```bash
make test
```
+5 -11
View File
@@ -4,14 +4,8 @@ Prior to the first stable release we neither have formal release processes nor d
To create a new pre-release:
1. Merge a release PR which updates the `-dev.X` versions in the workspace `Cargo.toml` and updates the `CHANGELOG.md` accordingly. The release workflow will attempt to build and publish a new release whenever the latest git tag does not match the cargo package version.
2. Wait for the `Release` workflow to finish. If the workflow fails after the `build-linux-all` step, check if a tag has been created and delete it before restarting or pushing updates. Note: It's more convenient to debug the release workflow in a fork (the fork has to be under the `paritytech` org to access `parity-large` runners).
3. Check draft release on [Releases page](https://github.com/paritytech/revive/releases) and publish (should contain `resolc.js`, `resolc.wasm`, `resolc-web.js`, and `resolc-static-linux` release assets)
4. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
# LLVM release
To create a new LLVM release, run "Release LLVM" workflow. Use current LLVM version as parameter, e.g. `18.1.8`.
Version suffix will be resolved automatically.
The workflows will create new GitHub release, and upload LLVM binaries.
Next release of resolc will use newly created binaries.
1. Merge a release PR which updates the `-dev.X` versions in the workspace `Cargo.toml` and updates the `CHANGELOG.md` accordingly
2. Push a release tag to `main`
3. Create a __pre-release__ from the tag and manually upload the `resolc` binary from docker image
4. Manually upload `resolc.js` and `resolc.wasm` from the `build-revive-wasm` action artifacts.
5. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
+6
View File
@@ -12,6 +12,12 @@ pub static EXTENSION_ABI: &str = "abi";
/// The Yul IR file extension.
pub static EXTENSION_YUL: &str = "yul";
/// The EVM legacy assembly IR file extension.
pub static EXTENSION_EVMLA: &str = "evmla";
/// The Ethereal IR file extension.
pub static EXTENSION_ETHIR: &str = "ethir";
/// The EVM file extension.
pub static EXTENSION_EVM: &str = "evm";
+2 -9
View File
@@ -16,16 +16,9 @@
"shanghaiTime": 0,
"cancunTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
"terminalTotalDifficultyPassed": true
},
"coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"coinbase": "0x0000000000000000000000000000000000000000",
"difficulty": "0x20000",
"extraData": "",
"gasLimit": "0xffffffff",
+1 -1
View File
@@ -413,7 +413,7 @@ impl Evm {
let stderr = str::from_utf8(output.stderr.as_slice())
.unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stderr failed to parse: {err}"));
let mut log: EvmLog = format!("{stdout}{stderr}").as_str().into();
let mut log: EvmLog = stdout.into();
log.stderr = stderr.into();
if self.bench {
log.parse_gas_used_from_bench();
-81
View File
@@ -1,81 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "BalanceReceiver"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Balance"
}
},
"value": 24
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "6ada15d90000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
}
]
}
*/
contract BalanceReceiver {
constructor() payable {}
fallback() external payable {}
}
contract Balance {
constructor() payable {
// 0 to EOA
transfer_to(payable(address(0xdeadbeef)), 0);
send_to(payable(address(0xdeadbeef)), 0);
// 1 to EOA
transfer_to(payable(address(0xcafebabe)), 1);
send_to(payable(address(0xcafebabe)), 1);
BalanceReceiver balanceReceiver = new BalanceReceiver();
// 0 to contract
transfer_to(payable(address(balanceReceiver)), 0);
send_to(payable(address(balanceReceiver)), 0);
// 1 to contract
transfer_to(payable(address(balanceReceiver)), 1);
send_to(payable(address(balanceReceiver)), 1);
}
function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount);
}
function send_to(address payable _dest, uint _amount) public payable {
require(_dest.send(_amount));
}
}
-27
View File
@@ -1,27 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Coinbase"
}
}
}
}
]
}
*/
contract Coinbase {
constructor() payable {
address coinbase = address(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF);
assert(block.coinbase == coinbase);
}
}
-50
View File
@@ -1,50 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Upload": {
"code": {
"Solidity": {
"contract": "CreateA"
}
}
}
},
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "CreateB"
}
},
"value": 100000
}
}
]
}
*/
contract CreateA {
constructor() payable {}
}
contract CreateB {
constructor() payable {
bytes32 salt = hex"ff";
try new CreateA{salt: salt}() returns (CreateA) {} catch {
revert("the first instantiation should succeed");
}
try new CreateA{salt: salt}() returns (CreateA) {} catch {
return;
}
revert("the second instantiation should have failed");
}
}
@@ -1,42 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "FunctionPointer"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "26121ff0"
}
}
]
}
*/
contract FunctionPointer {
bool public flag = false;
function f0() public {
flag = true;
}
function f() public returns (bool) {
function() internal x = f0;
x();
return flag;
}
}
-40
View File
@@ -1,40 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
/* runner.json
{
"differential": true,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "MLoad"
}
}
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "e2179b8e"
}
}
]
}
*/
contract MLoad {
constructor() payable {
assert(g() == 0);
}
function g() public payable returns (uint m) {
assembly {
m := mload(0)
}
}
}
-75
View File
@@ -1,75 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"actions": [
{
"Instantiate": {
"code": {
"Solidity": {
"contract": "Send"
}
},
"value": 211
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"VerifyCall": {
"success": true
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
}
}
]
}
*/
contract Send {
constructor() payable {}
function transfer_self(uint _amount) public payable {
transfer_to(payable(address(this)), _amount);
}
function transfer_to(address payable _dest, uint _amount) public payable {
if (_dest.send(_amount)) {}
}
fallback() external {}
receive() external payable {}
}
+11 -32
View File
@@ -3,7 +3,7 @@ pragma solidity ^0.8;
/* runner.json
{
"differential": false,
"differential": true,
"actions": [
{
"Instantiate": {
@@ -12,7 +12,7 @@ pragma solidity ^0.8;
"contract": "Transfer"
}
},
"value": 211
"value": 11
}
},
{
@@ -23,35 +23,12 @@ pragma solidity ^0.8;
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
}
},
{
"VerifyCall": {
"success": true
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
"data": "fb9e8d0500000000000000000000000003030303030303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000001"
}
}
]
@@ -59,17 +36,19 @@ pragma solidity ^0.8;
*/
contract Transfer {
constructor() payable {}
constructor() payable {
transfer_self(msg.value);
}
function address_self() internal view returns (address payable) {
return payable(address(this));
}
function transfer_self(uint _amount) public payable {
transfer_to(payable(address(this)), _amount);
transfer_to(address_self(), _amount);
}
function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount);
}
fallback() external {}
receive() external payable {}
}
-14
View File
@@ -156,20 +156,6 @@ case!("DivisionArithmetics.sol", DivisionArithmetics, sdivCall, division_arithme
case!("DivisionArithmetics.sol", DivisionArithmetics, modCall, division_arithmetics_mod, n: U256, d: U256);
case!("DivisionArithmetics.sol", DivisionArithmetics, smodCall, division_arithmetics_smod, n: I256, d: I256);
sol!(
contract Send {
function transfer_self(uint _amount) public payable;
}
);
case!("Send.sol", Send, transfer_selfCall, send_self, amount: U256);
sol!(
contract Transfer {
function transfer_self(uint _amount) public payable;
}
);
case!("Transfer.sol", Transfer, transfer_selfCall, transfer_self, amount: U256);
sol!(
contract MStore8 {
function mStore8(uint value) public pure returns (uint256 word);
+30 -48
View File
@@ -37,10 +37,10 @@ test_spec!(events, "Events", "Events.sol");
test_spec!(storage, "Storage", "Storage.sol");
test_spec!(mstore8, "MStore8", "MStore8.sol");
test_spec!(address, "Context", "Context.sol");
test_spec!(value, "Value", "Value.sol");
test_spec!(balance, "Value", "Value.sol");
test_spec!(create, "CreateB", "Create.sol");
test_spec!(call, "Caller", "Call.sol");
test_spec!(balance, "Balance", "Balance.sol");
test_spec!(transfer, "Transfer", "Transfer.sol");
test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol");
test_spec!(immutables, "Immutables", "Immutables.sol");
test_spec!(transaction, "Transaction", "Transaction.sol");
@@ -50,12 +50,6 @@ test_spec!(gas_price, "GasPrice", "GasPrice.sol");
test_spec!(gas_left, "GasLeft", "GasLeft.sol");
test_spec!(gas_limit, "GasLimit", "GasLimit.sol");
test_spec!(base_fee, "BaseFee", "BaseFee.sol");
test_spec!(coinbase, "Coinbase", "Coinbase.sol");
test_spec!(create2, "CreateB", "Create2.sol");
test_spec!(transfer, "Transfer", "Transfer.sol");
test_spec!(send, "Send", "Send.sol");
test_spec!(function_pointer, "FunctionPointer", "FunctionPointer.sol");
test_spec!(mload, "MLoad", "MLoad.sol");
fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate {
@@ -67,6 +61,7 @@ fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
path: Some(path.into()),
contract: contract.to_string(),
solc_optimizer: None,
pipeline: None,
},
data: vec![],
salt: OptionalHex::default(),
@@ -359,6 +354,7 @@ fn ext_code_size() {
path: Some("contracts/Baseline.sol".into()),
contract: "Baseline".to_string(),
solc_optimizer: None,
pipeline: None,
},
data: vec![],
salt: OptionalHex::from([0; 32]),
@@ -439,46 +435,32 @@ fn ext_code_size() {
.run();
}
#[test]
#[should_panic(expected = "ReentranceDenied")]
fn send_denies_reentrancy() {
let value = 1000;
Specs {
actions: vec![
instantiate("contracts/Send.sol", "Send").remove(0),
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::send_self(U256::from(value)).calldata,
},
],
differential: false,
..Default::default()
}
.run();
}
/*
// These test were implement for the mock-runtime and need to be ported yet.
#[test]
#[should_panic(expected = "ReentranceDenied")]
fn transfer_denies_reentrancy() {
let value = 1000;
Specs {
actions: vec![
instantiate("contracts/Transfer.sol", "Transfer").remove(0),
Call {
origin: TestAddress::Alice,
dest: TestAddress::Instantiated(0),
value,
gas_limit: None,
storage_deposit_limit: None,
data: Contract::transfer_self(U256::from(value)).calldata,
},
],
differential: false,
..Default::default()
}
.run();
fn 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);
}
*/
+11 -3
View File
@@ -40,9 +40,17 @@ pub const SHARED_BUILD_OPTS_NOT_MUSL: [&str; 4] = [
/// The shared build options to treat warnings as errors.
///
/// Disabled because it makes the build very brittle.
pub fn shared_build_opts_werror(_target_env: TargetEnv) -> Vec<String> {
vec!["-DLLVM_ENABLE_WERROR='Off'".to_string()]
/// Disabled on Windows due to the following upstream issue with MSYS2 with mingw-w64:
/// ProgramTest.cpp:23:15: error: '__p__environ' redeclared without 'dllimport' attribute
pub fn shared_build_opts_werror(target_env: TargetEnv) -> Vec<String> {
vec![format!(
"-DLLVM_ENABLE_WERROR='{}'",
if cfg!(target_os = "windows") || target_env == TargetEnv::Emscripten {
"Off"
} else {
"On"
},
)]
}
/// The build options to set the default target.
@@ -141,7 +141,7 @@ fn build_target(
Command::new("emcmake")
.env("EMCC_DEBUG", "2")
.env("CXXFLAGS", "-Dwait4=__syscall_wait4")
.env("LDFLAGS", "-lnodefs.js -s NO_INVOKE_RUN=1 -s EXIT_RUNTIME=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=FS,callMain,NODEFS -s MODULARIZE=1 -s WASM_BIGINT=1 -s ALLOW_TABLE_GROWTH=1 -s NODEJS_CATCH_EXIT=0 -sDYNAMIC_EXECUTION=0")
.env("LDFLAGS", "-lnodefs.js -s NO_INVOKE_RUN -s EXIT_RUNTIME -s INITIAL_MEMORY=64MB -s ALLOW_MEMORY_GROWTH -s EXPORTED_RUNTIME_METHODS=FS,callMain,NODEFS -s MODULARIZE -s EXPORT_ES6 -s WASM_BIGINT")
.arg("cmake")
.args([
"-S",
+1 -3
View File
@@ -83,7 +83,6 @@ fn clone_build_and_clean_musl() -> anyhow::Result<()> {
/// This test verifies that the LLVM repository can be successfully cloned and built in debug mode
/// with tests and coverage enabled.
#[test]
#[cfg(target_os = "linux")]
fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
@@ -108,7 +107,6 @@ fn debug_build_with_tests_coverage() -> anyhow::Result<()> {
/// This test verifies that the LLVM repository can be successfully built with address sanitizer.
#[test]
#[cfg(target_os = "linux")]
fn build_with_sanitizers() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
@@ -131,7 +129,7 @@ fn build_with_sanitizers() -> anyhow::Result<()> {
/// Tests the clone, build, and clean process of the LLVM repository for the emscripten target.
#[test]
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "macos"))]
fn clone_build_and_clean_emscripten() -> anyhow::Result<()> {
let test_dir = common::TestDir::with_lockfile(None)?;
let command = Command::cargo_bin(common::REVIVE_LLVM)?;
+1
View File
@@ -21,6 +21,7 @@ serde = { workspace = true, features = ["derive"] }
num = { workspace = true }
hex = { workspace = true }
sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true }
polkavm-disassembler = { workspace = true }
polkavm-common = { workspace = true }
@@ -1,11 +1,16 @@
//! The debug IR type.
/// The debug IR type.
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IRType {
/// Whether to dump the Yul code.
Yul,
/// Whether to dump the EVM legacy assembly code.
EVMLA,
/// Whether to dump the Ethereal IR code.
EthIR,
/// Whether to dump the LLVM IR code.
LLVM,
/// Whether to dump the assembly code.
@@ -22,6 +27,8 @@ impl IRType {
pub fn file_extension(&self) -> &'static str {
match self {
Self::Yul => revive_common::EXTENSION_YUL,
Self::EthIR => revive_common::EXTENSION_ETHIR,
Self::EVMLA => revive_common::EXTENSION_EVMLA,
Self::LLVM => revive_common::EXTENSION_LLVM_SOURCE,
Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY,
#[cfg(debug_assertions)]
@@ -39,6 +39,30 @@ impl DebugConfig {
Ok(())
}
/// Dumps the EVM legacy assembly IR.
pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(())
}
/// Dumps the Ethereal IR.
pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
if let Some(output_directory) = self.output_directory.as_ref() {
let mut file_path = output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
}
Ok(())
}
/// Dumps the unoptimized LLVM IR.
pub fn dump_llvm_ir_unoptimized(
&self,
+5
View File
@@ -17,7 +17,12 @@ pub use self::polkavm::context::attribute::Attribute as PolkaVMAttribute;
pub use self::polkavm::context::build::Build as PolkaVMBuild;
pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType;
pub use self::polkavm::context::debug_info::DebugInfo;
pub use self::polkavm::context::evmla_data::EVMLAData as PolkaVMContextEVMLAData;
pub use self::polkavm::context::function::block::evmla_data::key::Key as PolkaVMFunctionBlockKey;
pub use self::polkavm::context::function::block::evmla_data::EVMLAData as PolkaVMFunctionBlockEVMLAData;
pub use self::polkavm::context::function::block::Block as PolkaVMFunctionBlock;
pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFunctionDeclaration;
pub use self::polkavm::context::function::evmla_data::EVMLAData as PolkaVMFunctionEVMLAData;
pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction;
pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime;
pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn;
@@ -6,9 +6,15 @@ pub const LLVM_VERSION: semver::Version = semver::Version::new(18, 1, 4);
/// The pointer width sized type.
pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
/// The heap memory pointer pointer global variable name.
pub static GLOBAL_HEAP_MEMORY_POINTER: &str = "memory_pointer";
/// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The call flags global variable name.
pub static GLOBAL_CALL_FLAGS: &str = "call_flags";
/// The deployer call header size that consists of:
/// - bytecode hash (32 bytes)
pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD;
@@ -0,0 +1,27 @@
//! The LLVM IR generator EVM legacy assembly data.
use crate::polkavm::context::argument::Argument;
/// The LLVM IR generator EVM legacy assembly data.
/// Describes some data that is only relevant to the EVM legacy assembly.
#[derive(Debug, Clone)]
pub struct EVMLAData<'ctx> {
/// The Solidity compiler version.
/// Some instruction behave differenly depending on the version.
pub version: semver::Version,
/// The static stack allocated for the current function.
pub stack: Vec<Argument<'ctx>>,
}
impl EVMLAData<'_> {
/// The default stack size.
pub const DEFAULT_STACK_SIZE: usize = 64;
/// A shortcut constructor.
pub fn new(version: semver::Version) -> Self {
Self {
version,
stack: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
}
}
}
@@ -0,0 +1,34 @@
//! The LLVM IR generator function block key.
use crate::polkavm::context::code_type::CodeType;
/// The LLVM IR generator function block key.
/// Is only relevant to the EVM legacy assembly.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
/// The block code type.
pub code_type: CodeType,
/// The block tag.
pub tag: num::BigUint,
}
impl Key {
/// A shortcut constructor.
pub fn new(code_type: CodeType, tag: num::BigUint) -> Self {
Self { code_type, tag }
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}_{}",
match self.code_type {
CodeType::Deploy => "dt",
CodeType::Runtime => "rt",
},
self.tag
)
}
}
@@ -0,0 +1,18 @@
//! The LLVM function block EVM legacy assembly data.
pub mod key;
/// The LLVM function block EVM legacy assembly data.
/// Describes some data that is only relevant to the EVM legacy assembly.
#[derive(Debug, Clone)]
pub struct EVMLAData {
/// The initial hashes of the allowed stack states.
pub stack_hashes: Vec<md5::Digest>,
}
impl EVMLAData {
/// A shortcut constructor.
pub fn new(stack_hashes: Vec<md5::Digest>) -> Self {
Self { stack_hashes }
}
}
@@ -0,0 +1,52 @@
//! The LLVM IR generator function block.
pub mod evmla_data;
use self::evmla_data::EVMLAData;
/// The LLVM IR generator function block.
#[derive(Debug, Clone)]
pub struct Block<'ctx> {
/// The inner block.
inner: inkwell::basic_block::BasicBlock<'ctx>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData>,
}
impl<'ctx> Block<'ctx> {
/// A shortcut constructor.
pub fn new(inner: inkwell::basic_block::BasicBlock<'ctx>) -> Self {
Self {
inner,
evmla_data: None,
}
}
/// Sets the EVM legacy assembly data.
pub fn set_evmla_data(&mut self, data: EVMLAData) {
self.evmla_data = Some(data);
}
/// The LLVM object reference.
pub fn inner(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.inner
}
/// Returns the EVM data reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evm(&self) -> &EVMLAData {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
/// Returns the EVM data mutable reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evm_mut(&mut self) -> &mut EVMLAData {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
}
@@ -0,0 +1,74 @@
//! The LLVM function EVM legacy assembly data.
use std::collections::BTreeMap;
use crate::polkavm::context::function::block::evmla_data::key::Key as BlockKey;
use crate::polkavm::context::function::block::Block;
/// The LLVM function EVM legacy assembly data.
/// Describes some data that is only relevant to the EVM legacy assembly.
#[derive(Debug)]
pub struct EVMLAData<'ctx> {
/// The ordinary blocks with numeric tags.
/// Is only used by the Solidity EVM compiler.
pub blocks: BTreeMap<BlockKey, Vec<Block<'ctx>>>,
/// The function stack size.
pub stack_size: usize,
}
impl<'ctx> EVMLAData<'ctx> {
/// A shortcut constructor.
pub fn new(stack_size: usize) -> Self {
Self {
blocks: BTreeMap::new(),
stack_size,
}
}
/// Inserts a function block.
pub fn insert_block(&mut self, key: BlockKey, block: Block<'ctx>) {
if let Some(blocks) = self.blocks.get_mut(&key) {
blocks.push(block);
} else {
self.blocks.insert(key, vec![block]);
}
}
/// Returns the block with the specified tag and initial stack pattern.
/// If there is only one block, it is returned unconditionally.
pub fn find_block(
&self,
key: &BlockKey,
stack_hash: &md5::Digest,
) -> anyhow::Result<Block<'ctx>> {
if self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.len()
== 1
{
return self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.first()
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key));
}
self.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.iter()
.find(|block| {
block
.evm()
.stack_hashes
.iter()
.any(|hash| hash == stack_hash)
})
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))
}
}
@@ -1,6 +1,8 @@
//! The LLVM IR generator function.
pub mod block;
pub mod declaration;
pub mod evmla_data;
pub mod intrinsics;
pub mod llvm_runtime;
pub mod r#return;
@@ -17,6 +19,7 @@ use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::pointer::Pointer;
use self::declaration::Declaration;
use self::evmla_data::EVMLAData;
use self::r#return::Return;
use self::yul_data::YulData;
@@ -42,6 +45,8 @@ pub struct Function<'ctx> {
/// The Yul compiler data.
yul_data: Option<YulData>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData<'ctx>>,
}
impl<'ctx> Function<'ctx> {
@@ -67,6 +72,7 @@ impl<'ctx> Function<'ctx> {
return_block,
yul_data: None,
evmla_data: None,
}
}
@@ -212,6 +218,22 @@ impl<'ctx> Function<'ctx> {
}
}
/// Sets the exception handler attributes.
pub fn set_exception_handler_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false);
}
/// Sets the CXA-throw attributes.
pub fn set_cxa_throw_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(llvm, declaration, vec![Attribute::NoProfile], false);
}
/// Sets the pure function attributes.
pub fn set_pure_function_attributes(
llvm: &'ctx inkwell::context::Context,
@@ -278,6 +300,29 @@ impl<'ctx> Function<'ctx> {
self.return_block
}
/// Sets the EVM legacy assembly data.
pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) {
self.evmla_data = Some(data);
}
/// Returns the EVM legacy assembly data reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla(&self) -> &EVMLAData<'ctx> {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
/// Returns the EVM legacy assembly data mutable reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
/// Sets the Yul data.
pub fn set_yul_data(&mut self, data: YulData) {
self.yul_data = Some(data);
@@ -24,6 +24,22 @@ impl Entry {
where
D: Dependency + Clone,
{
context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER,
context.llvm().ptr_type(AddressSpace::Heap.into()),
AddressSpace::Stack,
context.xlen_type().get_undef(),
);
context.build_store(
context
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)?
.into(),
context.build_sbrk(
context.xlen_type().const_zero(),
context.xlen_type().const_zero(),
)?,
)?;
context.set_global(
crate::polkavm::GLOBAL_CALLDATA_SIZE,
context.xlen_type(),
@@ -31,6 +47,13 @@ impl Entry {
context.xlen_type().get_undef(),
);
context.set_global(
crate::polkavm::GLOBAL_CALL_FLAGS,
context.word_type(),
AddressSpace::Stack,
context.word_const(0),
);
Ok(())
}
@@ -47,11 +70,6 @@ impl Entry {
.build_runtime_call(revive_runtime_api::polkavm_imports::CALL_DATA_SIZE, &[])
.expect("the call_data_size syscall method should return a value")
.into_int_value();
let call_data_size_value = context.builder().build_int_truncate(
call_data_size_value,
context.xlen_type(),
"call_data_size_truncated",
)?;
context
.builder()
.build_store(call_data_size_pointer, call_data_size_value)?;
@@ -72,6 +90,13 @@ impl Entry {
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_CALL_FLAGS);
context.set_global(
crate::polkavm::GLOBAL_CALL_FLAGS,
is_deploy.get_type(),
AddressSpace::Stack,
is_deploy.into_int_value(),
);
let deploy_code_call_block = context.append_basic_block("deploy_code_call_block");
let runtime_code_call_block = context.append_basic_block("runtime_code_call_block");
+42 -21
View File
@@ -6,6 +6,7 @@ pub mod attribute;
pub mod build;
pub mod code_type;
pub mod debug_info;
pub mod evmla_data;
pub mod function;
pub mod global;
pub mod r#loop;
@@ -37,6 +38,7 @@ use self::attribute::Attribute;
use self::build::Build;
use self::code_type::CodeType;
use self::debug_info::DebugInfo;
use self::evmla_data::EVMLAData;
use self::function::declaration::Declaration as FunctionDeclaration;
use self::function::intrinsics::Intrinsics;
use self::function::llvm_runtime::LLVMRuntime;
@@ -93,6 +95,8 @@ where
solidity_data: Option<SolidityData>,
/// The Yul data.
yul_data: Option<YulData>,
/// The EVM legacy assembly data.
evmla_data: Option<EVMLAData<'ctx>>,
}
impl<'ctx, D> Context<'ctx, D>
@@ -253,6 +257,7 @@ where
solidity_data: None,
yul_data: None,
evmla_data: None,
}
}
@@ -1209,17 +1214,17 @@ where
/// Build a call to PolkaVM `msize` for querying the linear memory size.
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
let memory_size_pointer = self
.module()
.get_global(revive_runtime_api::polkavm_imports::MEMORY_SIZE)
.expect("the memory size symbol should have been declared")
.as_pointer_value();
let memory_size_value = self.builder().build_load(
self.xlen_type(),
memory_size_pointer,
"memory_size_value",
)?;
Ok(memory_size_value.into_int_value())
Ok(self
.builder()
.build_call(
self.runtime_api_method(revive_runtime_api::polkavm_imports::MEMORY_SIZE),
&[],
"call_msize",
)?
.try_as_basic_value()
.left()
.expect("sbrk returns an int")
.into_int_value())
}
/// Call PolkaVM `sbrk` for extending the heap by `offset` + `size`,
@@ -1265,9 +1270,8 @@ where
self.build_heap_alloc(offset, length)?;
let heap_start = self
.module()
.get_global(revive_runtime_api::polkavm_imports::MEMORY)
.expect("the memory symbol should have been declared")
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)?
.value
.as_pointer_value();
Ok(self.build_gep(
Pointer::new(self.byte_type(), AddressSpace::Stack, heap_start),
@@ -1335,17 +1339,11 @@ where
self.llvm.custom_width_int_type(bit_length as u32)
}
/// Returns the XLEN witdh sized type.
/// Returns the register witdh sized type.
pub fn xlen_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm.custom_width_int_type(crate::polkavm::XLEN as u32)
}
/// Returns the PolkaVM native register width sized type.
pub fn register_type(&self) -> inkwell::types::IntType<'ctx> {
self.llvm
.custom_width_int_type(revive_common::BIT_LENGTH_X64 as u32)
}
/// Returns the sentinel pointer value.
pub fn sentinel_pointer(&self) -> Pointer<'ctx> {
let sentinel_pointer = self
@@ -1570,6 +1568,29 @@ where
.expect("The Yul data must have been initialized")
}
/// Sets the EVM legacy assembly data.
pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) {
self.evmla_data = Some(data);
}
/// Returns the EVM legacy assembly data reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla(&self) -> &EVMLAData<'ctx> {
self.evmla_data
.as_ref()
.expect("The EVMLA data must have been initialized")
}
/// Returns the EVM legacy assembly data mutable reference.
/// # Panics
/// If the EVM data has not been initialized.
pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> {
self.evmla_data
.as_mut()
.expect("The EVMLA 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.
+74 -163
View File
@@ -8,7 +8,6 @@ use crate::polkavm::Dependency;
const STATIC_CALL_FLAG: u32 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
/// Translates a contract call.
#[allow(clippy::too_many_arguments)]
@@ -38,67 +37,60 @@ where
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
// TODO: What to supply here? Is there a weight to gas?
let _gas = context
.builder()
.build_int_truncate(gas, context.integer_type(64), "gas")?;
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.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?;
let (flags, deposit_limit_value) = if static_call {
let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
(
context.xlen_type().const_int(flags as u64, false),
context.word_type().const_zero(),
)
} else {
call_reentrancy_heuristic(context, gas, input_length, output_length)?
};
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, deposit_limit_value)?;
context.build_store(deposit_pointer, context.word_type().const_all_ones())?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
let flags = if static_call {
REENTRANT_CALL_FLAG | STATIC_CALL_FLAG
} else {
REENTRANT_CALL_FLAG
};
let flags = context.xlen_type().const_int(flags as u64, false);
let argument_type = revive_runtime_api::calling_convention::call(context.llvm());
let argument_pointer = context.build_alloca_at_entry(argument_type, "call_arguments");
let arguments = &[
flags.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
deposit_pointer.value.as_basic_value_enum(),
value_pointer.value.as_basic_value_enum(),
input_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_pointer.value.as_basic_value_enum(),
output_length_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(),
context.llvm(),
flags,
address_pointer.to_int(context),
"address_and_callee",
)?;
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
deposit_pointer.to_int(context),
value_pointer.to_int(context),
"deposit_and_value",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
input_pointer.to_int(context),
"input_data",
)?;
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
output_length_pointer.to_int(context),
output_pointer.to_int(context),
"output_data",
argument_pointer.value,
argument_type,
arguments,
)?;
let name = revive_runtime_api::polkavm_imports::CALL;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"call_argument_pointer",
)?;
let success = context
.build_runtime_call(
name,
&[
flags_and_callee.into(),
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_and_value.into(),
input_data.into(),
output_data.into(),
],
)
.build_runtime_call(name, &[argument_pointer.into()])
.unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value();
@@ -118,7 +110,7 @@ where
#[allow(clippy::too_many_arguments)]
pub fn delegate_call<'ctx, D>(
context: &mut Context<'ctx, D>,
_gas: inkwell::values::IntValue<'ctx>,
gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
@@ -136,6 +128,11 @@ where
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?;
// TODO: What to supply here? Is there a weight to gas?
let _gas = context
.builder()
.build_int_truncate(gas, context.integer_type(64), "gas")?;
let input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?;
@@ -147,41 +144,38 @@ where
let flags = context.xlen_type().const_int(0u64, false);
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
let argument_type = revive_runtime_api::calling_convention::delegate_call(context.llvm());
let argument_pointer = context.build_alloca_at_entry(argument_type, "delegate_call_arguments");
let arguments = &[
flags.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
deposit_pointer.value.as_basic_value_enum(),
input_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_pointer.value.as_basic_value_enum(),
output_length_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(),
context.llvm(),
flags,
address_pointer.to_int(context),
"address_and_callee",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
input_pointer.to_int(context),
"input_data",
)?;
let output_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
output_length_pointer.to_int(context),
output_pointer.to_int(context),
"output_data",
argument_pointer.value,
argument_type,
arguments,
)?;
let name = revive_runtime_api::polkavm_imports::DELEGATE_CALL;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"delegate_call_argument_pointer",
)?;
let success = context
.build_runtime_call(
name,
&[
flags_and_callee.into(),
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_pointer.to_int(context).into(),
input_data.into(),
output_data.into(),
],
)
.build_runtime_call(name, &[argument_pointer.into()])
.unwrap_or_else(|| panic!("{name} should return a value"))
.into_int_value();
@@ -215,86 +209,3 @@ where
.resolve_library(path.as_str())?
.as_basic_value_enum())
}
/// The Solidity `address.transfer` and `address.send` call detection heuristic.
///
/// # Why
/// This heuristic is an additional security feature to guard against re-entrancy attacks
/// in case contract authors violate Solidity best practices and use `address.transfer` or
/// `address.send`.
/// While contract authors are supposed to never use `address.transfer` or `address.send`,
/// for a small cost we can be extra defensive about it.
///
/// # How
/// The gas stipend emitted by solc for `transfer` and `send` is not static, thus:
/// - Dynamically allow re-entrancy only for calls considered not transfer or send.
/// - Detected balance transfers will supply 0 deposit limit instead of `u256::MAX`.
///
/// Calls are considered transfer or send if:
/// - (Input length | Output lenght) == 0;
/// - Gas <= 2300;
///
/// # Returns
/// The call flags xlen `IntValue` and the deposit limit word `IntValue`.
fn call_reentrancy_heuristic<'ctx, D>(
context: &mut Context<'ctx, D>,
gas: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<(
inkwell::values::IntValue<'ctx>,
inkwell::values::IntValue<'ctx>,
)>
where
D: Dependency + Clone,
{
// Branch-free SSA implementation: First derive the heuristic boolean (int1) value.
let input_length_or_output_length =
context
.builder()
.build_or(input_length, output_length, "input_length_or_output_length")?;
let is_no_input_no_output = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
context.xlen_type().const_zero(),
input_length_or_output_length,
"is_no_input_no_output",
)?;
let gas_stipend = context
.word_type()
.const_int(SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD, false);
let is_gas_stipend_for_transfer_or_send = context.builder().build_int_compare(
inkwell::IntPredicate::ULE,
gas,
gas_stipend,
"is_gas_stipend_for_transfer_or_send",
)?;
let is_balance_transfer = context.builder().build_and(
is_no_input_no_output,
is_gas_stipend_for_transfer_or_send,
"is_balance_transfer",
)?;
let is_regular_call = context
.builder()
.build_not(is_balance_transfer, "is_balance_transfer_inverted")?;
// Call flag: Left shift the heuristic boolean value.
let is_regular_call_xlen = context.builder().build_int_z_extend(
is_regular_call,
context.xlen_type(),
"is_balance_transfer_xlen",
)?;
let call_flags = context.builder().build_left_shift(
is_regular_call_xlen,
context.xlen_type().const_int(3, false),
"flags",
)?;
// Deposit limit value: Sign-extended the heuristic boolean value.
let deposit_limit_value = context.builder().build_int_s_extend(
is_regular_call,
context.word_type(),
"deposit_limit_value",
)?;
Ok((call_flags, deposit_limit_value))
}
+2 -10
View File
@@ -122,20 +122,12 @@ where
/// Translates the `coinbase` instruction.
pub fn coinbase<'ctx, D>(
context: &mut Context<'ctx, D>,
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let pointer = context.build_alloca_at_entry(
context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS),
"coinbase_output",
);
context.build_runtime_call(
revive_runtime_api::polkavm_imports::BLOCK_AUTHOR,
&[pointer.to_int(context).into()],
);
context.build_load_address(pointer)
todo!()
}
/// Translates the `basefee` instruction.
+38 -27
View File
@@ -26,6 +26,15 @@ where
let code_hash_pointer = context.build_heap_gep(input_offset, input_length)?;
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(),
"input_ptr_parameter_offset",
);
let value_pointer = context.build_alloca_at_entry(context.value_type(), "transferred_value");
context.build_store(value_pointer, value)?;
@@ -47,38 +56,40 @@ where
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, context.word_type().const_all_ones())?;
let deposit_and_value = revive_runtime_api::calling_convention::pack_hi_lo_reg(
let argument_type = revive_runtime_api::calling_convention::instantiate(context.llvm());
let argument_pointer = context.build_alloca_at_entry(argument_type, "instantiate_arguments");
let arguments = &[
code_hash_pointer.value.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
context
.integer_const(revive_common::BIT_LENGTH_X64, u64::MAX)
.as_basic_value_enum(),
deposit_pointer.value.as_basic_value_enum(),
value_pointer.value.as_basic_value_enum(),
input_data_pointer.value.as_basic_value_enum(),
input_length.as_basic_value_enum(),
address_pointer.value.as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
context.sentinel_pointer().value.as_basic_value_enum(),
salt_pointer.value.as_basic_value_enum(),
];
revive_runtime_api::calling_convention::spill(
context.builder(),
context.llvm(),
deposit_pointer.to_int(context),
value_pointer.to_int(context),
"deposit_and_value",
)?;
let input_data = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
input_length,
code_hash_pointer.to_int(context),
"input_data",
)?;
let address_and_salt = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(),
context.llvm(),
address_pointer.to_int(context),
salt_pointer.to_int(context),
"output_data",
argument_pointer.value,
argument_type,
arguments,
)?;
let argument_pointer = context.builder().build_ptr_to_int(
argument_pointer.value,
context.xlen_type(),
"instantiate_argument_pointer",
)?;
context.build_runtime_call(
revive_runtime_api::polkavm_imports::INSTANTIATE,
&[
context.register_type().const_all_ones().into(),
context.register_type().const_all_ones().into(),
deposit_and_value.into(),
input_data.into(),
context.register_type().const_all_ones().into(),
address_and_salt.into(),
],
&[argument_pointer.into()],
);
let address = context.build_byte_swap(context.build_load(address_pointer, "address")?)?;
+1
View File
@@ -59,6 +59,7 @@ pub fn build_assembly_text(
}
/// Implemented by items which are translated into LLVM IR.
#[allow(clippy::upper_case_acronyms)]
pub trait WriteLLVM<D>
where
D: Dependency + Clone,
+3 -1
View File
@@ -85,7 +85,6 @@ impl ExtBuilder {
.unwrap();
pallet_balances::GenesisConfig::<Runtime> {
balances: self.balance_genesis_config,
dev_accounts: None,
}
.assimilate_storage(&mut t)
.unwrap();
@@ -239,6 +238,7 @@ pub enum Code {
Solidity {
path: Option<std::path::PathBuf>,
solc_optimizer: Option<bool>,
pipeline: Option<revive_solidity::SolcPipeline>,
contract: String,
},
/// Read the contract blob from disk
@@ -263,6 +263,7 @@ impl From<Code> for pallet_revive::Code {
path,
contract,
solc_optimizer,
pipeline,
} => {
let Some(path) = path else {
panic!("Solidity source of contract '{contract}' missing path");
@@ -274,6 +275,7 @@ impl From<Code> for pallet_revive::Code {
&contract,
&source_code,
solc_optimizer.unwrap_or(true),
pipeline.unwrap_or(revive_solidity::SolcPipeline::Yul),
))
}
Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()),
+6 -13
View File
@@ -1,4 +1,5 @@
use frame_support::{runtime, traits::FindAuthor, weights::constants::WEIGHT_REF_TIME_PER_SECOND};
use frame_support::{runtime, weights::constants::WEIGHT_REF_TIME_PER_SECOND};
use pallet_revive::AccountId32Mapper;
use polkadot_sdk::*;
use polkadot_sdk::{
@@ -10,6 +11,8 @@ pub type Balance = u128;
pub type AccountId = pallet_revive::AccountId32Mapper<Runtime>;
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 {
@@ -23,8 +26,7 @@ mod runtime {
RuntimeHoldReason,
RuntimeSlashReason,
RuntimeLockId,
RuntimeTask,
RuntimeViewFunction
RuntimeTask
)]
pub struct Runtime;
@@ -85,15 +87,6 @@ impl pallet_revive::Config for Runtime {
type UploadOrigin = EnsureSigned<AccountId32>;
type InstantiateOrigin = EnsureSigned<AccountId32>;
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type Debug = ();
type ChainId = ConstU64<420_420_420>;
type FindAuthor = Self;
}
impl FindAuthor<<Runtime as frame_system::Config>::AccountId> for Runtime {
fn find_author<'a, I>(_digests: I) -> Option<<Runtime as frame_system::Config>::AccountId>
where
I: 'a + IntoIterator<Item = (frame_support::ConsensusEngineId, &'a [u8])>,
{
Some([0xff; 32].into())
}
}
+6
View File
@@ -282,11 +282,17 @@ impl Specs {
let Code::Solidity {
path: Some(path),
solc_optimizer,
pipeline,
contract,
} = code
else {
panic!("the differential runner requires Code::Solidity source");
};
assert_ne!(
pipeline,
Some(revive_solidity::SolcPipeline::EVMLA),
"yul pipeline must be enabled in differential mode"
);
assert!(
salt.0.is_none(),
"salt is not supported in differential mode"
+116 -18
View File
@@ -1,4 +1,10 @@
use inkwell::{builder::Builder, context::Context, module::Module, values::IntValue};
use inkwell::{
builder::Builder,
context::Context,
module::Module,
types::{BasicType, StructType},
values::{BasicValueEnum, PointerValue},
};
/// Creates a module that sets the PolkaVM minimum stack size to [`size`] if linked in.
pub fn min_stack_size<'context>(
@@ -15,23 +21,115 @@ pub fn min_stack_size<'context>(
module
}
/// Helper for packing two 32 bit integer values into a 64 bit integer value.
pub fn pack_hi_lo_reg<'ctx>(
/// Helper for building function calls with stack spilled arguments.
/// - `pointer`: points to a struct of the packed argument struct type
/// - `type`: the packed argument struct type
/// - `arguments`: a correctly ordered list of the struct field values
pub fn spill<'ctx>(
builder: &Builder<'ctx>,
context: &'ctx Context,
hi: IntValue<'ctx>,
lo: IntValue<'ctx>,
name: &str,
) -> anyhow::Result<IntValue<'ctx>> {
assert_eq!(hi.get_type(), context.i32_type());
assert_eq!(lo.get_type(), context.i32_type());
pointer: PointerValue<'ctx>,
r#type: StructType<'ctx>,
arguments: &[BasicValueEnum<'ctx>],
) -> anyhow::Result<()> {
for index in 0..r#type.get_field_types().len() {
let field_pointer = builder.build_struct_gep(
r#type,
pointer,
index as u32,
&format!("spill_parameter_{}", index),
)?;
let field_value = arguments
.get(index)
.ok_or_else(|| anyhow::anyhow!("invalid index {index} for struct type {}", r#type))?;
builder.build_store(field_pointer, *field_value)?;
}
let lo_part = builder.build_int_z_extend(lo, context.i64_type(), &format!("{name}_lo_part"))?;
let hi_part = builder.build_int_z_extend(hi, context.i64_type(), &format!("{name}_hi_part"))?;
let hi_part_shifted = builder.build_left_shift(
hi_part,
context.i64_type().const_int(32, false),
&format!("{name}_hi_part_shifted"),
)?;
Ok(builder.build_or(hi_part_shifted, lo_part, name)?)
Ok(())
}
/// Returns a packed struct argument type for the `instantiate` API.
pub fn instantiate(context: &Context) -> StructType {
context.struct_type(
&[
// code_hash_ptr: u32,
context.i32_type().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.i32_type().as_basic_type_enum(),
// value_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_len_ptr: u32,
context.i32_type().as_basic_type_enum(),
// salt_ptr: u32,
context.i32_type().as_basic_type_enum(),
],
false,
)
}
/// Returns a packed struct argument type for the `call` API.
pub fn call(context: &Context) -> StructType {
context.struct_type(
&[
// flags: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr:
context.i32_type().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.i32_type().as_basic_type_enum(),
// value_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_len_ptr: u32,
context.i32_type().as_basic_type_enum(),
],
false,
)
}
/// Returns a packed struct argument type for the `delegate_call` API.
pub fn delegate_call(context: &Context) -> StructType {
context.struct_type(
&[
// flags: u32,
context.i32_type().as_basic_type_enum(),
// address_ptr:
context.i32_type().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.i32_type().as_basic_type_enum(),
// input_data_ptr: u32,
context.i32_type().as_basic_type_enum(),
// input_data_len: u32,
context.i32_type().as_basic_type_enum(),
// output_ptr: u32,
context.i32_type().as_basic_type_enum(),
// output_len_ptr: u32,
context.i32_type().as_basic_type_enum(),
],
false,
)
}
+10 -8
View File
@@ -8,10 +8,10 @@
#define EVM_WORD_SIZE 32
#define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1))
#define MAX_MEMORY_SIZE (64 * 1024)
char __memory[MAX_MEMORY_SIZE];
uint32_t __memory_size = 0;
static char __memory[MAX_MEMORY_SIZE];
static uint32_t __memory_size = 0;
void * __sbrk_internal(uint32_t offset, uint32_t size) {
void * __sbrk_internal(uint32_t offset, uint32_t size) {
if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) {
return NULL;
}
@@ -27,6 +27,10 @@ void * __sbrk_internal(uint32_t offset, uint32_t size) {
return (void *)&__memory[__memory_size];
}
uint32_t __msize() {
return __memory_size;
}
void * memset(void *b, int c, size_t len) {
uint8_t *dest = b;
while (len-- > 0) *dest++ = c;
@@ -68,13 +72,11 @@ POLKAVM_IMPORT(void, balance_of, uint32_t, uint32_t)
POLKAVM_IMPORT(void, base_fee, uint32_t)
POLKAVM_IMPORT(void, block_author, uint32_t)
POLKAVM_IMPORT(void, block_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(void, block_number, uint32_t)
POLKAVM_IMPORT(uint64_t, call, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t)
POLKAVM_IMPORT(uint64_t, call, uint32_t)
POLKAVM_IMPORT(uint64_t, call_data_copy, uint32_t, uint32_t, uint32_t)
@@ -90,7 +92,7 @@ POLKAVM_IMPORT(uint64_t, code_size, uint32_t)
POLKAVM_IMPORT(void, code_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(uint64_t, delegate_call, uint64_t, uint64_t, uint64_t, uint32_t, uint64_t, uint64_t)
POLKAVM_IMPORT(uint64_t, delegate_call, uint32_t)
POLKAVM_IMPORT(void, deposit_event, uint32_t, uint32_t, uint32_t, uint32_t)
@@ -104,7 +106,7 @@ POLKAVM_IMPORT(uint64_t, get_storage, uint32_t, uint32_t, uint32_t, uint32_t, ui
POLKAVM_IMPORT(void, hash_keccak_256, uint32_t, uint32_t, uint32_t)
POLKAVM_IMPORT(uint64_t, instantiate, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t)
POLKAVM_IMPORT(uint64_t, instantiate, uint32_t)
POLKAVM_IMPORT(void, now, uint32_t)
+11 -9
View File
@@ -1,15 +1,19 @@
//! This crate vendors the [PolkaVM][0] C API and provides a LLVM module for interacting
//! with the `pallet-revive` runtime API.
//! At present, the revive 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_imports.rs"));
/// The emulated EVM heap memory global symbol.
pub static MEMORY: &str = "__memory";
/// The emulated EVM heap memory size global symbol.
pub static MEMORY_SIZE: &str = "__memory_size";
pub static SBRK: &str = "__sbrk_internal";
pub static MEMORY_SIZE: &str = "__msize";
pub static ADDRESS: &str = "address";
pub static BALANCE: &str = "balance";
@@ -18,8 +22,6 @@ pub static BALANCE_OF: &str = "balance_of";
pub static BASE_FEE: &str = "base_fee";
pub static BLOCK_AUTHOR: &str = "block_author";
pub static BLOCK_HASH: &str = "block_hash";
pub static BLOCK_NUMBER: &str = "block_number";
@@ -80,11 +82,11 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee";
/// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 34] = [
SBRK,
MEMORY_SIZE,
ADDRESS,
BALANCE,
BALANCE_OF,
BASE_FEE,
BLOCK_AUTHOR,
BLOCK_HASH,
BLOCK_NUMBER,
CALL,
+1
View File
@@ -33,6 +33,7 @@ regex = { workspace = true }
hex = { workspace = true }
num = { workspace = true }
sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true }
revive-common = { workspace = true }
+2 -2
View File
@@ -64,7 +64,7 @@ impl Contract {
file_path.push(file_name);
if file_path.exists() && !overwrite {
anyhow::bail!(
eprintln!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
);
} else {
@@ -87,7 +87,7 @@ impl Contract {
file_path.push(file_name);
if file_path.exists() && !overwrite {
anyhow::bail!(
eprintln!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
);
} else {
+2
View File
@@ -1,5 +1,7 @@
//! Solidity to PolkaVM compiler constants.
#![allow(dead_code)]
/// The default executable name.
pub static DEFAULT_EXECUTABLE_NAME: &str = "resolc";
@@ -0,0 +1,67 @@
//! The inner JSON legacy assembly code element.
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::evmla::assembly::Assembly;
/// The inner JSON legacy assembly code element.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum Data {
/// The assembly code wrapper.
Assembly(Assembly),
/// The hash.
Hash(String),
/// The full contract path after the factory dependencies replacing pass.
Path(String),
}
impl Data {
/// Returns the inner assembly reference if it is present.
pub fn get_assembly(&self) -> Option<&Assembly> {
match self {
Self::Assembly(ref assembly) => Some(assembly),
Self::Hash(_) => None,
Self::Path(_) => None,
}
}
/// Returns the inner assembly mutable reference if it is present.
pub fn get_assembly_mut(&mut self) -> Option<&mut Assembly> {
match self {
Self::Assembly(ref mut assembly) => Some(assembly),
Self::Hash(_) => None,
Self::Path(_) => None,
}
}
/// Get the list of missing deployable libraries.
pub fn get_missing_libraries(&self) -> HashSet<String> {
match self {
Self::Assembly(assembly) => assembly.get_missing_libraries(),
Self::Hash(_) => HashSet::new(),
Self::Path(_) => HashSet::new(),
}
}
/// Gets the contract `keccak256` hash.
pub fn keccak256(&self) -> String {
match self {
Self::Assembly(assembly) => assembly.keccak256(),
Self::Hash(hash) => panic!("Expected assembly, found hash `{hash}`"),
Self::Path(path) => panic!("Expected assembly, found path `{path}`"),
}
}
}
impl std::fmt::Display for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Assembly(inner) => writeln!(f, "{inner}"),
Self::Hash(inner) => writeln!(f, "Hash `{inner}`"),
Self::Path(inner) => writeln!(f, "Path `{inner}`"),
}
}
}
@@ -0,0 +1,79 @@
//! Translates the CODECOPY use cases.
/// Translates the contract hash copying.
pub fn contract_hash<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let offset = context.builder().build_int_add(
offset,
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, value)?;
Ok(())
}
/// Translates the library marker copying.
pub fn library_marker<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
offset: u64,
value: u64,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
revive_llvm_context::polkavm_evm_memory::store_byte(
context,
context.word_const(offset),
context.word_const(value),
)?;
Ok(())
}
/// Translates the static data copying.
pub fn static_data<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
destination: inkwell::values::IntValue<'ctx>,
source: &str,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let mut offset = 0;
for (index, chunk) in source
.chars()
.collect::<Vec<char>>()
.chunks(revive_common::BYTE_LENGTH_WORD * 2)
.enumerate()
{
let mut value_string = chunk.iter().collect::<String>();
value_string.push_str(
"0".repeat((revive_common::BYTE_LENGTH_WORD * 2) - chunk.len())
.as_str(),
);
let datacopy_destination = context.builder().build_int_add(
destination,
context.word_const(offset as u64),
format!("datacopy_destination_index_{index}").as_str(),
)?;
let datacopy_value = context.word_const_str_hex(value_string.as_str());
revive_llvm_context::polkavm_evm_memory::store(
context,
datacopy_destination,
datacopy_value,
)?;
offset += chunk.len() / 2;
}
Ok(())
}
@@ -0,0 +1,69 @@
//! Translates the jump operations.
/// Translates the unconditional jump.
pub fn unconditional<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
destination: num::BigUint,
stack_hash: md5::Digest,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let block_key = revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, destination);
let block = context
.current_function()
.borrow()
.evmla()
.find_block(&block_key, &stack_hash)?;
context.build_unconditional_branch(block.inner());
Ok(())
}
/// Translates the conditional jump.
pub fn conditional<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
destination: num::BigUint,
stack_hash: md5::Digest,
stack_height: usize,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let block_key = revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, destination);
let condition_pointer = context.evmla().stack[stack_height]
.to_llvm()
.into_pointer_value();
let condition = context.build_load(
revive_llvm_context::PolkaVMPointer::new_stack_field(context, condition_pointer),
format!("conditional_{block_key}_condition").as_str(),
)?;
let condition = context.builder().build_int_compare(
inkwell::IntPredicate::NE,
condition.into_int_value(),
context.word_const(0),
format!("conditional_{block_key}_condition_compared").as_str(),
)?;
let then_block = context
.current_function()
.borrow()
.evmla()
.find_block(&block_key, &stack_hash)?;
let join_block =
context.append_basic_block(format!("conditional_{block_key}_join_block").as_str());
context.build_conditional_branch(condition, then_block.inner(), join_block)?;
context.set_basic_block(join_block);
Ok(())
}
@@ -0,0 +1,382 @@
//! The EVM instruction.
pub mod codecopy;
pub mod jump;
pub mod name;
pub mod stack;
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
use self::name::Name;
/// The EVM instruction.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Instruction {
/// The opcode or tag identifier.
pub name: Name,
/// The optional value argument.
pub value: Option<String>,
/// The source code identifier.
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<isize>,
/// The source code location begin.
pub begin: isize,
/// The source code location end.
pub end: isize,
}
impl Instruction {
/// Returns the number of input stack arguments.
pub const fn input_size(&self, version: &semver::Version) -> usize {
match self.name {
Name::POP => 1,
Name::JUMP => 1,
Name::JUMPI => 2,
Name::ADD => 2,
Name::SUB => 2,
Name::MUL => 2,
Name::DIV => 2,
Name::MOD => 2,
Name::SDIV => 2,
Name::SMOD => 2,
Name::LT => 2,
Name::GT => 2,
Name::EQ => 2,
Name::ISZERO => 1,
Name::SLT => 2,
Name::SGT => 2,
Name::OR => 2,
Name::XOR => 2,
Name::NOT => 1,
Name::AND => 2,
Name::SHL => 2,
Name::SHR => 2,
Name::SAR => 2,
Name::BYTE => 2,
Name::ADDMOD => 3,
Name::MULMOD => 3,
Name::EXP => 2,
Name::SIGNEXTEND => 2,
Name::SHA3 => 2,
Name::KECCAK256 => 2,
Name::MLOAD => 1,
Name::MSTORE => 2,
Name::MSTORE8 => 2,
Name::MCOPY => 3,
Name::SLOAD => 1,
Name::SSTORE => 2,
Name::TLOAD => 1,
Name::TSTORE => 2,
Name::PUSHIMMUTABLE => 0,
Name::ASSIGNIMMUTABLE => {
if version.minor >= 8 {
2
} else {
1
}
}
Name::CALLDATALOAD => 1,
Name::CALLDATACOPY => 3,
Name::CODECOPY => 3,
Name::RETURNDATACOPY => 3,
Name::EXTCODESIZE => 1,
Name::EXTCODEHASH => 1,
Name::CALL => 7,
Name::CALLCODE => 7,
Name::STATICCALL => 6,
Name::DELEGATECALL => 6,
Name::RETURN => 2,
Name::REVERT => 2,
Name::SELFDESTRUCT => 1,
Name::LOG0 => 2,
Name::LOG1 => 3,
Name::LOG2 => 4,
Name::LOG3 => 5,
Name::LOG4 => 6,
Name::CREATE => 3,
Name::CREATE2 => 4,
Name::ZK_CREATE => 3,
Name::ZK_CREATE2 => 4,
Name::BALANCE => 1,
Name::BLOCKHASH => 1,
Name::BLOBHASH => 1,
Name::EXTCODECOPY => 4,
Name::RecursiveCall { input_size, .. } => input_size,
Name::RecursiveReturn { input_size } => input_size,
_ => 0,
}
}
/// Returns the number of output stack arguments.
pub const fn output_size(&self) -> usize {
match self.name {
Name::PUSH => 1,
Name::PUSH_Data => 1,
Name::PUSH_Tag => 1,
Name::PUSH_ContractHash => 1,
Name::PUSH_ContractHashSize => 1,
Name::PUSHLIB => 1,
Name::PUSHDEPLOYADDRESS => 1,
Name::PUSH1 => 1,
Name::PUSH2 => 1,
Name::PUSH3 => 1,
Name::PUSH4 => 1,
Name::PUSH5 => 1,
Name::PUSH6 => 1,
Name::PUSH7 => 1,
Name::PUSH8 => 1,
Name::PUSH9 => 1,
Name::PUSH10 => 1,
Name::PUSH11 => 1,
Name::PUSH12 => 1,
Name::PUSH13 => 1,
Name::PUSH14 => 1,
Name::PUSH15 => 1,
Name::PUSH16 => 1,
Name::PUSH17 => 1,
Name::PUSH18 => 1,
Name::PUSH19 => 1,
Name::PUSH20 => 1,
Name::PUSH21 => 1,
Name::PUSH22 => 1,
Name::PUSH23 => 1,
Name::PUSH24 => 1,
Name::PUSH25 => 1,
Name::PUSH26 => 1,
Name::PUSH27 => 1,
Name::PUSH28 => 1,
Name::PUSH29 => 1,
Name::PUSH30 => 1,
Name::PUSH31 => 1,
Name::PUSH32 => 1,
Name::DUP1 => 1,
Name::DUP2 => 1,
Name::DUP3 => 1,
Name::DUP4 => 1,
Name::DUP5 => 1,
Name::DUP6 => 1,
Name::DUP7 => 1,
Name::DUP8 => 1,
Name::DUP9 => 1,
Name::DUP10 => 1,
Name::DUP11 => 1,
Name::DUP12 => 1,
Name::DUP13 => 1,
Name::DUP14 => 1,
Name::DUP15 => 1,
Name::DUP16 => 1,
Name::ADD => 1,
Name::SUB => 1,
Name::MUL => 1,
Name::DIV => 1,
Name::MOD => 1,
Name::SDIV => 1,
Name::SMOD => 1,
Name::LT => 1,
Name::GT => 1,
Name::EQ => 1,
Name::ISZERO => 1,
Name::SLT => 1,
Name::SGT => 1,
Name::OR => 1,
Name::XOR => 1,
Name::NOT => 1,
Name::AND => 1,
Name::SHL => 1,
Name::SHR => 1,
Name::SAR => 1,
Name::BYTE => 1,
Name::ADDMOD => 1,
Name::MULMOD => 1,
Name::EXP => 1,
Name::SIGNEXTEND => 1,
Name::SHA3 => 1,
Name::KECCAK256 => 1,
Name::MLOAD => 1,
Name::SLOAD => 1,
Name::TLOAD => 1,
Name::PUSHIMMUTABLE => 1,
Name::CALLDATALOAD => 1,
Name::CALLDATASIZE => 1,
Name::CODESIZE => 1,
Name::PUSHSIZE => 1,
Name::RETURNDATASIZE => 1,
Name::EXTCODESIZE => 1,
Name::EXTCODEHASH => 1,
Name::CALL => 1,
Name::CALLCODE => 1,
Name::STATICCALL => 1,
Name::DELEGATECALL => 1,
Name::CREATE => 1,
Name::CREATE2 => 1,
Name::ZK_CREATE => 1,
Name::ZK_CREATE2 => 1,
Name::ADDRESS => 1,
Name::CALLER => 1,
Name::TIMESTAMP => 1,
Name::NUMBER => 1,
Name::CALLVALUE => 1,
Name::GAS => 1,
Name::BALANCE => 1,
Name::SELFBALANCE => 1,
Name::GASLIMIT => 1,
Name::GASPRICE => 1,
Name::ORIGIN => 1,
Name::CHAINID => 1,
Name::BLOCKHASH => 1,
Name::BLOBHASH => 1,
Name::DIFFICULTY => 1,
Name::PREVRANDAO => 1,
Name::COINBASE => 1,
Name::MSIZE => 1,
Name::BASEFEE => 1,
Name::BLOBBASEFEE => 1,
Name::PC => 1,
Name::RecursiveCall { output_size, .. } => output_size,
_ => 0,
}
}
/// Replaces the instruction data aliases with the actual data.
pub fn replace_data_aliases(
instructions: &mut [Self],
mapping: &BTreeMap<String, String>,
) -> anyhow::Result<()> {
for instruction in instructions.iter_mut() {
match instruction {
Instruction {
name: Name::PUSH_ContractHash | Name::PUSH_ContractHashSize,
value: Some(value),
..
} => {
*value = mapping.get(value.as_str()).cloned().ok_or_else(|| {
anyhow::anyhow!("Contract alias `{}` data not found", value)
})?;
}
Instruction {
name: Name::PUSH_Data,
value: Some(value),
..
} => {
let mut key_extended =
"0".repeat(revive_common::BYTE_LENGTH_WORD * 2 - value.len());
key_extended.push_str(value.as_str());
*value = mapping.get(key_extended.as_str()).cloned().ok_or_else(|| {
anyhow::anyhow!("Data chunk alias `{}` data not found", key_extended)
})?;
}
_ => {}
}
}
Ok(())
}
/// Initializes an `INVALID` instruction to terminate an invalid unreachable block part.
pub fn invalid(previous: &Self) -> Self {
Self {
name: Name::INVALID,
value: None,
source: previous.source,
begin: previous.begin,
end: previous.end,
}
}
/// Initializes a recursive function `Call` instruction.
pub fn recursive_call(
name: String,
entry_key: revive_llvm_context::PolkaVMFunctionBlockKey,
stack_hash: md5::Digest,
input_size: usize,
output_size: usize,
return_address: revive_llvm_context::PolkaVMFunctionBlockKey,
previous: &Self,
) -> Self {
Self {
name: Name::RecursiveCall {
name,
entry_key,
stack_hash,
input_size,
output_size,
return_address,
},
value: None,
source: previous.source,
begin: previous.begin,
end: previous.end,
}
}
/// Initializes a recursive function `Return` instruction.
pub fn recursive_return(input_size: usize, previous: &Self) -> Self {
Self {
name: Name::RecursiveReturn { input_size },
value: None,
source: previous.source,
begin: previous.begin,
end: previous.end,
}
}
}
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = self.name.to_string();
match self.name {
Name::Tag => write!(f, "{:4}", name),
_ => write!(f, "{:15}", name),
}?;
match self.value {
Some(ref value) if value.len() <= 64 => write!(f, "{}", value)?,
Some(ref value) => write!(f, "... {}", &value[value.len() - 60..])?,
None => {}
}
Ok(())
}
}
@@ -0,0 +1,417 @@
//! The EVM instruction name.
use serde::Deserialize;
use serde::Serialize;
/// The EVM instruction name.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum Name {
/// The eponymous EVM instruction.
PUSH,
/// Pushes a constant tag index.
#[serde(rename = "PUSH [tag]")]
PUSH_Tag,
/// Pushes an unknown `data` value.
#[serde(rename = "PUSH data")]
PUSH_Data,
/// Pushes a contract hash size.
#[serde(rename = "PUSH #[$]")]
PUSH_ContractHashSize,
/// Pushes a contract hash.
#[serde(rename = "PUSH [$]")]
PUSH_ContractHash,
/// The eponymous EVM instruction.
PUSH1,
/// The eponymous EVM instruction.
PUSH2,
/// The eponymous EVM instruction.
PUSH3,
/// The eponymous EVM instruction.
PUSH4,
/// The eponymous EVM instruction.
PUSH5,
/// The eponymous EVM instruction.
PUSH6,
/// The eponymous EVM instruction.
PUSH7,
/// The eponymous EVM instruction.
PUSH8,
/// The eponymous EVM instruction.
PUSH9,
/// The eponymous EVM instruction.
PUSH10,
/// The eponymous EVM instruction.
PUSH11,
/// The eponymous EVM instruction.
PUSH12,
/// The eponymous EVM instruction.
PUSH13,
/// The eponymous EVM instruction.
PUSH14,
/// The eponymous EVM instruction.
PUSH15,
/// The eponymous EVM instruction.
PUSH16,
/// The eponymous EVM instruction.
PUSH17,
/// The eponymous EVM instruction.
PUSH18,
/// The eponymous EVM instruction.
PUSH19,
/// The eponymous EVM instruction.
PUSH20,
/// The eponymous EVM instruction.
PUSH21,
/// The eponymous EVM instruction.
PUSH22,
/// The eponymous EVM instruction.
PUSH23,
/// The eponymous EVM instruction.
PUSH24,
/// The eponymous EVM instruction.
PUSH25,
/// The eponymous EVM instruction.
PUSH26,
/// The eponymous EVM instruction.
PUSH27,
/// The eponymous EVM instruction.
PUSH28,
/// The eponymous EVM instruction.
PUSH29,
/// The eponymous EVM instruction.
PUSH30,
/// The eponymous EVM instruction.
PUSH31,
/// The eponymous EVM instruction.
PUSH32,
/// The eponymous EVM instruction.
DUP1,
/// The eponymous EVM instruction.
DUP2,
/// The eponymous EVM instruction.
DUP3,
/// The eponymous EVM instruction.
DUP4,
/// The eponymous EVM instruction.
DUP5,
/// The eponymous EVM instruction.
DUP6,
/// The eponymous EVM instruction.
DUP7,
/// The eponymous EVM instruction.
DUP8,
/// The eponymous EVM instruction.
DUP9,
/// The eponymous EVM instruction.
DUP10,
/// The eponymous EVM instruction.
DUP11,
/// The eponymous EVM instruction.
DUP12,
/// The eponymous EVM instruction.
DUP13,
/// The eponymous EVM instruction.
DUP14,
/// The eponymous EVM instruction.
DUP15,
/// The eponymous EVM instruction.
DUP16,
/// The eponymous EVM instruction.
SWAP1,
/// The eponymous EVM instruction.
SWAP2,
/// The eponymous EVM instruction.
SWAP3,
/// The eponymous EVM instruction.
SWAP4,
/// The eponymous EVM instruction.
SWAP5,
/// The eponymous EVM instruction.
SWAP6,
/// The eponymous EVM instruction.
SWAP7,
/// The eponymous EVM instruction.
SWAP8,
/// The eponymous EVM instruction.
SWAP9,
/// The eponymous EVM instruction.
SWAP10,
/// The eponymous EVM instruction.
SWAP11,
/// The eponymous EVM instruction.
SWAP12,
/// The eponymous EVM instruction.
SWAP13,
/// The eponymous EVM instruction.
SWAP14,
/// The eponymous EVM instruction.
SWAP15,
/// The eponymous EVM instruction.
SWAP16,
/// The eponymous EVM instruction.
POP,
/// Sets the current basic code block.
#[serde(rename = "tag")]
Tag,
/// The eponymous EVM instruction.
JUMP,
/// The eponymous EVM instruction.
JUMPI,
/// The eponymous EVM instruction.
JUMPDEST,
/// The eponymous EVM instruction.
ADD,
/// The eponymous EVM instruction.
SUB,
/// The eponymous EVM instruction.
MUL,
/// The eponymous EVM instruction.
DIV,
/// The eponymous EVM instruction.
MOD,
/// The eponymous EVM instruction.
SDIV,
/// The eponymous EVM instruction.
SMOD,
/// The eponymous EVM instruction.
LT,
/// The eponymous EVM instruction.
GT,
/// The eponymous EVM instruction.
EQ,
/// The eponymous EVM instruction.
ISZERO,
/// The eponymous EVM instruction.
SLT,
/// The eponymous EVM instruction.
SGT,
/// The eponymous EVM instruction.
OR,
/// The eponymous EVM instruction.
XOR,
/// The eponymous EVM instruction.
NOT,
/// The eponymous EVM instruction.
AND,
/// The eponymous EVM instruction.
SHL,
/// The eponymous EVM instruction.
SHR,
/// The eponymous EVM instruction.
SAR,
/// The eponymous EVM instruction.
BYTE,
/// The eponymous EVM instruction.
ADDMOD,
/// The eponymous EVM instruction.
MULMOD,
/// The eponymous EVM instruction.
EXP,
/// The eponymous EVM instruction.
SIGNEXTEND,
/// The eponymous EVM instruction.
SHA3,
/// The eponymous EVM instruction.
KECCAK256,
/// The eponymous EVM instruction.
MLOAD,
/// The eponymous EVM instruction.
MSTORE,
/// The eponymous EVM instruction.
MSTORE8,
/// The eponymous EVM instruction.
MCOPY,
/// The eponymous EVM instruction.
SLOAD,
/// The eponymous EVM instruction.
SSTORE,
/// The eponymous EVM instruction.
TLOAD,
/// The eponymous EVM instruction.
TSTORE,
/// The eponymous EVM instruction.
PUSHIMMUTABLE,
/// The eponymous EVM instruction.
ASSIGNIMMUTABLE,
/// The eponymous EVM instruction.
CALLDATALOAD,
/// The eponymous EVM instruction.
CALLDATASIZE,
/// The eponymous EVM instruction.
CALLDATACOPY,
/// The eponymous EVM instruction.
CODESIZE,
/// The eponymous EVM instruction.
CODECOPY,
/// The eponymous EVM instruction.
PUSHSIZE,
/// The eponymous EVM instruction.
EXTCODESIZE,
/// The eponymous EVM instruction.
EXTCODEHASH,
/// The eponymous EVM instruction.
RETURNDATASIZE,
/// The eponymous EVM instruction.
RETURNDATACOPY,
/// The eponymous EVM instruction.
RETURN,
/// The eponymous EVM instruction.
REVERT,
/// The eponymous EVM instruction.
STOP,
/// The eponymous EVM instruction.
INVALID,
/// The eponymous EVM instruction.
LOG0,
/// The eponymous EVM instruction.
LOG1,
/// The eponymous EVM instruction.
LOG2,
/// The eponymous EVM instruction.
LOG3,
/// The eponymous EVM instruction.
LOG4,
/// The eponymous EVM instruction.
CALL,
/// The eponymous EVM instruction.
STATICCALL,
/// The eponymous EVM instruction.
DELEGATECALL,
/// The eponymous EVM instruction.
CREATE,
/// The eponymous EVM instruction.
CREATE2,
/// The eponymous PolkaVM instruction.
#[serde(rename = "$ZK_CREATE")]
ZK_CREATE,
/// The eponymous PolkaVM instruction.
#[serde(rename = "$ZK_CREATE2")]
ZK_CREATE2,
/// The eponymous EVM instruction.
ADDRESS,
/// The eponymous EVM instruction.
CALLER,
/// The eponymous EVM instruction.
CALLVALUE,
/// The eponymous EVM instruction.
GAS,
/// The eponymous EVM instruction.
BALANCE,
/// The eponymous EVM instruction.
SELFBALANCE,
/// The eponymous EVM instruction.
PUSHLIB,
/// The eponymous EVM instruction.
PUSHDEPLOYADDRESS,
/// The eponymous EVM instruction.
GASLIMIT,
/// The eponymous EVM instruction.
GASPRICE,
/// The eponymous EVM instruction.
ORIGIN,
/// The eponymous EVM instruction.
CHAINID,
/// The eponymous EVM instruction.
TIMESTAMP,
/// The eponymous EVM instruction.
NUMBER,
/// The eponymous EVM instruction.
BLOCKHASH,
/// The eponymous EVM instruction.
BLOBHASH,
/// The eponymous EVM instruction.
DIFFICULTY,
/// The eponymous EVM instruction.
PREVRANDAO,
/// The eponymous EVM instruction.
COINBASE,
/// The eponymous EVM instruction.
BASEFEE,
/// The eponymous EVM instruction.
BLOBBASEFEE,
/// The eponymous EVM instruction.
MSIZE,
/// The eponymous EVM instruction.
CALLCODE,
/// The eponymous EVM instruction.
PC,
/// The eponymous EVM instruction.
EXTCODECOPY,
/// The eponymous EVM instruction.
SELFDESTRUCT,
/// The recursive function call instruction.
#[serde(skip)]
RecursiveCall {
/// The called function name.
name: String,
/// The called function key.
entry_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The stack state hash after return.
stack_hash: md5::Digest,
/// The input size.
input_size: usize,
/// The output size.
output_size: usize,
/// The return address.
return_address: revive_llvm_context::PolkaVMFunctionBlockKey,
},
/// The recursive function return instruction.
#[serde(skip)]
RecursiveReturn {
/// The output size.
input_size: usize,
},
}
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tag => write!(f, "Tag"),
Self::RecursiveCall {
name,
entry_key,
input_size,
output_size,
return_address,
..
} => write!(
f,
"RECURSIVE_CALL({}_{}, {}, {}, {})",
name, entry_key, input_size, output_size, return_address
),
Self::RecursiveReturn { input_size } => write!(f, "RECURSIVE_RETURN({})", input_size),
_ => write!(
f,
"{}",
serde_json::to_string(self)
.expect("Always valid")
.trim_matches('\"')
),
}
}
}
@@ -0,0 +1,106 @@
//! Translates the stack memory operations.
use inkwell::values::BasicValue;
/// Translates the ordinar value push.
pub fn push<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
value: String,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let result = context
.word_type()
.const_int_from_string(
value.to_ascii_uppercase().as_str(),
inkwell::types::StringRadix::Hexadecimal,
)
.expect("Always valid")
.as_basic_value_enum();
Ok(result)
}
/// Translates the block tag label push.
pub fn push_tag<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
value: String,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let result = context
.word_type()
.const_int_from_string(value.as_str(), inkwell::types::StringRadix::Decimal)
.expect("Always valid");
Ok(result.as_basic_value_enum())
}
/// Translates the stack memory duplicate.
pub fn dup<'ctx, D>(
context: &mut revive_llvm_context::PolkaVMContext<'ctx, D>,
offset: usize,
height: usize,
original: &mut Option<String>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let element = &context.evmla().stack[height - offset - 1];
let value = context.build_load(
revive_llvm_context::PolkaVMPointer::new_stack_field(
context,
element.to_llvm().into_pointer_value(),
),
format!("dup{offset}").as_str(),
)?;
element.original.clone_into(original);
Ok(value)
}
/// Translates the stack memory swap.
pub fn swap<D>(
context: &mut revive_llvm_context::PolkaVMContext<D>,
offset: usize,
height: usize,
) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
let top_element = context.evmla().stack[height - 1].to_owned();
let top_pointer = revive_llvm_context::PolkaVMPointer::new_stack_field(
context,
top_element.to_llvm().into_pointer_value(),
);
let top_value = context.build_load(top_pointer, format!("swap{offset}_top_value").as_str())?;
let swap_element = context.evmla().stack[height - offset - 1].to_owned();
let swap_pointer = revive_llvm_context::PolkaVMPointer::new_stack_field(
context,
swap_element.to_llvm().into_pointer_value(),
);
let swap_value =
context.build_load(swap_pointer, format!("swap{offset}_swap_value").as_str())?;
swap_element
.original
.clone_into(&mut context.evmla_mut().stack[height - 1].original);
top_element
.original
.clone_into(&mut context.evmla_mut().stack[height - offset - 1].original);
context.build_store(top_pointer, swap_value)?;
context.build_store(swap_pointer, top_value)?;
Ok(())
}
/// Translates the stack memory pop.
pub fn pop<D>(_context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()>
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
Ok(())
}
+295
View File
@@ -0,0 +1,295 @@
//! The `solc --asm-json` output.
pub mod data;
pub mod instruction;
use std::collections::BTreeMap;
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use sha3::Digest;
use crate::evmla::ethereal_ir::entry_link::EntryLink;
use crate::evmla::ethereal_ir::EtherealIR;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use self::data::Data;
use self::instruction::name::Name as InstructionName;
use self::instruction::Instruction;
/// The JSON assembly.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Assembly {
/// The metadata string.
#[serde(rename = ".auxdata")]
pub auxdata: Option<String>,
/// The deploy code instructions.
#[serde(rename = ".code")]
pub code: Option<Vec<Instruction>>,
/// The runtime code.
#[serde(rename = ".data")]
pub data: Option<BTreeMap<String, Data>>,
/// The full contract path.
#[serde(skip_serializing_if = "Option::is_none")]
pub full_path: Option<String>,
/// The factory dependency paths.
#[serde(default = "HashSet::new")]
pub factory_dependencies: HashSet<String>,
/// The EVMLA extra metadata.
#[serde(skip_serializing_if = "Option::is_none")]
pub extra_metadata: Option<ExtraMetadata>,
}
impl Assembly {
/// Gets the contract `keccak256` hash.
pub fn keccak256(&self) -> String {
let json = serde_json::to_vec(self).expect("Always valid");
hex::encode(sha3::Keccak256::digest(json.as_slice()))
}
/// Sets the full contract path.
pub fn set_full_path(&mut self, full_path: String) {
self.full_path = Some(full_path);
}
/// Returns the full contract path if it is set, or `<undefined>` otherwise.
/// # Panics
/// If the `full_path` has not been set.
pub fn full_path(&self) -> &str {
self.full_path
.as_deref()
.unwrap_or_else(|| panic!("The full path of some contracts is unset"))
}
/// Get the list of missing deployable libraries.
pub fn get_missing_libraries(&self) -> HashSet<String> {
let mut missing_libraries = HashSet::new();
if let Some(code) = self.code.as_ref() {
for instruction in code.iter() {
if let InstructionName::PUSHLIB = instruction.name {
let library_path = instruction.value.to_owned().expect("Always exists");
missing_libraries.insert(library_path);
}
}
}
if let Some(data) = self.data.as_ref() {
for (_, data) in data.iter() {
missing_libraries.extend(data.get_missing_libraries());
}
}
missing_libraries
}
/// Replaces the deploy code dependencies with full contract path and returns the list.
pub fn deploy_dependencies_pass(
&mut self,
full_path: &str,
hash_data_mapping: &BTreeMap<String, String>,
) -> anyhow::Result<BTreeMap<String, String>> {
let mut index_path_mapping = BTreeMap::new();
let index = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2);
index_path_mapping.insert(index, full_path.to_owned());
let dependencies = match self.data.as_mut() {
Some(dependencies) => dependencies,
None => return Ok(index_path_mapping),
};
for (index, data) in dependencies.iter_mut() {
if index == "0" {
continue;
}
let mut index_extended = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2 - index.len());
index_extended.push_str(index.as_str());
*data = match data {
Data::Assembly(assembly) => {
let hash = assembly.keccak256();
let full_path =
hash_data_mapping
.get(hash.as_str())
.cloned()
.ok_or_else(|| {
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
})?;
self.factory_dependencies.insert(full_path.to_owned());
index_path_mapping.insert(index_extended, full_path.clone());
Data::Path(full_path)
}
Data::Hash(hash) => {
index_path_mapping.insert(index_extended, hash.to_owned());
continue;
}
_ => continue,
};
}
Ok(index_path_mapping)
}
/// Replaces the runtime code dependencies with full contract path and returns the list.
pub fn runtime_dependencies_pass(
&mut self,
full_path: &str,
hash_data_mapping: &BTreeMap<String, String>,
) -> anyhow::Result<BTreeMap<String, String>> {
let mut index_path_mapping = BTreeMap::new();
let index = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2);
index_path_mapping.insert(index, full_path.to_owned());
let dependencies = match self
.data
.as_mut()
.and_then(|data| data.get_mut("0"))
.and_then(|data| data.get_assembly_mut())
.and_then(|assembly| assembly.data.as_mut())
{
Some(dependencies) => dependencies,
None => return Ok(index_path_mapping),
};
for (index, data) in dependencies.iter_mut() {
let mut index_extended = "0".repeat(revive_common::BYTE_LENGTH_WORD * 2 - index.len());
index_extended.push_str(index.as_str());
*data = match data {
Data::Assembly(assembly) => {
let hash = assembly.keccak256();
let full_path =
hash_data_mapping
.get(hash.as_str())
.cloned()
.ok_or_else(|| {
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
})?;
self.factory_dependencies.insert(full_path.to_owned());
index_path_mapping.insert(index_extended, full_path.clone());
Data::Path(full_path)
}
Data::Hash(hash) => {
index_path_mapping.insert(index_extended, hash.to_owned());
continue;
}
_ => continue,
};
}
Ok(index_path_mapping)
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for Assembly
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
let mut entry = revive_llvm_context::PolkaVMEntryFunction::default();
entry.declare(context)?;
revive_llvm_context::PolkaVMDeployCodeFunction::new(
revive_llvm_context::PolkaVMDummyLLVMWritable::default(),
)
.declare(context)?;
revive_llvm_context::PolkaVMRuntimeCodeFunction::new(
revive_llvm_context::PolkaVMDummyLLVMWritable::default(),
)
.declare(context)?;
revive_llvm_context::PolkaVMImmutableDataLoadFunction.declare(context)?;
entry.into_llvm(context)?;
Ok(())
}
fn into_llvm(
mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
let full_path = self.full_path().to_owned();
context
.debug_config()
.dump_evmla(full_path.as_str(), self.to_string().as_str())?;
let deploy_code_blocks = EtherealIR::get_blocks(
context.evmla().version.to_owned(),
revive_llvm_context::PolkaVMCodeType::Deploy,
self.code
.as_deref()
.ok_or_else(|| anyhow::anyhow!("Deploy code instructions not found"))?,
)?;
let data = self
.data
.ok_or_else(|| anyhow::anyhow!("Runtime code data not found"))?
.remove("0")
.expect("Always exists");
context
.debug_config()
.dump_evmla(full_path.as_str(), data.to_string().as_str())?;
let runtime_code_instructions = match data {
Data::Assembly(assembly) => assembly
.code
.ok_or_else(|| anyhow::anyhow!("Runtime code instructions not found"))?,
Data::Hash(hash) => {
anyhow::bail!("Expected runtime code instructions, found hash `{}`", hash)
}
Data::Path(path) => {
anyhow::bail!("Expected runtime code instructions, found path `{}`", path)
}
};
let runtime_code_blocks = EtherealIR::get_blocks(
context.evmla().version.to_owned(),
revive_llvm_context::PolkaVMCodeType::Runtime,
runtime_code_instructions.as_slice(),
)?;
let extra_metadata = self.extra_metadata.take().unwrap_or_default();
let mut blocks = deploy_code_blocks;
blocks.extend(runtime_code_blocks);
let mut ethereal_ir =
EtherealIR::new(context.evmla().version.to_owned(), extra_metadata, blocks)?;
context
.debug_config()
.dump_ethir(full_path.as_str(), ethereal_ir.to_string().as_str())?;
ethereal_ir.declare(context)?;
ethereal_ir.into_llvm(context)?;
revive_llvm_context::PolkaVMDeployCodeFunction::new(EntryLink::new(
revive_llvm_context::PolkaVMCodeType::Deploy,
))
.into_llvm(context)?;
revive_llvm_context::PolkaVMRuntimeCodeFunction::new(EntryLink::new(
revive_llvm_context::PolkaVMCodeType::Runtime,
))
.into_llvm(context)?;
revive_llvm_context::PolkaVMImmutableDataLoadFunction.into_llvm(context)?;
Ok(())
}
}
impl std::fmt::Display for Assembly {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(instructions) = self.code.as_ref() {
for (index, instruction) in instructions.iter().enumerate() {
match instruction.name {
InstructionName::Tag => writeln!(f, "{index:03} {instruction}")?,
_ => writeln!(f, "{index:03} {instruction}")?,
}
}
}
Ok(())
}
}
@@ -0,0 +1,48 @@
//! The Ethereal IR entry function link.
use inkwell::values::BasicValue;
use crate::evmla::ethereal_ir::EtherealIR;
/// The Ethereal IR entry function link.
/// The link represents branching between the deploy and runtime code.
#[derive(Debug, Clone)]
pub struct EntryLink {
/// The code part type.
pub code_type: revive_llvm_context::PolkaVMCodeType,
}
impl EntryLink {
/// A shortcut constructor.
pub fn new(code_type: revive_llvm_context::PolkaVMCodeType) -> Self {
Self { code_type }
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for EntryLink
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
let target = context
.get_function(EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME)
.expect("Always exists")
.borrow()
.declaration();
let is_deploy_code = match self.code_type {
revive_llvm_context::PolkaVMCodeType::Deploy => context
.integer_type(revive_common::BIT_LENGTH_BOOLEAN)
.const_int(1, false),
revive_llvm_context::PolkaVMCodeType::Runtime => context
.integer_type(revive_common::BIT_LENGTH_BOOLEAN)
.const_int(0, false),
};
context.build_call(
target,
&[is_deploy_code.as_basic_value_enum()],
format!("call_link_{}", EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME).as_str(),
);
Ok(())
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
//! The Ethereal IR block element stack element.
/// The Ethereal IR block element stack element.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Element {
/// The runtime value.
Value(String),
/// The compile-time value.
Constant(num::BigUint),
/// The compile-time destination tag.
Tag(num::BigUint),
/// The compile-time path.
Path(String),
/// The compile-time hexadecimal data chunk.
Data(String),
/// The recursive function return address.
ReturnAddress(usize),
}
impl Element {
pub fn value(identifier: String) -> Self {
Self::Value(identifier)
}
}
impl std::fmt::Display for Element {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Value(identifier) => write!(f, "V_{identifier}"),
Self::Constant(value) => write!(f, "{value:X}"),
Self::Tag(tag) => write!(f, "T_{tag}"),
Self::Path(path) => write!(f, "{path}"),
Self::Data(data) => write!(f, "{data}"),
Self::ReturnAddress(_) => write!(f, "RETURN_ADDRESS"),
}
}
}
@@ -0,0 +1,120 @@
//! The Ethereal IR block element stack.
pub mod element;
use self::element::Element;
/// The Ethereal IR block element stack.
#[derive(Debug, Default, Clone)]
pub struct Stack {
/// The stack elements.
pub elements: Vec<Element>,
}
impl Stack {
/// The default stack size.
pub const DEFAULT_STACK_SIZE: usize = 16;
/// A shortcut constructor.
pub fn new() -> Self {
Self {
elements: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
}
}
/// A shortcut constructor.
pub fn with_capacity(capacity: usize) -> Self {
Self {
elements: Vec::with_capacity(capacity),
}
}
/// A shortcut constructor.
pub fn new_with_elements(elements: Vec<Element>) -> Self {
Self { elements }
}
/// The stack state hash, which acts as a block identifier.
/// Each block clone has its own initial stack state, which uniquely identifies the block.
pub fn hash(&self) -> md5::Digest {
let mut hash_context = md5::Context::new();
for element in self.elements.iter() {
match element {
Element::Tag(tag) => hash_context.consume(tag.to_bytes_be()),
_ => hash_context.consume([0]),
}
}
hash_context.compute()
}
/// Pushes an element onto the stack.
pub fn push(&mut self, element: Element) {
self.elements.push(element);
}
/// Appends another stack on top of this one.
pub fn append(&mut self, other: &mut Self) {
self.elements.append(&mut other.elements);
}
/// Pops a stack element.
pub fn pop(&mut self) -> anyhow::Result<Element> {
self.elements
.pop()
.ok_or_else(|| anyhow::anyhow!("Stack underflow"))
}
/// Pops the tag from the top.
pub fn pop_tag(&mut self) -> anyhow::Result<num::BigUint> {
match self.elements.pop() {
Some(Element::Tag(tag)) => Ok(tag),
Some(element) => anyhow::bail!("Expected tag, found {}", element),
None => anyhow::bail!("Stack underflow"),
}
}
/// Swaps two stack elements.
pub fn swap(&mut self, index: usize) -> anyhow::Result<()> {
if self.elements.len() < index + 1 {
anyhow::bail!("Stack underflow");
}
let length = self.elements.len();
self.elements.swap(length - 1, length - 1 - index);
Ok(())
}
/// Duplicates a stack element.
pub fn dup(&mut self, index: usize) -> anyhow::Result<Element> {
if self.elements.len() < index {
anyhow::bail!("Stack underflow");
}
Ok(self.elements[self.elements.len() - index].to_owned())
}
/// Returns the stack length.
pub fn len(&self) -> usize {
self.elements.len()
}
/// Returns an emptiness flag.
pub fn is_empty(&self) -> bool {
self.elements.len() == 0
}
}
impl std::fmt::Display for Stack {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[ {} ]",
self.elements
.iter()
.map(Element::to_string)
.collect::<Vec<String>>()
.join(" | ")
)
}
}
@@ -0,0 +1,156 @@
//! The Ethereal IR block.
pub mod element;
use std::collections::HashSet;
use num::Zero;
use crate::evmla::assembly::instruction::name::Name as InstructionName;
use crate::evmla::assembly::instruction::Instruction;
use self::element::stack::Stack as ElementStack;
use self::element::Element;
/// The Ethereal IR block.
#[derive(Debug, Clone)]
pub struct Block {
/// The Solidity compiler version.
pub solc_version: semver::Version,
/// The block key.
pub key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The block instance.
pub instance: Option<usize>,
/// The block elements relevant to the stack consistency.
pub elements: Vec<Element>,
/// The block predecessors.
pub predecessors: HashSet<(revive_llvm_context::PolkaVMFunctionBlockKey, usize)>,
/// The initial stack state.
pub initial_stack: ElementStack,
/// The stack.
pub stack: ElementStack,
/// The extra block hashes for alternative routes.
pub extra_hashes: Vec<md5::Digest>,
}
impl Block {
/// The elements vector initial capacity.
pub const ELEMENTS_VECTOR_DEFAULT_CAPACITY: usize = 64;
/// The predecessors hashset initial capacity.
pub const PREDECESSORS_HASHSET_DEFAULT_CAPACITY: usize = 4;
/// Assembles a block from the sequence of instructions.
pub fn try_from_instructions(
solc_version: semver::Version,
code_type: revive_llvm_context::PolkaVMCodeType,
slice: &[Instruction],
) -> anyhow::Result<(Self, usize)> {
let mut cursor = 0;
let tag: num::BigUint = match slice[cursor].name {
InstructionName::Tag => {
let tag = slice[cursor]
.value
.as_deref()
.expect("Always exists")
.parse()
.expect("Always valid");
cursor += 1;
tag
}
_ => num::BigUint::zero(),
};
let mut block = Self {
solc_version: solc_version.clone(),
key: revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, tag),
instance: None,
elements: Vec::with_capacity(Self::ELEMENTS_VECTOR_DEFAULT_CAPACITY),
predecessors: HashSet::with_capacity(Self::PREDECESSORS_HASHSET_DEFAULT_CAPACITY),
initial_stack: ElementStack::new(),
stack: ElementStack::new(),
extra_hashes: vec![],
};
let mut dead_code = false;
while cursor < slice.len() {
if !dead_code {
let element: Element = Element::new(solc_version.clone(), slice[cursor].to_owned());
block.elements.push(element);
}
match slice[cursor].name {
InstructionName::RETURN
| InstructionName::REVERT
| InstructionName::STOP
| InstructionName::INVALID => {
cursor += 1;
dead_code = true;
}
InstructionName::JUMP => {
cursor += 1;
dead_code = true;
}
InstructionName::Tag => {
break;
}
_ => {
cursor += 1;
}
}
}
Ok((block, cursor))
}
/// Inserts a predecessor tag.
pub fn insert_predecessor(
&mut self,
key: revive_llvm_context::PolkaVMFunctionBlockKey,
instance: usize,
) {
self.predecessors.insert((key, instance));
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for Block
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
context.set_code_type(self.key.code_type);
for element in self.elements.into_iter() {
element.into_llvm(context)?;
}
Ok(())
}
}
impl std::fmt::Display for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"block_{}/{}: {}",
self.key,
self.instance.unwrap_or_default(),
if self.predecessors.is_empty() {
"".to_owned()
} else {
format!(
"(predecessors: {})",
self.predecessors
.iter()
.map(|(key, instance)| format!("{}/{}", key, instance))
.collect::<Vec<String>>()
.join(", ")
)
},
)?;
for element in self.elements.iter() {
writeln!(f, " {element}")?;
}
Ok(())
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
//! The Ethereal IR block queue element.
use crate::evmla::ethereal_ir::function::block::element::stack::Stack;
/// The Ethereal IR block queue element.
#[derive(Debug, Clone)]
pub struct QueueElement {
/// The block key.
pub block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The block predecessor.
pub predecessor: Option<(revive_llvm_context::PolkaVMFunctionBlockKey, usize)>,
/// The predecessor's last stack state.
pub stack: Stack,
}
impl QueueElement {
/// A shortcut constructor.
pub fn new(
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
predecessor: Option<(revive_llvm_context::PolkaVMFunctionBlockKey, usize)>,
stack: Stack,
) -> Self {
Self {
block_key,
predecessor,
stack,
}
}
}
@@ -0,0 +1,41 @@
//! The Ethereal IR function type.
/// The Ethereal IR function type.
#[derive(Debug, Clone)]
pub enum Type {
/// The initial function, combining deploy and runtime code.
Initial,
/// The recursive function with a specific block starting its recursive context.
Recursive {
/// The function name.
name: String,
/// The function initial block key.
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The size of stack input (in cells or 256-bit words).
input_size: usize,
/// The size of stack output (in cells or 256-bit words).
output_size: usize,
},
}
impl Type {
/// A shortcut constructor.
pub fn new_initial() -> Self {
Self::Initial
}
/// A shortcut constructor.
pub fn new_recursive(
name: String,
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
input_size: usize,
output_size: usize,
) -> Self {
Self::Recursive {
name,
block_key,
input_size,
output_size,
}
}
}
@@ -0,0 +1,65 @@
//! The Ethereal IR block visited element.
use std::cmp::Ordering;
/// The Ethereal IR block visited element.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VisitedElement {
/// The block key.
pub block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
/// The initial stack state hash.
pub stack_hash: md5::Digest,
}
impl VisitedElement {
/// A shortcut constructor.
pub fn new(
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
stack_hash: md5::Digest,
) -> Self {
Self {
block_key,
stack_hash,
}
}
}
impl PartialOrd for VisitedElement {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for VisitedElement {
fn cmp(&self, other: &Self) -> Ordering {
match (self.block_key.code_type, other.block_key.code_type) {
(
revive_llvm_context::PolkaVMCodeType::Deploy,
revive_llvm_context::PolkaVMCodeType::Runtime,
) => Ordering::Less,
(
revive_llvm_context::PolkaVMCodeType::Runtime,
revive_llvm_context::PolkaVMCodeType::Deploy,
) => Ordering::Greater,
(
revive_llvm_context::PolkaVMCodeType::Deploy,
revive_llvm_context::PolkaVMCodeType::Deploy,
)
| (
revive_llvm_context::PolkaVMCodeType::Runtime,
revive_llvm_context::PolkaVMCodeType::Runtime,
) => {
let tag_comparison = self.block_key.tag.cmp(&other.block_key.tag);
if tag_comparison == Ordering::Equal {
if self.stack_hash == other.stack_hash {
Ordering::Equal
} else {
Ordering::Less
}
} else {
tag_comparison
}
}
}
}
}
@@ -0,0 +1,134 @@
//! The Ethereal IR of the EVM bytecode.
pub mod entry_link;
pub mod function;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use crate::evmla::assembly::instruction::Instruction;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use self::function::block::Block;
use self::function::r#type::Type as FunctionType;
use self::function::Function;
/// The Ethereal IR of the EVM bytecode.
/// The Ethereal IR (EthIR) is a special IR between the EVM legacy assembly and LLVM IR. It is
/// created to facilitate the translation and provide an additional environment for applying some
/// transformations, duplicating parts of the call and control flow graphs, tracking the
/// data flow, and a few more algorithms of static analysis.
/// The most important feature of EthIR is flattening the block tags and duplicating blocks for
/// each of initial states of the stack. The LLVM IR supports only static control flow, so the
/// stack state must be known all the way throughout the program.
#[derive(Debug)]
pub struct EtherealIR {
/// The Solidity compiler version.
pub solc_version: semver::Version,
/// The EVMLA extra metadata.
pub extra_metadata: ExtraMetadata,
/// The all-inlined function.
pub entry_function: Function,
/// The recursive functions.
pub recursive_functions: BTreeMap<revive_llvm_context::PolkaVMFunctionBlockKey, Function>,
}
impl EtherealIR {
/// The default entry function name.
pub const DEFAULT_ENTRY_FUNCTION_NAME: &'static str = "main";
/// The blocks hashmap initial capacity.
pub const BLOCKS_HASHMAP_DEFAULT_CAPACITY: usize = 64;
/// Assembles a sequence of functions from the sequence of instructions.
pub fn new(
solc_version: semver::Version,
extra_metadata: ExtraMetadata,
blocks: HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>,
) -> anyhow::Result<Self> {
let mut entry_function = Function::new(solc_version.clone(), FunctionType::new_initial());
let mut recursive_functions = BTreeMap::new();
let mut visited_functions = BTreeSet::new();
entry_function.traverse(
&blocks,
&mut recursive_functions,
&extra_metadata,
&mut visited_functions,
)?;
Ok(Self {
solc_version,
extra_metadata,
entry_function,
recursive_functions,
})
}
/// Gets blocks for the specified type of the contract code.
pub fn get_blocks(
solc_version: semver::Version,
code_type: revive_llvm_context::PolkaVMCodeType,
instructions: &[Instruction],
) -> anyhow::Result<HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>> {
let mut blocks = HashMap::with_capacity(Self::BLOCKS_HASHMAP_DEFAULT_CAPACITY);
let mut offset = 0;
while offset < instructions.len() {
let (block, size) = Block::try_from_instructions(
solc_version.clone(),
code_type,
&instructions[offset..],
)?;
blocks.insert(
revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, block.key.tag.clone()),
block,
);
offset += size;
}
Ok(blocks)
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for EtherealIR
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
self.entry_function.declare(context)?;
for (_key, function) in self.recursive_functions.iter_mut() {
function.declare(context)?;
}
Ok(())
}
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
context.evmla_mut().stack = vec![];
self.entry_function.into_llvm(context)?;
for (_key, function) in self.recursive_functions.into_iter() {
function.into_llvm(context)?;
}
Ok(())
}
}
impl std::fmt::Display for EtherealIR {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.entry_function)?;
for (_key, function) in self.recursive_functions.iter() {
writeln!(f, "{}", function)?;
}
Ok(())
}
}
+4
View File
@@ -0,0 +1,4 @@
//! The EVM legacy assembly compiling tools.
pub mod assembly;
pub mod ethereal_ir;
+43 -13
View File
@@ -2,6 +2,7 @@
pub(crate) mod build;
pub(crate) mod r#const;
pub(crate) mod evmla;
pub(crate) mod missing_libraries;
pub(crate) mod process;
pub(crate) mod project;
@@ -25,6 +26,7 @@ pub use self::project::Project;
pub use self::r#const::*;
pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract;
pub use self::solc::combined_json::CombinedJson as SolcCombinedJson;
pub use self::solc::pipeline::Pipeline as SolcPipeline;
#[cfg(not(target_os = "emscripten"))]
pub use self::solc::solc_compiler::SolcCompiler;
#[cfg(target_os = "emscripten")]
@@ -51,7 +53,6 @@ pub mod test_utils;
pub mod tests;
use std::collections::BTreeSet;
use std::io::Write;
use std::path::PathBuf;
/// Runs the Yul mode.
@@ -118,6 +119,7 @@ pub fn standard_output<T: Compiler>(
evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings,
force_evmla: bool,
include_metadata_hash: bool,
base_path: Option<String>,
include_paths: Vec<String>,
@@ -127,6 +129,7 @@ pub fn standard_output<T: Compiler>(
debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<Build> {
let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
let solc_input = SolcStandardJsonInput::try_from_paths(
SolcStandardJsonInputLanguage::Solidity,
@@ -134,7 +137,7 @@ pub fn standard_output<T: Compiler>(
input_files,
libraries,
remappings,
SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsSelection::new_required(solc_pipeline),
SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled,
None,
@@ -142,6 +145,7 @@ pub fn standard_output<T: Compiler>(
optimizer_settings.is_fallback_to_size_enabled(),
),
None,
solc_pipeline == SolcPipeline::Yul,
suppressed_warnings,
)?;
@@ -152,7 +156,13 @@ pub fn standard_output<T: Compiler>(
.collect();
let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
let mut solc_output = solc.standard_json(solc_input, base_path, include_paths, allow_paths)?;
let mut solc_output = solc.standard_json(
solc_input,
solc_pipeline,
base_path,
include_paths,
allow_paths,
)?;
if let Some(errors) = solc_output.errors.as_deref() {
let mut has_errors = false;
@@ -162,7 +172,7 @@ pub fn standard_output<T: Compiler>(
has_errors = true;
}
writeln!(std::io::stderr(), "{error}")?;
eprintln!("{error}");
}
if has_errors {
@@ -170,8 +180,13 @@ pub fn standard_output<T: Compiler>(
}
}
let project =
solc_output.try_to_project(source_code_files, libraries, &solc_version, &debug_config)?;
let project = solc_output.try_to_project(
source_code_files,
libraries,
solc_pipeline,
&solc_version,
&debug_config,
)?;
let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?;
@@ -179,17 +194,20 @@ pub fn standard_output<T: Compiler>(
}
/// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_json<T: Compiler>(
solc: &mut T,
detect_missing_libraries: bool,
force_evmla: bool,
base_path: Option<String>,
include_paths: Vec<String>,
allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<()> {
let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
let solc_input = SolcStandardJsonInput::try_from_stdin()?;
let solc_input = SolcStandardJsonInput::try_from_stdin(solc_pipeline)?;
let source_code_files = solc_input
.sources
.iter()
@@ -207,7 +225,13 @@ pub fn standard_json<T: Compiler>(
};
let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
let mut solc_output = solc.standard_json(solc_input, base_path, include_paths, allow_paths)?;
let mut solc_output = solc.standard_json(
solc_input,
solc_pipeline,
base_path,
include_paths,
allow_paths,
)?;
if let Some(errors) = solc_output.errors.as_deref() {
for error in errors.iter() {
@@ -218,8 +242,13 @@ pub fn standard_json<T: Compiler>(
}
}
let project =
solc_output.try_to_project(source_code_files, libraries, &solc_version, &debug_config)?;
let project = solc_output.try_to_project(
source_code_files,
libraries,
solc_pipeline,
&solc_version,
&debug_config,
)?;
if detect_missing_libraries {
let missing_libraries = project.get_missing_libraries();
@@ -242,6 +271,7 @@ pub fn combined_json<T: Compiler>(
evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings,
force_evmla: bool,
include_metadata_hash: bool,
base_path: Option<String>,
include_paths: Vec<String>,
@@ -259,6 +289,7 @@ pub fn combined_json<T: Compiler>(
evm_version,
solc_optimizer_enabled,
optimizer_settings,
force_evmla,
include_metadata_hash,
base_path,
include_paths,
@@ -278,11 +309,10 @@ pub fn combined_json<T: Compiler>(
combined_json.write_to_directory(output_directory.as_path(), overwrite)?;
}
None => {
writeln!(
std::io::stdout(),
println!(
"{}",
serde_json::to_string(&combined_json).expect("Always valid")
)?;
);
}
}
std::process::exit(0);
@@ -0,0 +1,47 @@
//! The contract EVM legacy assembly source code.
use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::evmla::assembly::Assembly;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
/// The contract EVM legacy assembly source code.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub struct EVMLA {
/// The EVM legacy assembly source code.
pub assembly: Assembly,
}
impl EVMLA {
/// A shortcut constructor.
pub fn new(mut assembly: Assembly, extra_metadata: ExtraMetadata) -> Self {
assembly.extra_metadata = Some(extra_metadata);
Self { assembly }
}
/// Get the list of missing deployable libraries.
pub fn get_missing_libraries(&self) -> HashSet<String> {
self.assembly.get_missing_libraries()
}
}
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for EVMLA
where
D: revive_llvm_context::PolkaVMDependency + Clone,
{
fn declare(
&mut self,
context: &mut revive_llvm_context::PolkaVMContext<D>,
) -> anyhow::Result<()> {
self.assembly.declare(context)
}
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
self.assembly.into_llvm(context)
}
}
@@ -1,5 +1,6 @@
//! The contract source code.
pub mod evmla;
pub mod llvm_ir;
pub mod yul;
@@ -8,17 +9,24 @@ use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::evmla::assembly::Assembly;
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
use crate::yul::parser::statement::object::Object;
use self::evmla::EVMLA;
use self::llvm_ir::LLVMIR;
use self::yul::Yul;
/// The contract source code.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
#[allow(clippy::enum_variant_names)]
pub enum IR {
/// The Yul source code.
Yul(Yul),
/// The EVM legacy assembly source code.
EVMLA(EVMLA),
/// The LLVM IR source code.
LLVMIR(LLVMIR),
}
@@ -29,6 +37,11 @@ impl IR {
Self::Yul(Yul::new(source_code, object))
}
/// A shortcut constructor.
pub fn new_evmla(assembly: Assembly, extra_metadata: ExtraMetadata) -> Self {
Self::EVMLA(EVMLA::new(assembly, extra_metadata))
}
/// A shortcut constructor.
pub fn new_llvm_ir(path: String, source: String) -> Self {
Self::LLVMIR(LLVMIR::new(path, source))
@@ -38,6 +51,7 @@ impl IR {
pub fn get_missing_libraries(&self) -> HashSet<String> {
match self {
Self::Yul(inner) => inner.get_missing_libraries(),
Self::EVMLA(inner) => inner.get_missing_libraries(),
Self::LLVMIR(_inner) => HashSet::new(),
}
}
@@ -53,6 +67,7 @@ where
) -> anyhow::Result<()> {
match self {
Self::Yul(inner) => inner.declare(context),
Self::EVMLA(inner) => inner.declare(context),
Self::LLVMIR(_inner) => Ok(()),
}
}
@@ -60,6 +75,7 @@ where
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
match self {
Self::Yul(inner) => inner.into_llvm(context),
Self::EVMLA(inner) => inner.into_llvm(context),
Self::LLVMIR(_inner) => Ok(()),
}
}
@@ -54,10 +54,12 @@ impl Contract {
/// Returns the contract identifier, which is:
/// - the Yul object identifier for Yul
/// - the full contract path for EVM legacy assembly
/// - the module name for LLVM IR
pub fn identifier(&self) -> &str {
match self.ir {
IR::Yul(ref yul) => yul.object.identifier.as_str(),
IR::EVMLA(ref evm) => evm.assembly.full_path(),
IR::LLVMIR(ref llvm_ir) => llvm_ir.path.as_str(),
}
}
@@ -66,6 +68,7 @@ impl Contract {
pub fn drain_factory_dependencies(&mut self) -> HashSet<String> {
match self.ir {
IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(),
IR::EVMLA(ref mut evm) => evm.assembly.factory_dependencies.drain().collect(),
IR::LLVMIR(_) => HashSet::new(),
}
}
@@ -126,6 +129,10 @@ impl Contract {
IR::Yul(_) => {
context.set_yul_data(Default::default());
}
IR::EVMLA(_) => {
let evmla_data = revive_llvm_context::PolkaVMContextEVMLAData::new(version.default);
context.set_evmla_data(evmla_data);
}
IR::LLVMIR(_) => {}
}
+61 -35
View File
@@ -13,14 +13,14 @@ use path_slash::PathExt;
/// output directory.
/// Example: resolc ERC20.sol -O3 --bin --output-dir './build/'
#[derive(Debug, Parser)]
#[command(name = "The PolkaVM Solidity compiler", arg_required_else_help = true)]
#[structopt(name = "The PolkaVM Solidity compiler")]
pub struct Arguments {
/// Print the version and exit.
#[arg(long = "version")]
#[structopt(long = "version")]
pub version: bool,
/// Print the licence and exit.
#[arg(long = "license")]
#[structopt(long = "license")]
pub license: bool,
/// Specify the input paths and remappings.
@@ -31,141 +31,159 @@ pub struct Arguments {
/// Set the given path as the root of the source tree instead of the root of the filesystem.
/// Passed to `solc` without changes.
#[arg(long = "base-path")]
#[structopt(long = "base-path")]
pub base_path: Option<String>,
/// Make an additional source directory available to the default import callback.
/// Can be used multiple times. Can only be used if the base path has a non-empty value.
/// Passed to `solc` without changes.
#[arg(long = "include-path")]
#[structopt(long = "include-path")]
pub include_paths: Vec<String>,
/// Allow a given path for imports. A list of paths can be supplied by separating them with a comma.
/// Passed to `solc` without changes.
#[arg(long = "allow-paths")]
#[structopt(long = "allow-paths")]
pub allow_paths: Option<String>,
/// Create one file per component and contract/file at the specified directory, if given.
#[arg(short = 'o', long = "output-dir")]
#[structopt(short = 'o', long = "output-dir")]
pub output_directory: Option<PathBuf>,
/// Overwrite existing files (used together with -o).
#[arg(long = "overwrite")]
#[structopt(long = "overwrite")]
pub overwrite: bool,
/// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Use `3` for best performance and `z` for minimal size.
#[arg(short = 'O', long = "optimization")]
#[structopt(short = 'O', long = "optimization")]
pub optimization: Option<char>,
/// Try to recompile with -Oz if the bytecode is too large.
#[arg(long = "fallback-Oz")]
#[structopt(long = "fallback-Oz")]
pub fallback_to_optimizing_for_size: bool,
/// Disable the `solc` optimizer.
/// Use it if your project uses the `MSIZE` instruction, or in other cases.
/// Beware that it will prevent libraries from being inlined.
#[arg(long = "disable-solc-optimizer")]
#[structopt(long = "disable-solc-optimizer")]
pub disable_solc_optimizer: bool,
/// Specify the path to the `solc` executable. By default, the one in `${PATH}` is used.
/// Yul mode: `solc` is used for source code validation, as `resolc` itself assumes that the input Yul is valid.
/// LLVM IR mode: `solc` is unused.
#[arg(long = "solc")]
#[structopt(long = "solc")]
pub solc: Option<String>,
/// The EVM target version to generate IR for.
/// See https://github.com/paritytech/revive/blob/main/crates/common/src/evm_version.rs for reference.
#[arg(long = "evm-version")]
#[structopt(long = "evm-version")]
pub evm_version: Option<String>,
/// Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`.
/// Addresses are interpreted as hexadecimal strings prefixed with `0x`.
#[arg(short = 'l', long = "libraries")]
#[structopt(short = 'l', long = "libraries")]
pub libraries: Vec<String>,
/// Output a single JSON document containing the specified information.
/// Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`.
#[arg(long = "combined-json")]
#[structopt(long = "combined-json")]
pub combined_json: Option<String>,
/// Switch to standard JSON input/output mode. Read from stdin, write the result to stdout.
/// This is the default used by the Hardhat plugin.
#[arg(long = "standard-json")]
#[structopt(long = "standard-json")]
pub standard_json: bool,
/// Switch to missing deployable libraries detection mode.
/// Only available for standard JSON input/output mode.
/// Contracts are not compiled in this mode, and all compilation artifacts are not included.
#[arg(long = "detect-missing-libraries")]
#[structopt(long = "detect-missing-libraries")]
pub detect_missing_libraries: bool,
/// Switch to Yul mode.
/// Only one input Yul file is allowed.
/// Cannot be used with combined and standard JSON modes.
#[arg(long = "yul")]
#[structopt(long = "yul")]
pub yul: bool,
/// Switch to LLVM IR mode.
/// Only one input LLVM IR file is allowed.
/// Cannot be used with combined and standard JSON modes.
/// Use this mode at your own risk, as LLVM IR input validation is not implemented.
#[arg(long = "llvm-ir")]
#[structopt(long = "llvm-ir")]
pub llvm_ir: bool,
/// Forcibly switch to EVM legacy assembly pipeline.
/// It is useful for older revisions of `solc` 0.8, where Yul was considered highly experimental
/// and contained more bugs than today.
#[structopt(long = "force-evmla")]
pub force_evmla: bool,
/// Set metadata hash mode.
/// The only supported value is `none` that disables appending the metadata hash.
/// Is enabled by default.
#[arg(long = "metadata-hash")]
#[structopt(long = "metadata-hash")]
pub metadata_hash: Option<String>,
/// Output PolkaVM assembly of the contracts.
#[arg(long = "asm")]
#[structopt(long = "asm")]
pub output_assembly: bool,
/// Output PolkaVM bytecode of the contracts.
#[arg(long = "bin")]
#[structopt(long = "bin")]
pub output_binary: bool,
/// Suppress specified warnings.
/// Available arguments: `ecrecover`, `sendtransfer`, `extcodesize`, `txorigin`, `blocktimestamp`, `blocknumber`, `blockhash`.
#[arg(long = "suppress-warnings")]
#[structopt(long = "suppress-warnings")]
pub suppress_warnings: Option<Vec<String>>,
/// Generate source based debug information in the output code file. This only has an effect
/// with the LLVM-IR code generator and is ignored otherwise.
#[arg(short = 'g')]
#[structopt(short = 'g')]
pub emit_source_debug_info: bool,
/// Dump all IRs to files in the specified directory.
/// Only for testing and debugging.
#[arg(long = "debug-output-dir")]
#[structopt(long = "debug-output-dir")]
pub debug_output_directory: Option<PathBuf>,
/// Set the verify-each option in LLVM.
/// Only for testing and debugging.
#[arg(long = "llvm-verify-each")]
#[structopt(long = "llvm-verify-each")]
pub llvm_verify_each: bool,
/// Set the debug-logging option in LLVM.
/// Only for testing and debugging.
#[arg(long = "llvm-debug-logging")]
#[structopt(long = "llvm-debug-logging")]
pub llvm_debug_logging: bool,
/// Run this process recursively and provide JSON input to compile a single contract.
/// Only for usage from within the compiler.
#[arg(long = "recursive-process")]
#[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)]
#[arg(long = "recursive-process-input")]
#[structopt(long = "recursive-process-input")]
pub recursive_process_input: Option<String>,
}
impl Default for Arguments {
fn default() -> Self {
Self::new()
}
}
impl Arguments {
/// A shortcut constructor.
pub fn new() -> Self {
Self::parse()
}
/// Validates the arguments.
#[allow(clippy::collapsible_if)]
pub fn validate(&self) -> anyhow::Result<()> {
if self.version && std::env::args().count() > 2 {
anyhow::bail!("No other options are allowed while getting the compiler version.");
@@ -230,19 +248,27 @@ impl Arguments {
);
}
if self.force_evmla {
anyhow::bail!("EVM legacy assembly mode is not supported in Yul, LLVM IR and PolkaVM assembly modes.");
}
if self.disable_solc_optimizer {
anyhow::bail!("Disabling the solc optimizer is not supported in Yul, LLVM IR and PolkaVM assembly modes.");
}
}
if self.llvm_ir && self.solc.is_some() {
anyhow::bail!("`solc` is not used in LLVM IR and PolkaVM assembly modes.");
if self.llvm_ir {
if self.solc.is_some() {
anyhow::bail!("`solc` is not used in LLVM IR and PolkaVM assembly modes.");
}
}
if self.combined_json.is_some() && (self.output_assembly || self.output_binary) {
anyhow::bail!(
"Cannot output assembly or binary outside of JSON in combined JSON mode."
);
if self.combined_json.is_some() {
if self.output_assembly || self.output_binary {
anyhow::bail!(
"Cannot output assembly or binary outside of JSON in combined JSON mode."
);
}
}
if self.standard_json {
+18 -25
View File
@@ -2,7 +2,6 @@
pub mod arguments;
use std::io::Write;
use std::str::FromStr;
use revive_solidity::Process;
@@ -17,27 +16,28 @@ const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024;
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main() -> anyhow::Result<()> {
/// The application entry point.
fn main() {
std::process::exit(match main_inner() {
Ok(()) => revive_common::EXIT_CODE_SUCCESS,
Err(error) => {
writeln!(std::io::stderr(), "{error}")?;
eprintln!("{error}");
revive_common::EXIT_CODE_FAILURE
}
})
}
/// The auxiliary `main` function to facilitate the `?` error conversion operator.
fn main_inner() -> anyhow::Result<()> {
let arguments = <Arguments as clap::Parser>::try_parse()?;
let arguments = Arguments::new();
arguments.validate()?;
if arguments.version {
writeln!(
std::io::stdout(),
println!(
"{} version {}",
env!("CARGO_PKG_DESCRIPTION"),
revive_solidity::ResolcVersion::default().long
)?;
);
return Ok(());
}
@@ -45,7 +45,7 @@ fn main_inner() -> anyhow::Result<()> {
let license_mit = include_str!("../../../../LICENSE-MIT");
let license_apache = include_str!("../../../../LICENSE-APACHE");
writeln!(std::io::stdout(), "{}\n{}\n", license_mit, license_apache)?;
println!("{}\n{}\n", license_mit, license_apache);
return Ok(());
}
@@ -103,7 +103,7 @@ fn main_inner() -> anyhow::Result<()> {
let mut solc = {
#[cfg(target_os = "emscripten")]
{
revive_solidity::SoljsonCompiler
revive_solidity::SoljsonCompiler { version: None }
}
#[cfg(not(target_os = "emscripten"))]
@@ -157,6 +157,7 @@ fn main_inner() -> anyhow::Result<()> {
revive_solidity::standard_json(
&mut solc,
arguments.detect_missing_libraries,
arguments.force_evmla,
arguments.base_path,
arguments.include_paths,
arguments.allow_paths,
@@ -172,6 +173,7 @@ fn main_inner() -> anyhow::Result<()> {
evm_version,
!arguments.disable_solc_optimizer,
optimizer_settings,
arguments.force_evmla,
include_metadata_hash,
arguments.base_path,
arguments.include_paths,
@@ -191,6 +193,7 @@ fn main_inner() -> anyhow::Result<()> {
evm_version,
!arguments.disable_solc_optimizer,
optimizer_settings,
arguments.force_evmla,
include_metadata_hash,
arguments.base_path,
arguments.include_paths,
@@ -211,36 +214,26 @@ fn main_inner() -> anyhow::Result<()> {
arguments.overwrite,
)?;
writeln!(
std::io::stderr(),
eprintln!(
"Compiler run successful. Artifact(s) can be found in directory {output_directory:?}."
)?;
);
} else if arguments.output_assembly || arguments.output_binary {
for (path, contract) in build.contracts.into_iter() {
if arguments.output_assembly {
let assembly_text = contract.build.assembly_text;
writeln!(
std::io::stdout(),
"Contract `{}` assembly:\n\n{}",
path,
assembly_text
)?;
println!("Contract `{}` assembly:\n\n{}", path, assembly_text);
}
if arguments.output_binary {
writeln!(
std::io::stdout(),
println!(
"Contract `{}` bytecode: 0x{}",
path,
hex::encode(contract.build.bytecode)
)?;
);
}
}
} else {
writeln!(
std::io::stderr(),
"Compiler run successful. No output requested. Use --asm and --bin flags."
)?;
eprintln!("Compiler run successful. No output requested. Use --asm and --bin flags.");
}
Ok(())
@@ -80,9 +80,10 @@ impl CombinedJson {
file_path.push(format!("combined.{}", revive_common::EXTENSION_JSON));
if file_path.exists() && !overwrite {
anyhow::bail!(
eprintln!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
);
return Ok(());
}
File::create(&file_path)
+18 -3
View File
@@ -1,6 +1,7 @@
//! The Solidity compiler.
pub mod combined_json;
pub mod pipeline;
#[cfg(not(target_os = "emscripten"))]
pub mod solc_compiler;
#[cfg(target_os = "emscripten")]
@@ -8,22 +9,35 @@ pub mod soljson_compiler;
pub mod standard_json;
pub mod version;
use once_cell::sync::Lazy;
use semver::VersionReq;
use std::path::Path;
use std::path::PathBuf;
use self::combined_json::CombinedJson;
use self::pipeline::Pipeline;
use self::standard_json::input::Input as StandardJsonInput;
use self::standard_json::output::Output as StandardJsonOutput;
use self::version::Version;
/// The first version of `solc` with the support of standard JSON interface.
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12);
/// The first version of `solc`, where Yul codegen is considered robust enough.
pub const FIRST_YUL_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// The first version of `solc`, where `--via-ir` codegen mode is supported.
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, 28);
/// `--base-path` was introduced in 0.6.9 <https://github.com/ethereum/solidity/releases/tag/v0.6.9>
pub static FIRST_SUPPORTS_BASE_PATH: Lazy<VersionReq> =
Lazy::new(|| VersionReq::parse(">=0.6.9").unwrap());
/// `--include-path` was introduced in solc `0.8.8` <https://github.com/ethereum/solidity/releases/tag/v0.8.8>
pub const FIRST_INCLUDE_PATH_VERSION: semver::Version = semver::Version::new(0, 8, 8);
/// `--include-path` was introduced in 0.8.8 <https://github.com/ethereum/solidity/releases/tag/v0.8.8>
pub static FIRST_SUPPORTS_INCLUDE_PATH: Lazy<VersionReq> =
Lazy::new(|| VersionReq::parse(">=0.8.8").unwrap());
/// The Solidity compiler.
pub trait Compiler {
@@ -31,6 +45,7 @@ pub trait Compiler {
fn standard_json(
&mut self,
input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>,
include_paths: Vec<String>,
allow_paths: Option<String>,
+27
View File
@@ -0,0 +1,27 @@
//! The Solidity compiler pipeline type.
use serde::{Deserialize, Serialize};
use crate::solc::version::Version as SolcVersion;
/// The Solidity compiler pipeline type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum Pipeline {
/// The Yul IR.
Yul,
/// The EVM legacy assembly IR.
EVMLA,
}
impl Pipeline {
/// We always use EVMLA for Solidity <=0.7, or if the user does not want to compile via Yul.
pub fn new(solc_version: &SolcVersion, force_evmla: bool) -> Self {
if solc_version.default < crate::solc::FIRST_YUL_VERSION || force_evmla {
Self::EVMLA
} else {
Self::Yul
}
}
}
+55 -20
View File
@@ -1,20 +1,24 @@
//! The Solidity compiler solc interface.
//! The Solidity compiler.
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson;
use crate::solc::pipeline::Pipeline;
use crate::solc::standard_json::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version;
use super::Compiler;
use crate::solc::{FIRST_SUPPORTS_BASE_PATH, FIRST_SUPPORTS_INCLUDE_PATH};
/// The Solidity compiler.
pub struct SolcCompiler {
/// The binary executable name.
pub executable: String,
/// The lazily-initialized compiler version.
pub version: Option<Version>,
}
impl SolcCompiler {
@@ -31,7 +35,10 @@ impl SolcCompiler {
error
);
}
Ok(Self { executable })
Ok(Self {
executable,
version: None,
})
}
}
@@ -40,31 +47,45 @@ impl Compiler for SolcCompiler {
fn standard_json(
&mut self,
mut input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>,
include_paths: Vec<String>,
allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> {
let version = self.version()?.validate(&include_paths)?.default;
let version = self.version()?;
let mut command = std::process::Command::new(self.executable.as_str());
command.stdin(std::process::Stdio::piped());
command.stdout(std::process::Stdio::piped());
command.arg("--standard-json");
if let Some(base_path) = &base_path {
if !FIRST_SUPPORTS_BASE_PATH.matches(&version.default) {
anyhow::bail!(
"--base-path not supported this version {} of solc",
&version.default
);
}
command.arg("--base-path").arg(base_path);
}
if !include_paths.is_empty() && !FIRST_SUPPORTS_INCLUDE_PATH.matches(&version.default) {
anyhow::bail!(
"--include-path not supported this version {} of solc",
&version.default
);
}
for include_path in include_paths.into_iter() {
command.arg("--include-path");
command.arg(include_path);
}
if let Some(base_path) = base_path {
command.arg("--base-path");
command.arg(base_path);
}
if let Some(allow_paths) = allow_paths {
command.arg("--allow-paths");
command.arg(allow_paths);
}
input.normalize(&version);
input.normalize(&version.default);
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
@@ -108,7 +129,7 @@ impl Compiler for SolcCompiler {
),
)
})?;
output.preprocess_ast(suppressed_warnings.as_slice())?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?;
Ok(output)
}
@@ -142,16 +163,8 @@ impl Compiler for SolcCompiler {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?;
if !output.status.success() {
writeln!(
std::io::stdout(),
"{}",
String::from_utf8_lossy(output.stdout.as_slice())
)?;
writeln!(
std::io::stdout(),
"{}",
String::from_utf8_lossy(output.stderr.as_slice())
)?;
println!("{}", String::from_utf8_lossy(output.stdout.as_slice()));
println!("{}", String::from_utf8_lossy(output.stderr.as_slice()));
anyhow::bail!(
"{} error: {}",
self.executable,
@@ -215,6 +228,10 @@ impl Compiler for SolcCompiler {
/// The `solc --version` mini-parser.
fn version(&mut self) -> anyhow::Result<Version> {
if let Some(version) = self.version.as_ref() {
return Ok(version.to_owned());
}
let mut command = std::process::Command::new(self.executable.as_str());
command.arg("--version");
let output = command.output().map_err(|error| {
@@ -260,6 +277,24 @@ impl Compiler for SolcCompiler {
.and_then(|line| line.split('-').nth(1))
.and_then(|version| version.parse().ok());
Ok(Version::new(long, default, l2_revision))
let version = Version::new(long, default, l2_revision);
if version.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
version.default
);
}
if version.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
version.default
);
}
self.version = Some(version.clone());
Ok(version)
}
}
+34 -20
View File
@@ -1,9 +1,10 @@
//! The Solidity compiler solJson interface.
//! The Solidity compiler.
use std::path::Path;
use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson;
use crate::solc::pipeline::Pipeline;
use crate::solc::standard_json::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version;
@@ -18,30 +19,23 @@ extern "C" {
}
/// The Solidity compiler.
pub struct SoljsonCompiler;
pub struct SoljsonCompiler {
/// The lazily-initialized compiler version.
pub version: Option<Version>,
}
impl Compiler for SoljsonCompiler {
/// Compiles the Solidity `--standard-json` input into Yul IR.
fn standard_json(
&mut self,
mut input: StandardJsonInput,
base_path: Option<String>,
include_paths: Vec<String>,
allow_paths: Option<String>,
pipeline: Pipeline,
_base_path: Option<String>,
_include_paths: Vec<String>,
_allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> {
if !include_paths.is_empty() {
anyhow::bail!("configuring include paths is not supported with solJson")
}
if base_path.is_some() {
anyhow::bail!("configuring the base path is not supported with solJson")
}
if allow_paths.is_some() {
anyhow::bail!("configuring allow paths is not supported with solJson")
}
let version = self.version()?.validate(&include_paths)?.default;
input.normalize(&version);
let version = self.version()?;
input.normalize(&version.default);
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
let input_json = serde_json::to_string(&input).expect("Always valid");
@@ -56,7 +50,7 @@ impl Compiler for SoljsonCompiler {
.unwrap_or_else(|_| String::from_utf8_lossy(out.as_bytes()).to_string()),
)
})?;
output.preprocess_ast(suppressed_warnings.as_slice())?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?;
Ok(output)
}
@@ -82,11 +76,31 @@ impl Compiler for SoljsonCompiler {
.ok_or_else(|| anyhow::anyhow!("Soljson version parsing: metadata dropping"))?
.parse()
.map_err(|error| anyhow::anyhow!("Soljson version parsing: {}", error))?;
let l2_revision: Option<semver::Version> = version
.split('-')
.nth(1)
.and_then(|version| version.parse().ok());
Ok(Version::new(long, default, l2_revision))
let version = Version::new(long, default, l2_revision);
if version.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`Soljson` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
version.default
);
}
if version.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`Soljson` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
version.default
);
}
self.version = Some(version.clone());
Ok(version)
}
}
@@ -13,6 +13,7 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::standard_json::input::settings::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
@@ -39,13 +40,13 @@ pub struct Input {
impl Input {
/// A shortcut constructor from stdin.
pub fn try_from_stdin() -> anyhow::Result<Self> {
pub fn try_from_stdin(solc_pipeline: SolcPipeline) -> anyhow::Result<Self> {
let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?;
input
.settings
.output_selection
.get_or_insert_with(SolcStandardJsonInputSettingsSelection::default)
.extend_with_required();
.extend_with_required(solc_pipeline);
Ok(input)
}
@@ -60,16 +61,15 @@ impl Input {
output_selection: SolcStandardJsonInputSettingsSelection,
optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> {
let mut paths: BTreeSet<PathBuf> = paths.iter().cloned().collect();
let libraries = Settings::parse_libraries(library_map)?;
for library_file in libraries.keys() {
paths.insert(PathBuf::from(library_file));
}
#[cfg(feature = "parallel")]
let iter = paths.into_par_iter(); // Parallel iterator
let sources = paths
.iter()
#[cfg(not(feature = "parallel"))]
let iter = paths.iter(); // Sequential iterator
let sources = iter
.map(|path| {
let source = Source::try_from(path.as_path()).unwrap_or_else(|error| {
panic!("Source code file {path:?} reading error: {error}")
@@ -78,6 +78,8 @@ impl Input {
})
.collect();
let libraries = Settings::parse_libraries(library_map)?;
Ok(Self {
language,
sources,
@@ -86,6 +88,7 @@ impl Input {
libraries,
remappings,
output_selection,
via_ir,
optimizer,
metadata,
),
@@ -104,6 +107,7 @@ impl Input {
output_selection: SolcStandardJsonInputSettingsSelection,
optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>,
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> {
#[cfg(feature = "parallel")]
@@ -123,6 +127,7 @@ impl Input {
libraries,
remappings,
output_selection,
via_ir,
optimizer,
metadata,
),
@@ -51,6 +51,7 @@ impl Settings {
libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>,
output_selection: Selection,
via_ir: bool,
optimizer: Optimizer,
metadata: Option<Metadata>,
) -> Self {
@@ -59,9 +60,9 @@ impl Settings {
libraries: Some(libraries),
remappings,
output_selection: Some(output_selection),
via_ir: if via_ir { Some(true) } else { None },
optimizer,
metadata,
via_ir: Some(true),
}
}
@@ -3,8 +3,12 @@
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
/// The `solc --standard-json` expected output selection flag.
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum Flag {
/// The ABI JSON.
#[serde(rename = "abi")]
@@ -42,6 +46,15 @@ pub enum Flag {
Assembly,
}
impl From<SolcPipeline> for Flag {
fn from(pipeline: SolcPipeline) -> Self {
match pipeline {
SolcPipeline::Yul => Self::Yul,
SolcPipeline::EVMLA => Self::EVMLA,
}
}
}
impl std::fmt::Display for Flag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -7,6 +7,8 @@ use std::collections::HashSet;
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use self::flag::Flag as SelectionFlag;
/// The `solc --standard-json` output file selection.
@@ -22,7 +24,7 @@ pub struct File {
impl File {
/// Creates the selection required by our compilation process.
pub fn new_required() -> Self {
pub fn new_required(pipeline: SolcPipeline) -> Self {
Self {
per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
per_contract: Some(HashSet::from_iter([
@@ -30,14 +32,14 @@ impl File {
SelectionFlag::EVMDBC,
SelectionFlag::MethodIdentifiers,
SelectionFlag::Metadata,
SelectionFlag::Yul,
SelectionFlag::from(pipeline),
])),
}
}
/// Extends the user's output selection with flag required by our compilation process.
pub fn extend_with_required(&mut self) -> &mut Self {
let required = Self::new_required();
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self {
let required = Self::new_required(pipeline);
self.per_file
.get_or_insert_with(HashSet::default)
@@ -5,6 +5,8 @@ pub mod file;
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use self::file::File as FileSelection;
/// The `solc --standard-json` output selection.
@@ -17,17 +19,17 @@ pub struct Selection {
impl Selection {
/// Creates the selection required by our compilation process.
pub fn new_required() -> Self {
pub fn new_required(pipeline: SolcPipeline) -> Self {
Self {
all: Some(FileSelection::new_required()),
all: Some(FileSelection::new_required(pipeline)),
}
}
/// Extends the user's output selection with flag required by our compilation process.
pub fn extend_with_required(&mut self) -> &mut Self {
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self {
self.all
.get_or_insert_with(FileSelection::new_required)
.extend_with_required();
.get_or_insert_with(|| FileSelection::new_required(pipeline))
.extend_with_required(pipeline);
self
}
}
@@ -0,0 +1,46 @@
//! The `solc --standard-json` output contract EVM extra metadata.
pub mod recursive_function;
use serde::Deserialize;
use serde::Serialize;
use self::recursive_function::RecursiveFunction;
/// The `solc --standard-json` output contract EVM extra metadata.
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExtraMetadata {
/// The list of recursive functions.
#[serde(default = "Vec::new")]
pub recursive_functions: Vec<RecursiveFunction>,
}
impl ExtraMetadata {
/// Returns the recursive function reference for the specified tag.
pub fn get(
&self,
block_key: &revive_llvm_context::PolkaVMFunctionBlockKey,
) -> Option<&RecursiveFunction> {
for function in self.recursive_functions.iter() {
match block_key.code_type {
revive_llvm_context::PolkaVMCodeType::Deploy => {
if let Some(creation_tag) = function.creation_tag {
if num::BigUint::from(creation_tag) == block_key.tag {
return Some(function);
}
}
}
revive_llvm_context::PolkaVMCodeType::Runtime => {
if let Some(runtime_tag) = function.runtime_tag {
if num::BigUint::from(runtime_tag) == block_key.tag {
return Some(function);
}
}
}
}
}
None
}
}
@@ -0,0 +1,22 @@
//! The `solc --standard-json` output contract EVM recursive function.
use serde::Deserialize;
use serde::Serialize;
/// The `solc --standard-json` output contract EVM recursive function.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RecursiveFunction {
/// The function name.
pub name: String,
/// The creation code function block tag.
pub creation_tag: Option<usize>,
/// The runtime code function block tag.
pub runtime_tag: Option<usize>,
/// The number of input arguments.
#[serde(rename = "totalParamSize")]
pub input_size: usize,
/// The number of output arguments.
#[serde(rename = "totalRetParamSize")]
pub output_size: usize,
}
@@ -1,20 +1,27 @@
//! The `solc --standard-json` output contract EVM data.
pub mod bytecode;
pub mod extra_metadata;
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
use crate::evmla::assembly::Assembly;
use self::bytecode::Bytecode;
use self::bytecode::DeployedBytecode;
use self::extra_metadata::ExtraMetadata;
/// The `solc --standard-json` output contract EVM data.
/// It is replaced by PolkaVM data after compiling.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct EVM {
/// The contract EVM legacy assembly code.
#[serde(rename = "legacyAssembly", skip_serializing_if = "Option::is_none")]
pub assembly: Option<Assembly>,
/// The contract PolkaVM assembly code.
#[serde(rename = "assembly", skip_serializing_if = "Option::is_none")]
pub assembly_text: Option<String>,
@@ -30,6 +37,9 @@ pub struct EVM {
/// The contract function signatures.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub method_identifiers: Option<BTreeMap<String, String>>,
/// The extra EVMLA metadata.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra_metadata: Option<ExtraMetadata>,
}
impl EVM {
@@ -33,12 +33,13 @@ impl Error {
/// Returns the `ecrecover` function usage warning.
pub fn message_ecrecover(src: Option<&str>) -> Self {
let message = r#"
Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.
Polkadot comes with native account abstraction support, therefore it is highly recommended NOT
to rely on the fact that the account has an ECDSA private key attached to it since accounts might
implement other signature schemes.
"#
.to_owned();
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Warning: It looks like you are using 'ecrecover' to validate a signature of a user account. │
│ Polkadot comes with native account abstraction support, therefore it is highly recommended NOT │
│ to rely on the fact that the account has an ECDSA private key attached to it since accounts might│
│ implement other signature schemes. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned();
Self {
component: "general".to_owned(),
@@ -54,16 +55,17 @@ implement other signature schemes.
/// Returns the `<address payable>`'s `send` and `transfer` methods usage error.
pub fn message_send_and_transfer(src: Option<&str>) -> Self {
let message = r#"
Warning: It looks like you are using '<address payable>.send/transfer(<X>)'.
Using '<address payable>.send/transfer(<X>)' is deprecated and strongly discouraged!
The resolc compiler uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls,
which disables call re-entrancy and supplies all remaining gas instead of the 2300 gas stipend.
However, detection is not guaranteed. You are advised to carefully test this, employ
re-entrancy guards or use the withdrawal pattern instead!
Learn more on https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy
and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts
"#
.to_owned();
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing │
│ the gas amount. Such calls will fail depending on the pubdata costs. │
│ This might be a false positive if you are using an interface (like IERC20) instead of the │
│ native Solidity `send/transfer`. │
│ Please use 'payable(<address>).call{value: <X>}("")' instead, but be careful with the reentrancy │
│ attack. `send` and `transfer` send limited amount of gas that prevents reentrancy, whereas │
│ `<address>.call{value: <X>}` sends all gas to the callee. Learn more on │
│ https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned();
Self {
component: "general".to_owned(),
@@ -79,15 +81,16 @@ and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from
/// Returns the `extcodesize` instruction usage warning.
pub fn message_extcodesize(src: Option<&str>) -> Self {
let message = r#"
Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is
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.
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();
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is │
│ 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. │
│ 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();
Self {
component: "general".to_owned(),
@@ -103,12 +106,13 @@ addresses.
/// Returns the `origin` instruction usage warning.
pub fn message_tx_origin(src: Option<&str>) -> Self {
let message = r#"
Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior.
Polkadot comes with native account abstraction support, and therefore the initiator of a
transaction might be different from the contract calling your code. It is highly recommended NOT
to rely on tx.origin, but use msg.sender instead.
"#
.to_owned();
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior. │
│ Polkadot comes with native account abstraction support, and therefore the initiator of a │
│ transaction might be different from the contract calling your code. It is highly recommended NOT │
│ to rely on tx.origin, but use msg.sender instead. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned();
Self {
component: "general".to_owned(),
@@ -121,6 +125,26 @@ to rely on tx.origin, but use msg.sender instead.
}
}
/// Returns the internal function pointer usage error.
pub fn message_internal_function_pointer(src: Option<&str>) -> Self {
let message = r#"
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Error: Internal function pointers are not supported in EVM legacy assembly pipeline. │
│ Please use the Yul IR codegen instead. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘"#
.to_owned();
Self {
component: "general".to_owned(),
error_code: None,
formatted_message: message.clone(),
message,
severity: "error".to_owned(),
source_location: src.map(SourceLocation::from_str).and_then(Result::ok),
r#type: "Error".to_owned(),
}
}
/// Appends the contract path to the message..
pub fn push_contract_path(&mut self, path: &str) {
self.formatted_message
@@ -10,9 +10,12 @@ use serde::Deserialize;
use serde::Serialize;
use sha3::Digest;
use crate::evmla::assembly::instruction::Instruction;
use crate::evmla::assembly::Assembly;
use crate::project::contract::ir::IR as ProjectContractIR;
use crate::project::contract::Contract as ProjectContract;
use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::version::Version as SolcVersion;
use crate::warning::Warning;
use crate::yul::lexer::Lexer;
@@ -50,9 +53,14 @@ impl Output {
&mut self,
source_code_files: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
solc_version: &SolcVersion,
debug_config: &revive_llvm_context::DebugConfig,
) -> anyhow::Result<Project> {
if let SolcPipeline::EVMLA = pipeline {
self.preprocess_dependencies()?;
}
let files = match self.contracts.as_ref() {
Some(files) => files,
None => match &self.errors {
@@ -68,22 +76,38 @@ impl Output {
for (name, contract) in contracts.iter() {
let full_path = format!("{path}:{name}");
let ir_optimized = match contract.ir_optimized.to_owned() {
Some(ir_optimized) => ir_optimized,
None => continue,
let source = match pipeline {
SolcPipeline::Yul => {
let ir_optimized = match contract.ir_optimized.to_owned() {
Some(ir_optimized) => ir_optimized,
None => continue,
};
if ir_optimized.is_empty() {
continue;
}
debug_config.dump_yul(full_path.as_str(), ir_optimized.as_str())?;
let mut lexer = Lexer::new(ir_optimized.to_owned());
let object = Object::parse(&mut lexer, None).map_err(|error| {
anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error)
})?;
ProjectContractIR::new_yul(ir_optimized.to_owned(), object)
}
SolcPipeline::EVMLA => {
let evm = contract.evm.as_ref();
let assembly = match evm.and_then(|evm| evm.assembly.to_owned()) {
Some(assembly) => assembly.to_owned(),
None => continue,
};
let extra_metadata = evm
.and_then(|evm| evm.extra_metadata.to_owned())
.unwrap_or_default();
ProjectContractIR::new_evmla(assembly, extra_metadata)
}
};
if ir_optimized.is_empty() {
continue;
}
debug_config.dump_yul(full_path.as_str(), ir_optimized.as_str())?;
let mut lexer = Lexer::new(ir_optimized.to_owned());
let object = Object::parse(&mut lexer, None).map_err(|error| {
anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error)
})?;
let source = ProjectContractIR::new_yul(ir_optimized.to_owned(), object);
let source_code = source_code_files
.get(path.as_str())
@@ -109,7 +133,12 @@ impl Output {
}
/// Traverses the AST and returns the list of additional errors and warnings.
pub fn preprocess_ast(&mut self, suppressed_warnings: &[Warning]) -> anyhow::Result<()> {
pub fn preprocess_ast(
&mut self,
version: &SolcVersion,
pipeline: SolcPipeline,
suppressed_warnings: &[Warning],
) -> anyhow::Result<()> {
let sources = match self.sources.as_ref() {
Some(sources) => sources,
None => return Ok(()),
@@ -118,7 +147,8 @@ impl Output {
let mut messages = Vec::new();
for (path, source) in sources.iter() {
if let Some(ast) = source.ast.as_ref() {
let mut polkavm_messages = Source::get_messages(ast, suppressed_warnings);
let mut polkavm_messages =
Source::get_messages(ast, version, pipeline, suppressed_warnings);
for message in polkavm_messages.iter_mut() {
message.push_contract_path(path.as_str());
}
@@ -135,4 +165,83 @@ impl Output {
Ok(())
}
/// The pass, which replaces with dependency indexes with actual data.
fn preprocess_dependencies(&mut self) -> anyhow::Result<()> {
let files = match self.contracts.as_mut() {
Some(files) => files,
None => return Ok(()),
};
let mut hash_path_mapping = BTreeMap::new();
for (path, contracts) in files.iter() {
for (name, contract) in contracts.iter() {
let full_path = format!("{path}:{name}");
let hash = match contract
.evm
.as_ref()
.and_then(|evm| evm.assembly.as_ref())
.map(|assembly| assembly.keccak256())
{
Some(hash) => hash,
None => continue,
};
hash_path_mapping.insert(hash, full_path);
}
}
for (path, contracts) in files.iter_mut() {
for (name, contract) in contracts.iter_mut() {
let assembly = match contract.evm.as_mut().and_then(|evm| evm.assembly.as_mut()) {
Some(assembly) => assembly,
None => continue,
};
let full_path = format!("{path}:{name}");
Self::preprocess_dependency_level(
full_path.as_str(),
assembly,
&hash_path_mapping,
)?;
}
}
Ok(())
}
/// Preprocesses an assembly JSON structure dependency data map.
fn preprocess_dependency_level(
full_path: &str,
assembly: &mut Assembly,
hash_path_mapping: &BTreeMap<String, String>,
) -> anyhow::Result<()> {
assembly.set_full_path(full_path.to_owned());
let deploy_code_index_path_mapping =
assembly.deploy_dependencies_pass(full_path, hash_path_mapping)?;
if let Some(deploy_code_instructions) = assembly.code.as_deref_mut() {
Instruction::replace_data_aliases(
deploy_code_instructions,
&deploy_code_index_path_mapping,
)?;
};
let runtime_code_index_path_mapping =
assembly.runtime_dependencies_pass(full_path, hash_path_mapping)?;
if let Some(runtime_code_instructions) = assembly
.data
.as_mut()
.and_then(|data_map| data_map.get_mut("0"))
.and_then(|data| data.get_assembly_mut())
.and_then(|assembly| assembly.code.as_deref_mut())
{
Instruction::replace_data_aliases(
runtime_code_instructions,
&runtime_code_index_path_mapping,
)?;
}
Ok(())
}
}
@@ -3,7 +3,9 @@
use serde::Deserialize;
use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError;
use crate::solc::version::Version as SolcVersion;
use crate::warning::Warning;
/// The `solc --standard-json` output source.
@@ -130,9 +132,37 @@ impl Source {
))
}
/// Checks the AST node for the internal function pointers value usage.
pub fn check_internal_function_pointer(
ast: &serde_json::Value,
) -> Option<SolcStandardJsonOutputError> {
let ast = ast.as_object()?;
if ast.get("nodeType")?.as_str()? != "VariableDeclaration" {
return None;
}
let type_descriptions = ast.get("typeDescriptions")?.as_object()?;
if !type_descriptions
.get("typeIdentifier")?
.as_str()?
.contains("function_internal")
{
return None;
}
Some(
SolcStandardJsonOutputError::message_internal_function_pointer(
ast.get("src")?.as_str(),
),
)
}
/// Returns the list of messages for some specific parts of the AST.
pub fn get_messages(
ast: &serde_json::Value,
version: &SolcVersion,
pipeline: SolcPipeline,
suppressed_warnings: &[Warning],
) -> Vec<SolcStandardJsonOutputError> {
let mut messages = Vec::new();
@@ -159,16 +189,31 @@ impl Source {
messages.push(message);
}
}
if SolcPipeline::EVMLA == pipeline && version.l2_revision.is_none() {
if let Some(message) = Self::check_internal_function_pointer(ast) {
messages.push(message);
}
}
match ast {
serde_json::Value::Array(array) => {
for element in array.iter() {
messages.extend(Self::get_messages(element, suppressed_warnings));
messages.extend(Self::get_messages(
element,
version,
pipeline,
suppressed_warnings,
));
}
}
serde_json::Value::Object(object) => {
for (_key, value) in object.iter() {
messages.extend(Self::get_messages(value, suppressed_warnings));
messages.extend(Self::get_messages(
value,
version,
pipeline,
suppressed_warnings,
));
}
}
_ => {}
-22
View File
@@ -36,26 +36,4 @@ impl Version {
l2_revision: None,
}
}
pub fn validate(self, include_paths: &[String]) -> anyhow::Result<Self> {
if self.default < super::FIRST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions <{} are not supported, found {}",
super::FIRST_SUPPORTED_VERSION,
self.default
);
}
if self.default > super::LAST_SUPPORTED_VERSION {
anyhow::bail!(
"`solc` versions >{} are not supported, found {}",
super::LAST_SUPPORTED_VERSION,
self.default
);
}
if !include_paths.is_empty() && self.default < super::FIRST_INCLUDE_PATH_VERSION {
anyhow::bail!("--include-path is not supported in solc {}", self.default);
}
Ok(self)
}
}
+38 -15
View File
@@ -8,6 +8,7 @@ use std::sync::Mutex;
use once_cell::sync::Lazy;
use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::solc_compiler::SolcCompiler;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
@@ -29,8 +30,8 @@ const DEBUG_CONFIG: revive_llvm_context::DebugConfig =
#[derive(Hash, PartialEq, Eq)]
struct CachedBlob {
contract_name: String,
solidity: String,
solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
}
/// Checks if the required executables are present in `${PATH}`.
@@ -53,9 +54,17 @@ pub fn build_solidity(
sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
optimizer_settings: revive_llvm_context::OptimizerSettings,
) -> anyhow::Result<SolcStandardJsonOutput> {
build_solidity_with_options(sources, libraries, remappings, optimizer_settings, true)
build_solidity_with_options(
sources,
libraries,
remappings,
pipeline,
optimizer_settings,
true,
)
}
/// Builds the Solidity project and returns the standard JSON output.
@@ -65,6 +74,7 @@ pub fn build_solidity_with_options(
sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
optimizer_settings: revive_llvm_context::OptimizerSettings,
solc_optimizer_enabled: bool,
) -> anyhow::Result<SolcStandardJsonOutput> {
@@ -83,7 +93,7 @@ pub fn build_solidity_with_options(
sources.clone(),
libraries.clone(),
remappings,
SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled,
None,
@@ -91,12 +101,14 @@ pub fn build_solidity_with_options(
false,
),
None,
pipeline == SolcPipeline::Yul,
None,
)?;
let mut output = solc.standard_json(input, None, vec![], None)?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
let project = output.try_to_project(sources, libraries, &solc_version, &DEBUG_CONFIG)?;
let project =
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let build: crate::Build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?;
build.write_to_standard_json(&mut output, &solc_version)?;
@@ -109,6 +121,7 @@ pub fn build_solidity_with_options_evm(
sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
solc_optimizer_enabled: bool,
) -> anyhow::Result<BTreeMap<String, (Bytecode, DeployedBytecode)>> {
check_dependencies();
@@ -126,7 +139,7 @@ pub fn build_solidity_with_options_evm(
sources.clone(),
libraries.clone(),
remappings,
SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled,
None,
@@ -134,10 +147,11 @@ pub fn build_solidity_with_options_evm(
false,
),
None,
pipeline == SolcPipeline::Yul,
None,
)?;
let mut output = solc.standard_json(input, None, vec![], None)?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
let mut contracts = BTreeMap::new();
if let Some(files) = output.contracts.as_mut() {
@@ -162,6 +176,7 @@ pub fn build_solidity_with_options_evm(
pub fn build_solidity_and_detect_missing_libraries(
sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
) -> anyhow::Result<SolcStandardJsonOutput> {
check_dependencies();
@@ -178,15 +193,17 @@ pub fn build_solidity_and_detect_missing_libraries(
sources.clone(),
libraries.clone(),
None,
SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None,
pipeline == SolcPipeline::Yul,
None,
)?;
let mut output = solc.standard_json(input, None, vec![], None)?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?;
let project = output.try_to_project(sources, libraries, &solc_version, &DEBUG_CONFIG)?;
let project =
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let missing_libraries = project.get_missing_libraries();
missing_libraries.write_to_standard_json(&mut output, &solc.version()?)?;
@@ -217,6 +234,7 @@ pub fn check_solidity_warning(
source_code: &str,
warning_substring: &str,
libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
skip_for_revive_edition: bool,
suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<bool> {
@@ -235,13 +253,14 @@ pub fn check_solidity_warning(
sources.clone(),
libraries,
None,
SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsSelection::new_required(pipeline),
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None,
pipeline == SolcPipeline::Yul,
suppressed_warnings,
)?;
let output = solc.standard_json(input, None, vec![], None)?;
let output = solc.standard_json(input, pipeline, None, vec![], None)?;
let contains_warning = output
.errors
.ok_or_else(|| anyhow::anyhow!("Solidity compiler messages not found"))?
@@ -254,7 +273,7 @@ pub fn check_solidity_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)
compile_blob_with_options(contract_name, source_code, true, SolcPipeline::Yul)
}
/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`.
@@ -279,9 +298,10 @@ fn compile_evm(
solc_optimizer_enabled: bool,
runtime: bool,
) -> Vec<u8> {
let pipeline = SolcPipeline::Yul;
let id = CachedBlob {
contract_name: contract_name.to_owned(),
solidity: source_code.to_owned(),
pipeline,
solc_optimizer_enabled,
};
@@ -299,6 +319,7 @@ fn compile_evm(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
pipeline,
solc_optimizer_enabled,
)
.expect("source should compile");
@@ -322,11 +343,12 @@ 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(),
solidity: source_code.to_owned(),
solc_optimizer_enabled,
pipeline,
};
if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) {
@@ -338,6 +360,7 @@ pub fn compile_blob_with_options(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
pipeline,
revive_llvm_context::OptimizerSettings::cycles(),
solc_optimizer_enabled,
)
@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="jest tests" tests="27" failures="0" errors="0" time="2.146">
<testsuite name="Run with --yul by default" errors="0" failures="0" skipped="1" timestamp="2024-10-24T17:08:50" time="1.508" tests="6">
<testcase classname="Run with --yul by default Valid command exit code = 0" name="Run with --yul by default Valid command exit code = 0" time="0.003">
</testcase>
<testcase classname="Run with --yul by default --yul output is presented" name="Run with --yul by default --yul output is presented" time="0">
</testcase>
<testcase classname="Run with --yul by default solc exit code == resolc exit code" name="Run with --yul by default solc exit code == resolc exit code" time="0">
<skipped/>
</testcase>
<testcase classname="Run with --yul by default run invalid: resolc --yul" name="Run with --yul by default run invalid: resolc --yul" time="0.001">
</testcase>
<testcase classname="Run with --yul by default Invalid command exit code = 1" name="Run with --yul by default Invalid command exit code = 1" time="0">
</testcase>
<testcase classname="Run with --yul by default Invalid solc exit code == Invalid resolc exit code" name="Run with --yul by default Invalid solc exit code == Invalid resolc exit code" time="0.041">
</testcase>
</testsuite>
<testsuite name="Run with --asm by default" errors="0" failures="0" skipped="0" timestamp="2024-10-24T17:08:50" time="1.512" tests="6">
<testcase classname="Run with --asm by default Valid command exit code = 0" name="Run with --asm by default Valid command exit code = 0" time="0.002">
</testcase>
<testcase classname="Run with --asm by default --asm output is presented" name="Run with --asm by default --asm output is presented" time="0.001">
</testcase>
<testcase classname="Run with --asm by default solc exit code == resolc exit code" name="Run with --asm by default solc exit code == resolc exit code" time="0.044">
</testcase>
<testcase classname="Run with --asm by default run invalid: resolc --asm" name="Run with --asm by default run invalid: resolc --asm" time="0">
</testcase>
<testcase classname="Run with --asm by default Invalid command exit code = 1" name="Run with --asm by default Invalid command exit code = 1" time="0.001">
</testcase>
<testcase classname="Run with --asm by default Invalid solc exit code == Invalid resolc exit code" name="Run with --asm by default Invalid solc exit code == Invalid resolc exit code" time="0.04">
</testcase>
</testsuite>
<testsuite name="Run resolc without any options" errors="0" failures="0" skipped="2" timestamp="2024-10-24T17:08:50" time="2.016" tests="15">
<testcase classname="Run resolc without any options Info with help is presented" name="Run resolc without any options Info with help is presented" time="0.002">
</testcase>
<testcase classname="Run resolc without any options Exit code = 1" name="Run resolc without any options Exit code = 1" time="0">
</testcase>
<testcase classname="Run resolc without any options solc exit code == resolc exit code" name="Run resolc without any options solc exit code == resolc exit code" time="0.044">
</testcase>
<testcase classname="Default run a command from the help Compiler run successful" name="Default run a command from the help Compiler run successful" time="0">
</testcase>
<testcase classname="Default run a command from the help Exit code = 0" name="Default run a command from the help Exit code = 0" time="0.001">
</testcase>
<testcase classname="Default run a command from the help Output dir is created" name="Default run a command from the help Output dir is created" time="0">
</testcase>
<testcase classname="Default run a command from the help Output file is created" name="Default run a command from the help Output file is created" time="0">
<skipped/>
</testcase>
<testcase classname="Default run a command from the help the output file is not empty" name="Default run a command from the help the output file is not empty" time="0">
</testcase>
<testcase classname="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" name="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" time="0">
</testcase>
<testcase classname="Default run a command from the help Compiler run successful" name="Default run a command from the help Compiler run successful" time="0.001">
</testcase>
<testcase classname="Default run a command from the help Exit code = 0" name="Default run a command from the help Exit code = 0" time="0">
</testcase>
<testcase classname="Default run a command from the help Output dir is created" name="Default run a command from the help Output dir is created" time="0">
</testcase>
<testcase classname="Default run a command from the help Output files are created" name="Default run a command from the help Output files are created" time="0">
<skipped/>
</testcase>
<testcase classname="Default run a command from the help the output files are not empty" name="Default run a command from the help the output files are not empty" time="0.003">
</testcase>
<testcase classname="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" name="Default run a command from the help No &apos;Error&apos;/&apos;Warning&apos;/&apos;Fail&apos; in the output" time="0">
</testcase>
</testsuite>
</testsuites>
@@ -16,11 +16,11 @@
],
"license": "MIT",
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/jest": "^29.5.11",
"@types/shelljs": "^0.8.15",
"jest": "^29.7.0",
"shelljs": "^0.8.5",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3"
"ts-jest": "^29.1.1",
"typescript": "^5.3.3"
}
}
@@ -10,7 +10,7 @@ describe("Run resolc without any options", () => {
const result = executeCommand(command);
it("Info with help is presented", () => {
expect(result.output).toMatch(/(Usage: resolc)/i);
expect(result.output).toMatch(/(No input sources specified|Error(s) found.)/i);
});
it("Exit code = 1", () => {
@@ -28,7 +28,7 @@ describe("Run resolc without any options", () => {
//#1713
describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command);
it("Compiler run successful", () => {
@@ -54,7 +54,7 @@ describe("Default run a command from the help", () => {
//#1818
describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command);
it("Compiler run successful", () => {
@@ -81,8 +81,8 @@ describe("Default run a command from the help", () => {
describe("Run resolc with source debug information", () => {
const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`
`resolc -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
@@ -114,8 +114,8 @@ describe("Run resolc with source debug information", () => {
describe("Run resolc with source debug information, check LLVM debug-info", () => {
const commands = [
`resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`
`resolc -g ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) {
@@ -1,153 +0,0 @@
import { executeCommand } from "../src/helper";
import { paths } from '../src/entities';
describe("Set of --combined-json tests", () => {
const zksolcCommand = 'zksolc';
const solcCommand = 'solc';
const json_args: string[] = [`abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`];
//id1742:I
describe(`Run ${zksolcCommand} with just --combined-json`, () => {
const args = [`--combined-json`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
//id1742:II
describe(`Run ${zksolcCommand} with Sol contract and --combined-json`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(requires a value but none was supplied)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
//id1742:III
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and ARG: ${json_args[i]}`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 0", () => {
expect(result.exitCode).toBe(0);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(contracts)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1829:I
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and wrong ARG: --${json_args[i]}`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `--${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(Invalid option|error)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1829:II
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, --combined-json and multiple ARG: ${json_args[i]} ${json_args[i]}`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
xit("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(No such file or directory|cannot find the file specified)/i); // Hopefully we should have more precise message here!
});
xit("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1829:III
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Sol, and multiple (--combined-json ${json_args[i]})`, () => {
const args = [`${paths.pathToBasicSolContract}`, `--combined-json`, `${json_args[i]}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(cannot be used multiple times)/i);
});
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
//id1830
for (let i = 0; i < json_args.length; i++) {
describe(`Run ${zksolcCommand} with Yul, and --combined-json ${json_args[i]}`, () => {
const args = [`${paths.pathToBasicYulContract}`, `--combined-json`, `${json_args[i]}`];
const result = executeCommand(zksolcCommand, args);
it("Valid command exit code = 1", () => {
expect(result.exitCode).toBe(1);
});
it("--combined-json error is presented", () => {
expect(result.output).toMatch(/(ParserError: Expected identifier)/i);
});
asd
it("solc exit code == zksolc exit code", () => {
const solcResult = executeCommand(solcCommand, args);
expect(solcResult.exitCode).toBe(result.exitCode);
});
});
}
});
@@ -4,6 +4,8 @@
use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const MAIN_CODE: &str = r#"
// SPDX-License-Identifier: MIT
@@ -49,6 +51,7 @@ fn default() {
sources,
BTreeMap::new(),
None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Build failure");

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