Compare commits

...

23 Commits

Author SHA1 Message Date
xermicus 6c2c633651 release resolc v0.1.0-dev.11 (#214)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-18 08:08:20 +01:00
xermicus a73b0925c6 ci: do not run bun tests (#215) 2025-02-17 17:04:53 +01:00
xermicus ee650cf03a ci: use upstream evm for tests (#213)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-17 11:33:30 +01:00
Evgeny Snitko c2210442b6 Release workflow (#201)
Co-authored-by: xermicus <cyrill@parity.io>
2025-02-17 11:09:10 +01:00
xermicus cb268850a9 llvm-context: remove the linear memory pointer indirection (#211) 2025-02-13 13:21:43 +01:00
xermicus f3a86588f3 update dependencies (#210)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-12 13:12:09 +01:00
xermicus 7233738f45 release resolc v0.1.0-dev.10 (#209)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-11 15:51:52 +01:00
Sebastian Miasojed 79ec4dd04b Add all resolc dependencies to resolc_web.js file (#176) 2025-02-11 11:55:24 +01:00
xermicus 374563bbe5 integration: add function pointer integration test (#205)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-10 18:50:12 +01:00
xermicus a921e425b4 CI: fix and pin geth (#206) 2025-02-10 17:39:44 +01:00
xermicus 60fc09f787 llvm-context: disable call re-entrancy for send and transfer (#196) 2025-02-06 16:49:50 +01:00
Sebastian Miasojed 10b8ff989c Align emscripten compilation options (#180) 2025-02-06 00:37:10 +01:00
xermicus 157647d5c1 remove superfluous warning and clippy allows (#194)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-05 12:44:29 +01:00
xermicus 35419e8202 solidity: update custom warnings and version validation (#193) 2025-02-05 11:42:35 +01:00
xermicus de3e7bf253 integration: add create2 test case with duplicate salt (#188)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-04 14:04:14 +01:00
xermicus 9fb24b607d disable werror (#191)
Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-02-04 12:43:56 +01:00
xermicus ba7310fdff integration: bugfix blob cache (#192)
Signed-off-by: xermicus <cyrill@parity.io>
2025-02-04 12:11:25 +01:00
dependabot[bot] 4ef495beea Bump openssl from 0.10.68 to 0.10.70 (#190)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.68 to 0.10.70.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.68...openssl-v0.10.70)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-04 07:28:29 +01:00
xermicus 8ed689e7ec solidity: various small resolc fixes (#189) 2025-02-03 16:16:54 +01:00
xermicus bfda465c32 remove support for legacy evm assembly (#186) 2025-02-03 14:13:43 +01:00
xermicus ab90af49df Solidity: add --libraries to sources (#187)
* bugfix: add libraries to sources

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
2025-01-31 18:08:11 +01:00
Sebastian Miasojed 8201401fef Fix stack overflow issue (#184) 2025-01-31 14:31:34 +01:00
xermicus 1a8a7926e9 implement the coinbase opcode (#179)
Signed-off-by: xermicus <cyrill@parity.io>
2025-01-29 15:24:45 +01:00
123 changed files with 3026 additions and 7412 deletions
+2 -3
View File
@@ -12,7 +12,6 @@ rustflags = [
"-Clink-arg=-sALLOW_TABLE_GROWTH=1", "-Clink-arg=-sALLOW_TABLE_GROWTH=1",
"-Clink-arg=--js-library=js/embed/soljson_interface.js", "-Clink-arg=--js-library=js/embed/soljson_interface.js",
"-Clink-arg=--pre-js=js/embed/pre.js", "-Clink-arg=--pre-js=js/embed/pre.js",
"-Clink-arg=-sNODEJS_CATCH_EXIT=0", "-Clink-arg=-sSTACK_SIZE=128kb",
"-Clink-arg=-sDISABLE_EXCEPTION_CATCHING=0", "-Clink-arg=-sNODEJS_CATCH_EXIT=0"
"-Copt-level=3"
] ]
+1 -16
View File
@@ -9,7 +9,6 @@ on:
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
REVIVE_WASM_INSTALL_DIR: ${{ github.workspace }}/target/wasm32-unknown-emscripten/release REVIVE_WASM_INSTALL_DIR: ${{ github.workspace }}/target/wasm32-unknown-emscripten/release
BUN_VERSION: 1.1.43
jobs: jobs:
build-revive-wasm: build-revive-wasm:
@@ -87,6 +86,7 @@ jobs:
path: | path: |
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js ${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.js
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm ${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc.wasm
${{ env.REVIVE_WASM_INSTALL_DIR }}/resolc_web.js
retention-days: 1 retention-days: 1
test-revive-wasm: test-revive-wasm:
@@ -112,21 +112,6 @@ jobs:
with: with:
node-version: "20" 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 - name: Install packages
run: npm install run: npm install
+347
View File
@@ -0,0 +1,347 @@
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
jobs:
#
#
#
tag:
runs-on: ubuntu-24.04
permissions:
contents: write
outputs:
TAG: ${{ steps.versions.outputs.TAG }}
PKG_VER: ${{ steps.versions.outputs.PKG_VER }}
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
#
#
#
build-linux-all:
if: ${{ needs.tag.outputs.TAG == 'new' }}
runs-on: parity-large
needs: [tag]
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
- name: Install Rust stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rust-src
target: wasm32-unknown-emscripten
- name: versions
run: |
rustup show
cargo --version
cmake --version
echo "bash:" && bash --version
echo "ninja:" && ninja --version
echo "clang:" && clang --version
- name: build revive-llvm
run: make install-llvm-builder
# musl LLVM
- name: llvm-musl-cache restore
id: llvm-musl-cache
uses: actions/cache/restore@v4
with:
path: target-llvm/musl/target-final
key: llvm-linux-musl-${{ hashFiles('crates/solidity/**') }}
- name: Build musl LLVM
if: steps.llvm-musl-cache.outputs.cache-hit != 'true'
run: |
revive-llvm --target-env musl clone
revive-llvm --target-env musl build --llvm-projects lld --llvm-projects clang
- name: llvm-musl-cache save
if: steps.llvm-musl-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: target-llvm/musl/target-final
key: llvm-linux-musl-${{ hashFiles('crates/solidity/**') }}
# emscripten LLVM
- name: llvm-emscripten-cache restore
id: llvm-emscripten-cache
uses: actions/cache/restore@v4
with:
path: |
target-llvm/emscripten/target-final
emsdk
key: llvm-linux-emscripten-${{ hashFiles('crates/solidity/**') }}
- name: Build emscripten LLVM
if: steps.llvm-emscripten-cache.outputs.cache-hit != 'true'
run: |
revive-llvm --target-env emscripten clone
source emsdk/emsdk_env.sh
revive-llvm --target-env emscripten build --llvm-projects lld
- name: llvm-emscripten-cache save
if: steps.llvm-emscripten-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: |
target-llvm/emscripten/target-final
emsdk
key: llvm-linux-emscripten-${{ hashFiles('crates/solidity/**') }}
# 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
"
- 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 --bin crates/integration/contracts/flipper.sol)
echo $result
if [[ $result == *'0x50564d'* ]]; then exit 0; else exit 1; fi
- name: Set Up Node.js
uses: actions/setup-node@v3
with:
node-version: "20"
- name: build wasm
run: |
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); }
"
- uses: actions/upload-artifact@v4
with:
name: revive-wasm
path: |
./target/wasm32-unknown-emscripten/release/resolc.js
./target/wasm32-unknown-emscripten/release/resolc.wasm
./target/wasm32-unknown-emscripten/release/resolc_web.js
retention-days: 1
- uses: actions/upload-artifact@v4
with:
name: revive-linux
path: |
./resolc-out/resolc
retention-days: 1
#
#
#
create-release:
needs: [tag, build-linux-all]
runs-on: ubuntu-24.04
permissions:
contents: write
outputs:
upload_url: ${{ steps.create_release.outputs.result }}
steps:
- uses: actions/checkout@v4
- name: Create/update tag
id: tag
uses: actions/github-script@v7
with:
result-encoding: string
script: |
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/${{ needs.tag.outputs.PKG_VER }}',
sha: context.sha
})
- name: get relese notes
id: get-notes
run: |
{
echo 'releaseNotes<<EOF'
sed '/^## ${{ needs.tag.outputs.PKG_VER }}/,/^## v/!d' CHANGELOG.md | sed -e '1d' -e '$d'
echo EOF
} >> "$GITHUB_OUTPUT"
- name: Create release
id: create_release
env:
releaseNotes: ${{ steps.get-notes.outputs.releaseNotes }}
version: ${{ needs.tag.outputs.PKG_VER }}
uses: actions/github-script@v7
with:
result-encoding: string
script: |
let response = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: process.env.version,
name: process.env.version,
body: process.env.releaseNotes,
draft: true,
prerelease: true
});
console.log(response);
return response.data.upload_url;
- name: Log
run: |
echo "tag result: ${{ needs.tag.outputs.TAG }}"
echo "pkg version: ${{ needs.tag.outputs.PKG_VER }}"
#
#
#
upload-assets:
runs-on: ubuntu-24.04
needs: [create-release]
steps:
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: revive-wasm
path: resolc/
- name: Download Artifact
uses: actions/download-artifact@v4
with:
name: revive-linux
path: resolc/
- name: upload resolc
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc
asset_name: resolc-static-linux
asset_content_type: application/octet-stream
- name: upload resolc.js
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc.js
asset_name: resolc.js
asset_content_type: application/octet-stream
- name: upload resolc.wasm
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc.wasm
asset_name: resolc.wasm
asset_content_type: application/octet-stream
- name: upload resolc_web.js
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ./resolc/resolc_web.js
asset_name: resolc_web.js
asset_content_type: application/octet-stream
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
tar Jxf llvm.tar.xz -C llvm18/ tar Jxf llvm.tar.xz -C llvm18/
echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm18" >> $GITHUB_ENV echo "LLVM_SYS_181_PREFIX=$(pwd)/llvm18" >> $GITHUB_ENV
- name: Install apt dependencies - name: Install geth
run: | run: |
sudo add-apt-repository -y ppa:ethereum/ethereum sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update sudo apt update
+44
View File
@@ -2,6 +2,50 @@
## Unreleased ## 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 ## v0.1.0-dev.9
This is a development pre-release. This is a development pre-release.
Generated
+806 -655
View File
File diff suppressed because it is too large Load Diff
+30 -31
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["crates/*"] members = ["crates/*"]
[workspace.package] [workspace.package]
version = "0.1.0-dev.9" version = "0.1.0-dev.11"
authors = [ authors = [
"Cyrill Leutwiler <cyrill@parity.io>", "Cyrill Leutwiler <cyrill@parity.io>",
"Parity Technologies <admin@parity.io>", "Parity Technologies <admin@parity.io>",
@@ -14,48 +14,47 @@ repository = "https://github.com/paritytech/revive"
rust-version = "1.81.0" rust-version = "1.81.0"
[workspace.dependencies] [workspace.dependencies]
revive-benchmarks = { version = "0.1.0-dev.9", path = "crates/benchmarks" } revive-benchmarks = { version = "0.1.0-dev.11", path = "crates/benchmarks" }
revive-builtins = { version = "0.1.0-dev.9", path = "crates/builtins" } revive-builtins = { version = "0.1.0-dev.11", path = "crates/builtins" }
revive-common = { version = "0.1.0-dev.9", path = "crates/common" } revive-common = { version = "0.1.0-dev.11", path = "crates/common" }
revive-differential = { version = "0.1.0-dev.9", path = "crates/differential" } revive-differential = { version = "0.1.0-dev.11", path = "crates/differential" }
revive-integration = { version = "0.1.0-dev.9", path = "crates/integration" } revive-integration = { version = "0.1.0-dev.11", path = "crates/integration" }
revive-linker = { version = "0.1.0-dev.9", path = "crates/linker" } revive-linker = { version = "0.1.0-dev.11", path = "crates/linker" }
lld-sys = { version = "0.1.0-dev.9", path = "crates/lld-sys" } lld-sys = { version = "0.1.0-dev.11", path = "crates/lld-sys" }
revive-llvm-context = { version = "0.1.0-dev.9", path = "crates/llvm-context" } revive-llvm-context = { version = "0.1.0-dev.11", path = "crates/llvm-context" }
revive-runtime-api = { version = "0.1.0-dev.9", path = "crates/runtime-api" } revive-runtime-api = { version = "0.1.0-dev.11", path = "crates/runtime-api" }
revive-runner = { version = "0.1.0-dev.9", path = "crates/runner" } revive-runner = { version = "0.1.0-dev.11", path = "crates/runner" }
revive-solidity = { version = "0.1.0-dev.9", path = "crates/solidity" } revive-solidity = { version = "0.1.0-dev.11", path = "crates/solidity" }
revive-stdlib = { version = "0.1.0-dev.9", path = "crates/stdlib" } revive-stdlib = { version = "0.1.0-dev.11", path = "crates/stdlib" }
revive-build-utils = { version = "0.1.0-dev.9", path = "crates/build-utils" } revive-build-utils = { version = "0.1.0-dev.11", path = "crates/build-utils" }
hex = "0.4.3" hex = "0.4.3"
cc = "1.0" cc = "1.2"
libc = "0.2.169" libc = "0.2.169"
tempfile = "3.8" tempfile = "3.17"
anyhow = "1.0" anyhow = "1.0"
semver = { version = "1.0", features = ["serde"] } semver = { version = "1.0", features = ["serde"] }
itertools = "0.14" itertools = "0.14"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["arbitrary_precision"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] }
regex = "1.10" regex = "1.10"
once_cell = "1.19" once_cell = "1.20"
num = "0.4.3" num = "0.4.3"
sha1 = "0.10" sha1 = "0.10"
sha3 = "0.10" sha3 = "0.10"
md5 = "0.7.0"
thiserror = "2.0" thiserror = "2.0"
which = "7.0" which = "7.0"
path-slash = "0.2" path-slash = "0.2"
rayon = "1.8" rayon = "1.8"
clap = { version = "4", default-features = false, features = ["derive"] } clap = { version = "4", default-features = false, features = ["derive"] }
polkavm-common = "0.19.0" polkavm-common = "0.21.0"
polkavm-linker = "0.19.0" polkavm-linker = "0.21.0"
polkavm-disassembler = "0.19.0" polkavm-disassembler = "0.21.0"
polkavm = "0.19.0" polkavm = "0.21.0"
alloy-primitives = { version = "0.8.19", features = ["serde"] } alloy-primitives = { version = "0.8.21", features = ["serde"] }
alloy-sol-types = "0.8.19" alloy-sol-types = "0.8.21"
alloy-genesis = "0.9.2" alloy-genesis = "0.11.1"
alloy-serde = "0.9.2" alloy-serde = "0.11.1"
env_logger = { version = "0.11.6", default-features = false } env_logger = { version = "0.11.6", default-features = false }
serde_stacker = "0.1.11" serde_stacker = "0.1.11"
criterion = { version = "0.5.1", features = ["html_reports"] } criterion = { version = "0.5.1", features = ["html_reports"] }
@@ -65,15 +64,15 @@ downloader = "0.2.8"
flate2 = "1.0.35" flate2 = "1.0.35"
fs_extra = "1.3.0" fs_extra = "1.3.0"
num_cpus = "1" num_cpus = "1"
tar = "0.4.43" tar = "0.4"
toml = "0.8.19" toml = "0.8"
assert_cmd = "2.0.16" assert_cmd = "2.0"
assert_fs = "1.1.2" assert_fs = "1.1"
# polkadot-sdk and friends # polkadot-sdk and friends
codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" } codec = { version = "3.6.12", default-features = false, package = "parity-scale-codec" }
scale-info = { version = "2.11.6", default-features = false } scale-info = { version = "2.11.6", default-features = false }
polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "4302f74f7874e6a894578731142a7b310a1449b0" } polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk", rev = "274a781e8ca1a9432c7ec87593bd93214abbff50" }
# llvm # llvm
[workspace.dependencies.inkwell] [workspace.dependencies.inkwell]
+2 -1
View File
@@ -30,6 +30,7 @@ install-npm:
install-wasm: install-npm install-wasm: install-npm
cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features cargo build --target wasm32-unknown-emscripten -p revive-solidity --release --no-default-features
npm run build:package
install-llvm-builder: install-llvm-builder:
cargo install --path crates/llvm-builder cargo install --path crates/llvm-builder
@@ -42,7 +43,7 @@ format:
cargo fmt --all --check cargo fmt --all --check
clippy: clippy:
cargo clippy --all-features --workspace --tests --benches -- --deny warnings --allow dead_code cargo clippy --all-features --workspace --tests --benches -- --deny warnings
machete: machete:
cargo install cargo-machete cargo install cargo-machete
+1 -1
View File
@@ -3,7 +3,7 @@
# revive # revive
YUL and EVM assembly recompiler to LLVM, targetting RISC-V on [PolkaVM](https://github.com/koute/polkavm). YUL 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! Visit [contracts.polkadot.io](https://contracts.polkadot.io) to learn more about contracts on Polkadot!
+4 -5
View File
@@ -4,8 +4,7 @@ Prior to the first stable release we neither have formal release processes nor d
To create a new pre-release: 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 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. Push a release tag to `main` 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. Create a __pre-release__ from the tag and manually upload the `resolc` binary from docker image 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. Manually upload `resolc.js` and `resolc.wasm` from the `build-revive-wasm` action artifacts. 4. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
5. Update the [contract-docs](https://github.com/paritytech/contract-docs/) accordingly
-6
View File
@@ -12,12 +12,6 @@ pub static EXTENSION_ABI: &str = "abi";
/// The Yul IR file extension. /// The Yul IR file extension.
pub static EXTENSION_YUL: &str = "yul"; 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. /// The EVM file extension.
pub static EXTENSION_EVM: &str = "evm"; pub static EXTENSION_EVM: &str = "evm";
+9 -2
View File
@@ -16,9 +16,16 @@
"shanghaiTime": 0, "shanghaiTime": 0,
"cancunTime": 0, "cancunTime": 0,
"terminalTotalDifficulty": 0, "terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true "terminalTotalDifficultyPassed": true,
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
}, },
"coinbase": "0x0000000000000000000000000000000000000000", "coinbase": "0xffffffffffffffffffffffffffffffffffffffff",
"difficulty": "0x20000", "difficulty": "0x20000",
"extraData": "", "extraData": "",
"gasLimit": "0xffffffff", "gasLimit": "0xffffffff",
+1 -1
View File
@@ -413,7 +413,7 @@ impl Evm {
let stderr = str::from_utf8(output.stderr.as_slice()) let stderr = str::from_utf8(output.stderr.as_slice())
.unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stderr failed to parse: {err}")); .unwrap_or_else(|err| panic!("{EXECUTABLE_NAME} stderr failed to parse: {err}"));
let mut log: EvmLog = stdout.into(); let mut log: EvmLog = format!("{stdout}{stderr}").as_str().into();
log.stderr = stderr.into(); log.stderr = stderr.into();
if self.bench { if self.bench {
log.parse_gas_used_from_bench(); log.parse_gas_used_from_bench();
+81
View File
@@ -0,0 +1,81 @@
// 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
@@ -0,0 +1,27 @@
// 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
@@ -0,0 +1,50 @@
// 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");
}
}
@@ -0,0 +1,42 @@
// 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
@@ -0,0 +1,40 @@
// 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
@@ -0,0 +1,75 @@
// 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 {}
}
+32 -11
View File
@@ -3,7 +3,7 @@ pragma solidity ^0.8;
/* runner.json /* runner.json
{ {
"differential": true, "differential": false,
"actions": [ "actions": [
{ {
"Instantiate": { "Instantiate": {
@@ -12,7 +12,7 @@ pragma solidity ^0.8;
"contract": "Transfer" "contract": "Transfer"
} }
}, },
"value": 11 "value": 211
} }
}, },
{ {
@@ -23,12 +23,35 @@ pragma solidity ^0.8;
"data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a" "data": "1c8d16b30000000000000000000000000303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000a"
} }
}, },
{
"VerifyCall": {
"success": true
}
},
{ {
"Call": { "Call": {
"dest": { "dest": {
"Instantiated": 0 "Instantiated": 0
}, },
"data": "fb9e8d0500000000000000000000000003030303030303030303030303030303030303030000000000000000000000000000000000000000000000000000000000000001" "data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000001"
}
},
{
"VerifyCall": {
"success": false
}
},
{
"Call": {
"dest": {
"Instantiated": 0
},
"data": "fb9e8d050000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"VerifyCall": {
"success": false
} }
} }
] ]
@@ -36,19 +59,17 @@ pragma solidity ^0.8;
*/ */
contract Transfer { 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 { function transfer_self(uint _amount) public payable {
transfer_to(address_self(), _amount); transfer_to(payable(address(this)), _amount);
} }
function transfer_to(address payable _dest, uint _amount) public payable { function transfer_to(address payable _dest, uint _amount) public payable {
_dest.transfer(_amount); _dest.transfer(_amount);
} }
fallback() external {}
receive() external payable {}
} }
+14
View File
@@ -156,6 +156,20 @@ case!("DivisionArithmetics.sol", DivisionArithmetics, sdivCall, division_arithme
case!("DivisionArithmetics.sol", DivisionArithmetics, modCall, division_arithmetics_mod, n: U256, d: U256); case!("DivisionArithmetics.sol", DivisionArithmetics, modCall, division_arithmetics_mod, n: U256, d: U256);
case!("DivisionArithmetics.sol", DivisionArithmetics, smodCall, division_arithmetics_smod, n: I256, d: I256); 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!( sol!(
contract MStore8 { contract MStore8 {
function mStore8(uint value) public pure returns (uint256 word); function mStore8(uint value) public pure returns (uint256 word);
+48 -30
View File
@@ -37,10 +37,10 @@ test_spec!(events, "Events", "Events.sol");
test_spec!(storage, "Storage", "Storage.sol"); test_spec!(storage, "Storage", "Storage.sol");
test_spec!(mstore8, "MStore8", "MStore8.sol"); test_spec!(mstore8, "MStore8", "MStore8.sol");
test_spec!(address, "Context", "Context.sol"); test_spec!(address, "Context", "Context.sol");
test_spec!(balance, "Value", "Value.sol"); test_spec!(value, "Value", "Value.sol");
test_spec!(create, "CreateB", "Create.sol"); test_spec!(create, "CreateB", "Create.sol");
test_spec!(call, "Caller", "Call.sol"); test_spec!(call, "Caller", "Call.sol");
test_spec!(transfer, "Transfer", "Transfer.sol"); test_spec!(balance, "Balance", "Balance.sol");
test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol"); test_spec!(return_data_oob, "ReturnDataOob", "ReturnDataOob.sol");
test_spec!(immutables, "Immutables", "Immutables.sol"); test_spec!(immutables, "Immutables", "Immutables.sol");
test_spec!(transaction, "Transaction", "Transaction.sol"); test_spec!(transaction, "Transaction", "Transaction.sol");
@@ -50,6 +50,12 @@ test_spec!(gas_price, "GasPrice", "GasPrice.sol");
test_spec!(gas_left, "GasLeft", "GasLeft.sol"); test_spec!(gas_left, "GasLeft", "GasLeft.sol");
test_spec!(gas_limit, "GasLimit", "GasLimit.sol"); test_spec!(gas_limit, "GasLimit", "GasLimit.sol");
test_spec!(base_fee, "BaseFee", "BaseFee.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> { fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
vec![Instantiate { vec![Instantiate {
@@ -61,7 +67,6 @@ fn instantiate(path: &str, contract: &str) -> Vec<SpecsAction> {
path: Some(path.into()), path: Some(path.into()),
contract: contract.to_string(), contract: contract.to_string(),
solc_optimizer: None, solc_optimizer: None,
pipeline: None,
}, },
data: vec![], data: vec![],
salt: OptionalHex::default(), salt: OptionalHex::default(),
@@ -354,7 +359,6 @@ fn ext_code_size() {
path: Some("contracts/Baseline.sol".into()), path: Some("contracts/Baseline.sol".into()),
contract: "Baseline".to_string(), contract: "Baseline".to_string(),
solc_optimizer: None, solc_optimizer: None,
pipeline: None,
}, },
data: vec![], data: vec![],
salt: OptionalHex::from([0; 32]), salt: OptionalHex::from([0; 32]),
@@ -435,32 +439,46 @@ fn ext_code_size() {
.run(); .run();
} }
/* #[test]
// These test were implement for the mock-runtime and need to be ported yet. #[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();
}
#[test] #[test]
fn create2_failure() { #[should_panic(expected = "ReentranceDenied")]
let mut state = State::default(); fn transfer_denies_reentrancy() {
let contract_a = Contract::create_a(); let value = 1000;
state.upload_code(&contract_a.pvm_runtime); Specs {
actions: vec![
let contract = Contract::create_b(); instantiate("contracts/Transfer.sol", "Transfer").remove(0),
let (state, output) = state Call {
.transaction() origin: TestAddress::Alice,
.with_default_account(&contract.pvm_runtime) dest: TestAddress::Instantiated(0),
.calldata(contract.calldata.clone()) value,
.call(); gas_limit: None,
storage_deposit_limit: None,
assert_eq!(output.flags, ReturnFlags::Success); data: Contract::transfer_self(U256::from(value)).calldata,
},
// The address already exists, which should cause the contract to revert ],
differential: false,
let (_, output) = state ..Default::default()
.transaction() }
.with_default_account(&contract.pvm_runtime) .run();
.calldata(contract.calldata)
.call();
assert_eq!(output.flags, ReturnFlags::Revert);
} }
*/
+3 -11
View File
@@ -40,17 +40,9 @@ pub const SHARED_BUILD_OPTS_NOT_MUSL: [&str; 4] = [
/// The shared build options to treat warnings as errors. /// The shared build options to treat warnings as errors.
/// ///
/// Disabled on Windows due to the following upstream issue with MSYS2 with mingw-w64: /// Disabled because it makes the build very brittle.
/// ProgramTest.cpp:23:15: error: '__p__environ' redeclared without 'dllimport' attribute pub fn shared_build_opts_werror(_target_env: TargetEnv) -> Vec<String> {
pub fn shared_build_opts_werror(target_env: TargetEnv) -> Vec<String> { vec!["-DLLVM_ENABLE_WERROR='Off'".to_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. /// The build options to set the default target.
@@ -141,7 +141,7 @@ fn build_target(
Command::new("emcmake") Command::new("emcmake")
.env("EMCC_DEBUG", "2") .env("EMCC_DEBUG", "2")
.env("CXXFLAGS", "-Dwait4=__syscall_wait4") .env("CXXFLAGS", "-Dwait4=__syscall_wait4")
.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") .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")
.arg("cmake") .arg("cmake")
.args([ .args([
"-S", "-S",
-1
View File
@@ -21,7 +21,6 @@ serde = { workspace = true, features = ["derive"] }
num = { workspace = true } num = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
sha3 = { workspace = true } sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true } inkwell = { workspace = true }
polkavm-disassembler = { workspace = true } polkavm-disassembler = { workspace = true }
polkavm-common = { workspace = true } polkavm-common = { workspace = true }
@@ -1,16 +1,11 @@
//! The debug IR type. //! The debug IR type.
/// The debug IR type. /// The debug IR type.
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IRType { pub enum IRType {
/// Whether to dump the Yul code. /// Whether to dump the Yul code.
Yul, 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. /// Whether to dump the LLVM IR code.
LLVM, LLVM,
/// Whether to dump the assembly code. /// Whether to dump the assembly code.
@@ -27,8 +22,6 @@ impl IRType {
pub fn file_extension(&self) -> &'static str { pub fn file_extension(&self) -> &'static str {
match self { match self {
Self::Yul => revive_common::EXTENSION_YUL, 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::LLVM => revive_common::EXTENSION_LLVM_SOURCE,
Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY, Self::Assembly => revive_common::EXTENSION_POLKAVM_ASSEMBLY,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@@ -39,30 +39,6 @@ impl DebugConfig {
Ok(()) 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. /// Dumps the unoptimized LLVM IR.
pub fn dump_llvm_ir_unoptimized( pub fn dump_llvm_ir_unoptimized(
&self, &self,
-5
View File
@@ -17,12 +17,7 @@ pub use self::polkavm::context::attribute::Attribute as PolkaVMAttribute;
pub use self::polkavm::context::build::Build as PolkaVMBuild; pub use self::polkavm::context::build::Build as PolkaVMBuild;
pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType; pub use self::polkavm::context::code_type::CodeType as PolkaVMCodeType;
pub use self::polkavm::context::debug_info::DebugInfo; 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::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::intrinsics::Intrinsics as PolkaVMIntrinsicFunction;
pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime; pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime;
pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn; pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn;
@@ -6,15 +6,9 @@ pub const LLVM_VERSION: semver::Version = semver::Version::new(18, 1, 4);
/// The pointer width sized type. /// The pointer width sized type.
pub static XLEN: usize = revive_common::BIT_LENGTH_X32; 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. /// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize"; 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: /// The deployer call header size that consists of:
/// - bytecode hash (32 bytes) /// - bytecode hash (32 bytes)
pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD; pub const DEPLOYER_CALL_HEADER_SIZE: usize = revive_common::BYTE_LENGTH_WORD;
@@ -1,27 +0,0 @@
//! 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),
}
}
}
@@ -1,34 +0,0 @@
//! 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
)
}
}
@@ -1,18 +0,0 @@
//! 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 }
}
}
@@ -1,52 +0,0 @@
//! 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")
}
}
@@ -1,74 +0,0 @@
//! 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,8 +1,6 @@
//! The LLVM IR generator function. //! The LLVM IR generator function.
pub mod block;
pub mod declaration; pub mod declaration;
pub mod evmla_data;
pub mod intrinsics; pub mod intrinsics;
pub mod llvm_runtime; pub mod llvm_runtime;
pub mod r#return; pub mod r#return;
@@ -19,7 +17,6 @@ use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::pointer::Pointer; use crate::polkavm::context::pointer::Pointer;
use self::declaration::Declaration; use self::declaration::Declaration;
use self::evmla_data::EVMLAData;
use self::r#return::Return; use self::r#return::Return;
use self::yul_data::YulData; use self::yul_data::YulData;
@@ -45,8 +42,6 @@ pub struct Function<'ctx> {
/// The Yul compiler data. /// The Yul compiler data.
yul_data: Option<YulData>, yul_data: Option<YulData>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData<'ctx>>,
} }
impl<'ctx> Function<'ctx> { impl<'ctx> Function<'ctx> {
@@ -72,7 +67,6 @@ impl<'ctx> Function<'ctx> {
return_block, return_block,
yul_data: None, yul_data: None,
evmla_data: None,
} }
} }
@@ -300,29 +294,6 @@ impl<'ctx> Function<'ctx> {
self.return_block 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. /// Sets the Yul data.
pub fn set_yul_data(&mut self, data: YulData) { pub fn set_yul_data(&mut self, data: YulData) {
self.yul_data = Some(data); self.yul_data = Some(data);
@@ -24,22 +24,6 @@ impl Entry {
where where
D: Dependency + Clone, 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( context.set_global(
crate::polkavm::GLOBAL_CALLDATA_SIZE, crate::polkavm::GLOBAL_CALLDATA_SIZE,
context.xlen_type(), context.xlen_type(),
@@ -47,13 +31,6 @@ impl Entry {
context.xlen_type().get_undef(), context.xlen_type().get_undef(),
); );
context.set_global(
crate::polkavm::GLOBAL_CALL_FLAGS,
context.word_type(),
AddressSpace::Stack,
context.word_const(0),
);
Ok(()) Ok(())
} }
@@ -70,6 +47,11 @@ impl Entry {
.build_runtime_call(revive_runtime_api::polkavm_imports::CALL_DATA_SIZE, &[]) .build_runtime_call(revive_runtime_api::polkavm_imports::CALL_DATA_SIZE, &[])
.expect("the call_data_size syscall method should return a value") .expect("the call_data_size syscall method should return a value")
.into_int_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 context
.builder() .builder()
.build_store(call_data_size_pointer, call_data_size_value)?; .build_store(call_data_size_pointer, call_data_size_value)?;
@@ -90,13 +72,6 @@ impl Entry {
.borrow() .borrow()
.get_nth_param(Self::ARGUMENT_INDEX_CALL_FLAGS); .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 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"); let runtime_code_call_block = context.append_basic_block("runtime_code_call_block");
+14 -41
View File
@@ -6,7 +6,6 @@ pub mod attribute;
pub mod build; pub mod build;
pub mod code_type; pub mod code_type;
pub mod debug_info; pub mod debug_info;
pub mod evmla_data;
pub mod function; pub mod function;
pub mod global; pub mod global;
pub mod r#loop; pub mod r#loop;
@@ -38,7 +37,6 @@ use self::attribute::Attribute;
use self::build::Build; use self::build::Build;
use self::code_type::CodeType; use self::code_type::CodeType;
use self::debug_info::DebugInfo; use self::debug_info::DebugInfo;
use self::evmla_data::EVMLAData;
use self::function::declaration::Declaration as FunctionDeclaration; use self::function::declaration::Declaration as FunctionDeclaration;
use self::function::intrinsics::Intrinsics; use self::function::intrinsics::Intrinsics;
use self::function::llvm_runtime::LLVMRuntime; use self::function::llvm_runtime::LLVMRuntime;
@@ -95,8 +93,6 @@ where
solidity_data: Option<SolidityData>, solidity_data: Option<SolidityData>,
/// The Yul data. /// The Yul data.
yul_data: Option<YulData>, yul_data: Option<YulData>,
/// The EVM legacy assembly data.
evmla_data: Option<EVMLAData<'ctx>>,
} }
impl<'ctx, D> Context<'ctx, D> impl<'ctx, D> Context<'ctx, D>
@@ -257,7 +253,6 @@ where
solidity_data: None, solidity_data: None,
yul_data: None, yul_data: None,
evmla_data: None,
} }
} }
@@ -1214,17 +1209,17 @@ where
/// Build a call to PolkaVM `msize` for querying the linear memory size. /// Build a call to PolkaVM `msize` for querying the linear memory size.
pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> { pub fn build_msize(&self) -> anyhow::Result<inkwell::values::IntValue<'ctx>> {
Ok(self let memory_size_pointer = self
.builder() .module()
.build_call( .get_global(revive_runtime_api::polkavm_imports::MEMORY_SIZE)
self.runtime_api_method(revive_runtime_api::polkavm_imports::MEMORY_SIZE), .expect("the memory size symbol should have been declared")
&[], .as_pointer_value();
"call_msize", let memory_size_value = self.builder().build_load(
)? self.xlen_type(),
.try_as_basic_value() memory_size_pointer,
.left() "memory_size_value",
.expect("sbrk returns an int") )?;
.into_int_value()) Ok(memory_size_value.into_int_value())
} }
/// Call PolkaVM `sbrk` for extending the heap by `offset` + `size`, /// Call PolkaVM `sbrk` for extending the heap by `offset` + `size`,
@@ -1270,8 +1265,9 @@ where
self.build_heap_alloc(offset, length)?; self.build_heap_alloc(offset, length)?;
let heap_start = self let heap_start = self
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)? .module()
.value .get_global(revive_runtime_api::polkavm_imports::MEMORY)
.expect("the memory symbol should have been declared")
.as_pointer_value(); .as_pointer_value();
Ok(self.build_gep( Ok(self.build_gep(
Pointer::new(self.byte_type(), AddressSpace::Stack, heap_start), Pointer::new(self.byte_type(), AddressSpace::Stack, heap_start),
@@ -1574,29 +1570,6 @@ where
.expect("The Yul data must have been initialized") .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. /// 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 /// If the size is set manually, then it is returned. Otherwise, the number of elements in
/// the identifier-to-offset mapping tree is returned. /// the identifier-to-offset mapping tree is returned.
+95 -18
View File
@@ -8,6 +8,7 @@ use crate::polkavm::Dependency;
const STATIC_CALL_FLAG: u32 = 0b0001_0000; const STATIC_CALL_FLAG: u32 = 0b0001_0000;
const REENTRANT_CALL_FLAG: u32 = 0b0000_1000; const REENTRANT_CALL_FLAG: u32 = 0b0000_1000;
const SOLIDITY_TRANSFER_GAS_STIPEND_THRESHOLD: u64 = 2300;
/// Translates a contract call. /// Translates a contract call.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@@ -37,26 +38,24 @@ where
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?; let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?; 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 input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_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"); let output_length_pointer = context.build_alloca_at_entry(context.xlen_type(), "output_length");
context.build_store(output_length_pointer, output_length)?; context.build_store(output_length_pointer, output_length)?;
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer"); let (flags, deposit_limit_value) = if static_call {
context.build_store(deposit_pointer, context.word_type().const_all_ones())?; let flags = REENTRANT_CALL_FLAG | STATIC_CALL_FLAG;
(
let flags = if static_call { context.xlen_type().const_int(flags as u64, false),
REENTRANT_CALL_FLAG | STATIC_CALL_FLAG context.word_type().const_zero(),
)
} else { } else {
REENTRANT_CALL_FLAG call_reentrancy_heuristic(context, gas, input_length, output_length)?
}; };
let flags = context.xlen_type().const_int(flags as u64, false);
let deposit_pointer = context.build_alloca_at_entry(context.word_type(), "deposit_pointer");
context.build_store(deposit_pointer, deposit_limit_value)?;
let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg( let flags_and_callee = revive_runtime_api::calling_convention::pack_hi_lo_reg(
context.builder(), context.builder(),
@@ -119,7 +118,7 @@ where
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn delegate_call<'ctx, D>( pub fn delegate_call<'ctx, D>(
context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
gas: inkwell::values::IntValue<'ctx>, _gas: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>, address: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>, input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>, input_length: inkwell::values::IntValue<'ctx>,
@@ -137,11 +136,6 @@ where
let output_offset = context.safe_truncate_int_to_xlen(output_offset)?; let output_offset = context.safe_truncate_int_to_xlen(output_offset)?;
let output_length = context.safe_truncate_int_to_xlen(output_length)?; 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 input_pointer = context.build_heap_gep(input_offset, input_length)?;
let output_pointer = context.build_heap_gep(output_offset, output_length)?; let output_pointer = context.build_heap_gep(output_offset, output_length)?;
@@ -221,3 +215,86 @@ where
.resolve_library(path.as_str())? .resolve_library(path.as_str())?
.as_basic_value_enum()) .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))
}
+10 -2
View File
@@ -122,12 +122,20 @@ where
/// Translates the `coinbase` instruction. /// Translates the `coinbase` instruction.
pub fn coinbase<'ctx, D>( pub fn coinbase<'ctx, D>(
_context: &mut Context<'ctx, D>, context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>> ) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where where
D: Dependency + Clone, D: Dependency + Clone,
{ {
todo!() 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)
} }
/// Translates the `basefee` instruction. /// Translates the `basefee` instruction.
-1
View File
@@ -59,7 +59,6 @@ pub fn build_assembly_text(
} }
/// Implemented by items which are translated into LLVM IR. /// Implemented by items which are translated into LLVM IR.
#[allow(clippy::upper_case_acronyms)]
pub trait WriteLLVM<D> pub trait WriteLLVM<D>
where where
D: Dependency + Clone, D: Dependency + Clone,
-3
View File
@@ -239,7 +239,6 @@ pub enum Code {
Solidity { Solidity {
path: Option<std::path::PathBuf>, path: Option<std::path::PathBuf>,
solc_optimizer: Option<bool>, solc_optimizer: Option<bool>,
pipeline: Option<revive_solidity::SolcPipeline>,
contract: String, contract: String,
}, },
/// Read the contract blob from disk /// Read the contract blob from disk
@@ -264,7 +263,6 @@ impl From<Code> for pallet_revive::Code {
path, path,
contract, contract,
solc_optimizer, solc_optimizer,
pipeline,
} => { } => {
let Some(path) = path else { let Some(path) = path else {
panic!("Solidity source of contract '{contract}' missing path"); panic!("Solidity source of contract '{contract}' missing path");
@@ -276,7 +274,6 @@ impl From<Code> for pallet_revive::Code {
&contract, &contract,
&source_code, &source_code,
solc_optimizer.unwrap_or(true), solc_optimizer.unwrap_or(true),
pipeline.unwrap_or(revive_solidity::SolcPipeline::Yul),
)) ))
} }
Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()), Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()),
+13 -5
View File
@@ -1,5 +1,4 @@
use frame_support::{runtime, weights::constants::WEIGHT_REF_TIME_PER_SECOND}; use frame_support::{runtime, traits::FindAuthor, weights::constants::WEIGHT_REF_TIME_PER_SECOND};
use pallet_revive::AccountId32Mapper; use pallet_revive::AccountId32Mapper;
use polkadot_sdk::*; use polkadot_sdk::*;
use polkadot_sdk::{ use polkadot_sdk::{
@@ -11,8 +10,6 @@ pub type Balance = u128;
pub type AccountId = pallet_revive::AccountId32Mapper<Runtime>; pub type AccountId = pallet_revive::AccountId32Mapper<Runtime>;
pub type Block = frame_system::mocking::MockBlock<Runtime>; pub type Block = frame_system::mocking::MockBlock<Runtime>;
pub type Hash = <Runtime as frame_system::Config>::Hash; pub type Hash = <Runtime as frame_system::Config>::Hash;
pub type EventRecord =
frame_system::EventRecord<<Runtime as frame_system::Config>::RuntimeEvent, Hash>;
#[runtime] #[runtime]
mod runtime { mod runtime {
@@ -26,7 +23,8 @@ mod runtime {
RuntimeHoldReason, RuntimeHoldReason,
RuntimeSlashReason, RuntimeSlashReason,
RuntimeLockId, RuntimeLockId,
RuntimeTask RuntimeTask,
RuntimeViewFunction
)] )]
pub struct Runtime; pub struct Runtime;
@@ -88,4 +86,14 @@ impl pallet_revive::Config for Runtime {
type InstantiateOrigin = EnsureSigned<AccountId32>; type InstantiateOrigin = EnsureSigned<AccountId32>;
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type ChainId = ConstU64<420_420_420>; 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,17 +282,11 @@ impl Specs {
let Code::Solidity { let Code::Solidity {
path: Some(path), path: Some(path),
solc_optimizer, solc_optimizer,
pipeline,
contract, contract,
} = code } = code
else { else {
panic!("the differential runner requires Code::Solidity source"); 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!( assert!(
salt.0.is_none(), salt.0.is_none(),
"salt is not supported in differential mode" "salt is not supported in differential mode"
+4 -6
View File
@@ -8,8 +8,8 @@
#define EVM_WORD_SIZE 32 #define EVM_WORD_SIZE 32
#define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1)) #define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1))
#define MAX_MEMORY_SIZE (64 * 1024) #define MAX_MEMORY_SIZE (64 * 1024)
static char __memory[MAX_MEMORY_SIZE]; char __memory[MAX_MEMORY_SIZE];
static uint32_t __memory_size = 0; 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) { if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) {
@@ -27,10 +27,6 @@ void * __sbrk_internal(uint32_t offset, uint32_t size) {
return (void *)&__memory[__memory_size]; return (void *)&__memory[__memory_size];
} }
uint32_t __msize() {
return __memory_size;
}
void * memset(void *b, int c, size_t len) { void * memset(void *b, int c, size_t len) {
uint8_t *dest = b; uint8_t *dest = b;
while (len-- > 0) *dest++ = c; while (len-- > 0) *dest++ = c;
@@ -72,6 +68,8 @@ POLKAVM_IMPORT(void, balance_of, uint32_t, uint32_t)
POLKAVM_IMPORT(void, base_fee, 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_hash, uint32_t, uint32_t)
POLKAVM_IMPORT(void, block_number, uint32_t) POLKAVM_IMPORT(void, block_number, uint32_t)
+9 -11
View File
@@ -1,18 +1,14 @@
//! 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}; use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, support::LLVMString};
include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs")); include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs"));
pub static SBRK: &str = "__sbrk_internal"; /// The emulated EVM heap memory global symbol.
pub static MEMORY: &str = "__memory";
pub static MEMORY_SIZE: &str = "__msize"; /// The emulated EVM heap memory size global symbol.
pub static MEMORY_SIZE: &str = "__memory_size";
pub static SBRK: &str = "__sbrk_internal";
pub static ADDRESS: &str = "address"; pub static ADDRESS: &str = "address";
@@ -22,6 +18,8 @@ pub static BALANCE_OF: &str = "balance_of";
pub static BASE_FEE: &str = "base_fee"; pub static BASE_FEE: &str = "base_fee";
pub static BLOCK_AUTHOR: &str = "block_author";
pub static BLOCK_HASH: &str = "block_hash"; pub static BLOCK_HASH: &str = "block_hash";
pub static BLOCK_NUMBER: &str = "block_number"; pub static BLOCK_NUMBER: &str = "block_number";
@@ -82,11 +80,11 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee";
/// Useful for configuring common attributes and linkage. /// Useful for configuring common attributes and linkage.
pub static IMPORTS: [&str; 34] = [ pub static IMPORTS: [&str; 34] = [
SBRK, SBRK,
MEMORY_SIZE,
ADDRESS, ADDRESS,
BALANCE, BALANCE,
BALANCE_OF, BALANCE_OF,
BASE_FEE, BASE_FEE,
BLOCK_AUTHOR,
BLOCK_HASH, BLOCK_HASH,
BLOCK_NUMBER, BLOCK_NUMBER,
CALL, CALL,
-1
View File
@@ -33,7 +33,6 @@ regex = { workspace = true }
hex = { workspace = true } hex = { workspace = true }
num = { workspace = true } num = { workspace = true }
sha3 = { workspace = true } sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true } inkwell = { workspace = true }
revive-common = { workspace = true } revive-common = { workspace = true }
+2 -2
View File
@@ -64,7 +64,7 @@ impl Contract {
file_path.push(file_name); file_path.push(file_name);
if file_path.exists() && !overwrite { if file_path.exists() && !overwrite {
eprintln!( anyhow::bail!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
); );
} else { } else {
@@ -87,7 +87,7 @@ impl Contract {
file_path.push(file_name); file_path.push(file_name);
if file_path.exists() && !overwrite { if file_path.exists() && !overwrite {
eprintln!( anyhow::bail!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
); );
} else { } else {
-2
View File
@@ -1,7 +1,5 @@
//! Solidity to PolkaVM compiler constants. //! Solidity to PolkaVM compiler constants.
#![allow(dead_code)]
/// The default executable name. /// The default executable name.
pub static DEFAULT_EXECUTABLE_NAME: &str = "resolc"; pub static DEFAULT_EXECUTABLE_NAME: &str = "resolc";
@@ -1,67 +0,0 @@
//! 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}`"),
}
}
}
@@ -1,79 +0,0 @@
//! 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(())
}
@@ -1,69 +0,0 @@
//! 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(())
}
@@ -1,382 +0,0 @@
//! 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(())
}
}
@@ -1,417 +0,0 @@
//! 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('\"')
),
}
}
}
@@ -1,106 +0,0 @@
//! 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
@@ -1,295 +0,0 @@
//! 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(())
}
}
@@ -1,48 +0,0 @@
//! 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
@@ -1,37 +0,0 @@
//! 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"),
}
}
}
@@ -1,120 +0,0 @@
//! 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(" | ")
)
}
}
@@ -1,156 +0,0 @@
//! 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
@@ -1,29 +0,0 @@
//! 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,
}
}
}
@@ -1,41 +0,0 @@
//! 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,
}
}
}
@@ -1,65 +0,0 @@
//! 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
}
}
}
}
}
@@ -1,134 +0,0 @@
//! 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
@@ -1,4 +0,0 @@
//! The EVM legacy assembly compiling tools.
pub mod assembly;
pub mod ethereal_ir;
+13 -43
View File
@@ -2,7 +2,6 @@
pub(crate) mod build; pub(crate) mod build;
pub(crate) mod r#const; pub(crate) mod r#const;
pub(crate) mod evmla;
pub(crate) mod missing_libraries; pub(crate) mod missing_libraries;
pub(crate) mod process; pub(crate) mod process;
pub(crate) mod project; pub(crate) mod project;
@@ -26,7 +25,6 @@ pub use self::project::Project;
pub use self::r#const::*; pub use self::r#const::*;
pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract; pub use self::solc::combined_json::contract::Contract as SolcCombinedJsonContract;
pub use self::solc::combined_json::CombinedJson as SolcCombinedJson; pub use self::solc::combined_json::CombinedJson as SolcCombinedJson;
pub use self::solc::pipeline::Pipeline as SolcPipeline;
#[cfg(not(target_os = "emscripten"))] #[cfg(not(target_os = "emscripten"))]
pub use self::solc::solc_compiler::SolcCompiler; pub use self::solc::solc_compiler::SolcCompiler;
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
@@ -53,6 +51,7 @@ pub mod test_utils;
pub mod tests; pub mod tests;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
/// Runs the Yul mode. /// Runs the Yul mode.
@@ -119,7 +118,6 @@ pub fn standard_output<T: Compiler>(
evm_version: Option<revive_common::EVMVersion>, evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
force_evmla: bool,
include_metadata_hash: bool, include_metadata_hash: bool,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
@@ -129,7 +127,6 @@ pub fn standard_output<T: Compiler>(
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<Build> { ) -> anyhow::Result<Build> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
let solc_input = SolcStandardJsonInput::try_from_paths( let solc_input = SolcStandardJsonInput::try_from_paths(
SolcStandardJsonInputLanguage::Solidity, SolcStandardJsonInputLanguage::Solidity,
@@ -137,7 +134,7 @@ pub fn standard_output<T: Compiler>(
input_files, input_files,
libraries, libraries,
remappings, remappings,
SolcStandardJsonInputSettingsSelection::new_required(solc_pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new( SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled, solc_optimizer_enabled,
None, None,
@@ -145,7 +142,6 @@ pub fn standard_output<T: Compiler>(
optimizer_settings.is_fallback_to_size_enabled(), optimizer_settings.is_fallback_to_size_enabled(),
), ),
None, None,
solc_pipeline == SolcPipeline::Yul,
suppressed_warnings, suppressed_warnings,
)?; )?;
@@ -156,13 +152,7 @@ pub fn standard_output<T: Compiler>(
.collect(); .collect();
let libraries = solc_input.settings.libraries.clone().unwrap_or_default(); let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
let mut solc_output = solc.standard_json( let mut solc_output = solc.standard_json(solc_input, base_path, include_paths, allow_paths)?;
solc_input,
solc_pipeline,
base_path,
include_paths,
allow_paths,
)?;
if let Some(errors) = solc_output.errors.as_deref() { if let Some(errors) = solc_output.errors.as_deref() {
let mut has_errors = false; let mut has_errors = false;
@@ -172,7 +162,7 @@ pub fn standard_output<T: Compiler>(
has_errors = true; has_errors = true;
} }
eprintln!("{error}"); writeln!(std::io::stderr(), "{error}")?;
} }
if has_errors { if has_errors {
@@ -180,13 +170,8 @@ pub fn standard_output<T: Compiler>(
} }
} }
let project = solc_output.try_to_project( let project =
source_code_files, solc_output.try_to_project(source_code_files, libraries, &solc_version, &debug_config)?;
libraries,
solc_pipeline,
&solc_version,
&debug_config,
)?;
let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?; let build = project.compile(optimizer_settings, include_metadata_hash, debug_config)?;
@@ -194,20 +179,17 @@ pub fn standard_output<T: Compiler>(
} }
/// Runs the standard JSON mode. /// Runs the standard JSON mode.
#[allow(clippy::too_many_arguments)]
pub fn standard_json<T: Compiler>( pub fn standard_json<T: Compiler>(
solc: &mut T, solc: &mut T,
detect_missing_libraries: bool, detect_missing_libraries: bool,
force_evmla: bool,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
debug_config: revive_llvm_context::DebugConfig, debug_config: revive_llvm_context::DebugConfig,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let solc_version = solc.version()?; let solc_version = solc.version()?;
let solc_pipeline = SolcPipeline::new(&solc_version, force_evmla);
let solc_input = SolcStandardJsonInput::try_from_stdin(solc_pipeline)?; let solc_input = SolcStandardJsonInput::try_from_stdin()?;
let source_code_files = solc_input let source_code_files = solc_input
.sources .sources
.iter() .iter()
@@ -225,13 +207,7 @@ pub fn standard_json<T: Compiler>(
}; };
let libraries = solc_input.settings.libraries.clone().unwrap_or_default(); let libraries = solc_input.settings.libraries.clone().unwrap_or_default();
let mut solc_output = solc.standard_json( let mut solc_output = solc.standard_json(solc_input, base_path, include_paths, allow_paths)?;
solc_input,
solc_pipeline,
base_path,
include_paths,
allow_paths,
)?;
if let Some(errors) = solc_output.errors.as_deref() { if let Some(errors) = solc_output.errors.as_deref() {
for error in errors.iter() { for error in errors.iter() {
@@ -242,13 +218,8 @@ pub fn standard_json<T: Compiler>(
} }
} }
let project = solc_output.try_to_project( let project =
source_code_files, solc_output.try_to_project(source_code_files, libraries, &solc_version, &debug_config)?;
libraries,
solc_pipeline,
&solc_version,
&debug_config,
)?;
if detect_missing_libraries { if detect_missing_libraries {
let missing_libraries = project.get_missing_libraries(); let missing_libraries = project.get_missing_libraries();
@@ -271,7 +242,6 @@ pub fn combined_json<T: Compiler>(
evm_version: Option<revive_common::EVMVersion>, evm_version: Option<revive_common::EVMVersion>,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
force_evmla: bool,
include_metadata_hash: bool, include_metadata_hash: bool,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
@@ -289,7 +259,6 @@ pub fn combined_json<T: Compiler>(
evm_version, evm_version,
solc_optimizer_enabled, solc_optimizer_enabled,
optimizer_settings, optimizer_settings,
force_evmla,
include_metadata_hash, include_metadata_hash,
base_path, base_path,
include_paths, include_paths,
@@ -309,10 +278,11 @@ pub fn combined_json<T: Compiler>(
combined_json.write_to_directory(output_directory.as_path(), overwrite)?; combined_json.write_to_directory(output_directory.as_path(), overwrite)?;
} }
None => { None => {
println!( writeln!(
std::io::stdout(),
"{}", "{}",
serde_json::to_string(&combined_json).expect("Always valid") serde_json::to_string(&combined_json).expect("Always valid")
); )?;
} }
} }
std::process::exit(0); std::process::exit(0);
@@ -1,47 +0,0 @@
//! 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,6 +1,5 @@
//! The contract source code. //! The contract source code.
pub mod evmla;
pub mod llvm_ir; pub mod llvm_ir;
pub mod yul; pub mod yul;
@@ -9,24 +8,17 @@ use std::collections::HashSet;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; 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 crate::yul::parser::statement::object::Object;
use self::evmla::EVMLA;
use self::llvm_ir::LLVMIR; use self::llvm_ir::LLVMIR;
use self::yul::Yul; use self::yul::Yul;
/// The contract source code. /// The contract source code.
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
#[allow(clippy::enum_variant_names)]
pub enum IR { pub enum IR {
/// The Yul source code. /// The Yul source code.
Yul(Yul), Yul(Yul),
/// The EVM legacy assembly source code.
EVMLA(EVMLA),
/// The LLVM IR source code. /// The LLVM IR source code.
LLVMIR(LLVMIR), LLVMIR(LLVMIR),
} }
@@ -37,11 +29,6 @@ impl IR {
Self::Yul(Yul::new(source_code, object)) 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. /// A shortcut constructor.
pub fn new_llvm_ir(path: String, source: String) -> Self { pub fn new_llvm_ir(path: String, source: String) -> Self {
Self::LLVMIR(LLVMIR::new(path, source)) Self::LLVMIR(LLVMIR::new(path, source))
@@ -51,7 +38,6 @@ impl IR {
pub fn get_missing_libraries(&self) -> HashSet<String> { pub fn get_missing_libraries(&self) -> HashSet<String> {
match self { match self {
Self::Yul(inner) => inner.get_missing_libraries(), Self::Yul(inner) => inner.get_missing_libraries(),
Self::EVMLA(inner) => inner.get_missing_libraries(),
Self::LLVMIR(_inner) => HashSet::new(), Self::LLVMIR(_inner) => HashSet::new(),
} }
} }
@@ -67,7 +53,6 @@ where
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match self { match self {
Self::Yul(inner) => inner.declare(context), Self::Yul(inner) => inner.declare(context),
Self::EVMLA(inner) => inner.declare(context),
Self::LLVMIR(_inner) => Ok(()), Self::LLVMIR(_inner) => Ok(()),
} }
} }
@@ -75,7 +60,6 @@ where
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> { fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
match self { match self {
Self::Yul(inner) => inner.into_llvm(context), Self::Yul(inner) => inner.into_llvm(context),
Self::EVMLA(inner) => inner.into_llvm(context),
Self::LLVMIR(_inner) => Ok(()), Self::LLVMIR(_inner) => Ok(()),
} }
} }
@@ -54,12 +54,10 @@ impl Contract {
/// Returns the contract identifier, which is: /// Returns the contract identifier, which is:
/// - the Yul object identifier for Yul /// - the Yul object identifier for Yul
/// - the full contract path for EVM legacy assembly
/// - the module name for LLVM IR /// - the module name for LLVM IR
pub fn identifier(&self) -> &str { pub fn identifier(&self) -> &str {
match self.ir { match self.ir {
IR::Yul(ref yul) => yul.object.identifier.as_str(), 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(), IR::LLVMIR(ref llvm_ir) => llvm_ir.path.as_str(),
} }
} }
@@ -68,7 +66,6 @@ impl Contract {
pub fn drain_factory_dependencies(&mut self) -> HashSet<String> { pub fn drain_factory_dependencies(&mut self) -> HashSet<String> {
match self.ir { match self.ir {
IR::Yul(ref mut yul) => yul.object.factory_dependencies.drain().collect(), 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(), IR::LLVMIR(_) => HashSet::new(),
} }
} }
@@ -129,10 +126,6 @@ impl Contract {
IR::Yul(_) => { IR::Yul(_) => {
context.set_yul_data(Default::default()); 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(_) => {} IR::LLVMIR(_) => {}
} }
+31 -57
View File
@@ -13,14 +13,14 @@ use path_slash::PathExt;
/// output directory. /// output directory.
/// Example: resolc ERC20.sol -O3 --bin --output-dir './build/' /// Example: resolc ERC20.sol -O3 --bin --output-dir './build/'
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[structopt(name = "The PolkaVM Solidity compiler")] #[command(name = "The PolkaVM Solidity compiler", arg_required_else_help = true)]
pub struct Arguments { pub struct Arguments {
/// Print the version and exit. /// Print the version and exit.
#[structopt(long = "version")] #[arg(long = "version")]
pub version: bool, pub version: bool,
/// Print the licence and exit. /// Print the licence and exit.
#[structopt(long = "license")] #[arg(long = "license")]
pub license: bool, pub license: bool,
/// Specify the input paths and remappings. /// Specify the input paths and remappings.
@@ -31,159 +31,141 @@ pub struct Arguments {
/// Set the given path as the root of the source tree instead of the root of the filesystem. /// Set the given path as the root of the source tree instead of the root of the filesystem.
/// Passed to `solc` without changes. /// Passed to `solc` without changes.
#[structopt(long = "base-path")] #[arg(long = "base-path")]
pub base_path: Option<String>, pub base_path: Option<String>,
/// Make an additional source directory available to the default import callback. /// 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. /// Can be used multiple times. Can only be used if the base path has a non-empty value.
/// Passed to `solc` without changes. /// Passed to `solc` without changes.
#[structopt(long = "include-path")] #[arg(long = "include-path")]
pub include_paths: Vec<String>, pub include_paths: Vec<String>,
/// Allow a given path for imports. A list of paths can be supplied by separating them with a comma. /// Allow a given path for imports. A list of paths can be supplied by separating them with a comma.
/// Passed to `solc` without changes. /// Passed to `solc` without changes.
#[structopt(long = "allow-paths")] #[arg(long = "allow-paths")]
pub allow_paths: Option<String>, pub allow_paths: Option<String>,
/// Create one file per component and contract/file at the specified directory, if given. /// Create one file per component and contract/file at the specified directory, if given.
#[structopt(short = 'o', long = "output-dir")] #[arg(short = 'o', long = "output-dir")]
pub output_directory: Option<PathBuf>, pub output_directory: Option<PathBuf>,
/// Overwrite existing files (used together with -o). /// Overwrite existing files (used together with -o).
#[structopt(long = "overwrite")] #[arg(long = "overwrite")]
pub overwrite: bool, pub overwrite: bool,
/// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z]. /// Set the optimization parameter -O[0 | 1 | 2 | 3 | s | z].
/// Use `3` for best performance and `z` for minimal size. /// Use `3` for best performance and `z` for minimal size.
#[structopt(short = 'O', long = "optimization")] #[arg(short = 'O', long = "optimization")]
pub optimization: Option<char>, pub optimization: Option<char>,
/// Try to recompile with -Oz if the bytecode is too large. /// Try to recompile with -Oz if the bytecode is too large.
#[structopt(long = "fallback-Oz")] #[arg(long = "fallback-Oz")]
pub fallback_to_optimizing_for_size: bool, pub fallback_to_optimizing_for_size: bool,
/// Disable the `solc` optimizer. /// Disable the `solc` optimizer.
/// Use it if your project uses the `MSIZE` instruction, or in other cases. /// Use it if your project uses the `MSIZE` instruction, or in other cases.
/// Beware that it will prevent libraries from being inlined. /// Beware that it will prevent libraries from being inlined.
#[structopt(long = "disable-solc-optimizer")] #[arg(long = "disable-solc-optimizer")]
pub disable_solc_optimizer: bool, pub disable_solc_optimizer: bool,
/// Specify the path to the `solc` executable. By default, the one in `${PATH}` is used. /// 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. /// 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. /// LLVM IR mode: `solc` is unused.
#[structopt(long = "solc")] #[arg(long = "solc")]
pub solc: Option<String>, pub solc: Option<String>,
/// The EVM target version to generate IR for. /// The EVM target version to generate IR for.
/// See https://github.com/paritytech/revive/blob/main/crates/common/src/evm_version.rs for reference. /// See https://github.com/paritytech/revive/blob/main/crates/common/src/evm_version.rs for reference.
#[structopt(long = "evm-version")] #[arg(long = "evm-version")]
pub evm_version: Option<String>, pub evm_version: Option<String>,
/// Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`. /// Specify addresses of deployable libraries. Syntax: `<libraryName>=<address> [, or whitespace] ...`.
/// Addresses are interpreted as hexadecimal strings prefixed with `0x`. /// Addresses are interpreted as hexadecimal strings prefixed with `0x`.
#[structopt(short = 'l', long = "libraries")] #[arg(short = 'l', long = "libraries")]
pub libraries: Vec<String>, pub libraries: Vec<String>,
/// Output a single JSON document containing the specified information. /// Output a single JSON document containing the specified information.
/// Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`. /// Available arguments: `abi`, `hashes`, `metadata`, `devdoc`, `userdoc`, `storage-layout`, `ast`, `asm`, `bin`, `bin-runtime`.
#[structopt(long = "combined-json")] #[arg(long = "combined-json")]
pub combined_json: Option<String>, pub combined_json: Option<String>,
/// Switch to standard JSON input/output mode. Read from stdin, write the result to stdout. /// Switch to standard JSON input/output mode. Read from stdin, write the result to stdout.
/// This is the default used by the Hardhat plugin. /// This is the default used by the Hardhat plugin.
#[structopt(long = "standard-json")] #[arg(long = "standard-json")]
pub standard_json: bool, pub standard_json: bool,
/// Switch to missing deployable libraries detection mode. /// Switch to missing deployable libraries detection mode.
/// Only available for standard JSON input/output mode. /// Only available for standard JSON input/output mode.
/// Contracts are not compiled in this mode, and all compilation artifacts are not included. /// Contracts are not compiled in this mode, and all compilation artifacts are not included.
#[structopt(long = "detect-missing-libraries")] #[arg(long = "detect-missing-libraries")]
pub detect_missing_libraries: bool, pub detect_missing_libraries: bool,
/// Switch to Yul mode. /// Switch to Yul mode.
/// Only one input Yul file is allowed. /// Only one input Yul file is allowed.
/// Cannot be used with combined and standard JSON modes. /// Cannot be used with combined and standard JSON modes.
#[structopt(long = "yul")] #[arg(long = "yul")]
pub yul: bool, pub yul: bool,
/// Switch to LLVM IR mode. /// Switch to LLVM IR mode.
/// Only one input LLVM IR file is allowed. /// Only one input LLVM IR file is allowed.
/// Cannot be used with combined and standard JSON modes. /// Cannot be used with combined and standard JSON modes.
/// Use this mode at your own risk, as LLVM IR input validation is not implemented. /// Use this mode at your own risk, as LLVM IR input validation is not implemented.
#[structopt(long = "llvm-ir")] #[arg(long = "llvm-ir")]
pub llvm_ir: bool, 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. /// Set metadata hash mode.
/// The only supported value is `none` that disables appending the metadata hash. /// The only supported value is `none` that disables appending the metadata hash.
/// Is enabled by default. /// Is enabled by default.
#[structopt(long = "metadata-hash")] #[arg(long = "metadata-hash")]
pub metadata_hash: Option<String>, pub metadata_hash: Option<String>,
/// Output PolkaVM assembly of the contracts. /// Output PolkaVM assembly of the contracts.
#[structopt(long = "asm")] #[arg(long = "asm")]
pub output_assembly: bool, pub output_assembly: bool,
/// Output PolkaVM bytecode of the contracts. /// Output PolkaVM bytecode of the contracts.
#[structopt(long = "bin")] #[arg(long = "bin")]
pub output_binary: bool, pub output_binary: bool,
/// Suppress specified warnings. /// Suppress specified warnings.
/// Available arguments: `ecrecover`, `sendtransfer`, `extcodesize`, `txorigin`, `blocktimestamp`, `blocknumber`, `blockhash`. /// Available arguments: `ecrecover`, `sendtransfer`, `extcodesize`, `txorigin`, `blocktimestamp`, `blocknumber`, `blockhash`.
#[structopt(long = "suppress-warnings")] #[arg(long = "suppress-warnings")]
pub suppress_warnings: Option<Vec<String>>, pub suppress_warnings: Option<Vec<String>>,
/// Generate source based debug information in the output code file. This only has an effect /// 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. /// with the LLVM-IR code generator and is ignored otherwise.
#[structopt(short = 'g')] #[arg(short = 'g')]
pub emit_source_debug_info: bool, pub emit_source_debug_info: bool,
/// Dump all IRs to files in the specified directory. /// Dump all IRs to files in the specified directory.
/// Only for testing and debugging. /// Only for testing and debugging.
#[structopt(long = "debug-output-dir")] #[arg(long = "debug-output-dir")]
pub debug_output_directory: Option<PathBuf>, pub debug_output_directory: Option<PathBuf>,
/// Set the verify-each option in LLVM. /// Set the verify-each option in LLVM.
/// Only for testing and debugging. /// Only for testing and debugging.
#[structopt(long = "llvm-verify-each")] #[arg(long = "llvm-verify-each")]
pub llvm_verify_each: bool, pub llvm_verify_each: bool,
/// Set the debug-logging option in LLVM. /// Set the debug-logging option in LLVM.
/// Only for testing and debugging. /// Only for testing and debugging.
#[structopt(long = "llvm-debug-logging")] #[arg(long = "llvm-debug-logging")]
pub llvm_debug_logging: bool, pub llvm_debug_logging: bool,
/// Run this process recursively and provide JSON input to compile a single contract. /// Run this process recursively and provide JSON input to compile a single contract.
/// Only for usage from within the compiler. /// Only for usage from within the compiler.
#[structopt(long = "recursive-process")] #[arg(long = "recursive-process")]
pub recursive_process: bool, pub recursive_process: bool,
/// Specify the input file to use instead of stdin when --recursive-process is given. /// Specify the input file to use instead of stdin when --recursive-process is given.
/// This is only intended for use when developing the compiler. /// This is only intended for use when developing the compiler.
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
#[structopt(long = "recursive-process-input")] #[arg(long = "recursive-process-input")]
pub recursive_process_input: Option<String>, pub recursive_process_input: Option<String>,
} }
impl Default for Arguments {
fn default() -> Self {
Self::new()
}
}
impl Arguments { impl Arguments {
/// A shortcut constructor.
pub fn new() -> Self {
Self::parse()
}
/// Validates the arguments. /// Validates the arguments.
#[allow(clippy::collapsible_if)]
pub fn validate(&self) -> anyhow::Result<()> { pub fn validate(&self) -> anyhow::Result<()> {
if self.version && std::env::args().count() > 2 { if self.version && std::env::args().count() > 2 {
anyhow::bail!("No other options are allowed while getting the compiler version."); anyhow::bail!("No other options are allowed while getting the compiler version.");
@@ -248,28 +230,20 @@ 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 { if self.disable_solc_optimizer {
anyhow::bail!("Disabling the solc optimizer is not supported in Yul, LLVM IR and PolkaVM assembly modes."); anyhow::bail!("Disabling the solc optimizer is not supported in Yul, LLVM IR and PolkaVM assembly modes.");
} }
} }
if self.llvm_ir { if self.llvm_ir && self.solc.is_some() {
if self.solc.is_some() {
anyhow::bail!("`solc` is not used in LLVM IR and PolkaVM assembly modes."); anyhow::bail!("`solc` is not used in LLVM IR and PolkaVM assembly modes.");
} }
}
if self.combined_json.is_some() { if self.combined_json.is_some() && (self.output_assembly || self.output_binary) {
if self.output_assembly || self.output_binary {
anyhow::bail!( anyhow::bail!(
"Cannot output assembly or binary outside of JSON in combined JSON mode." "Cannot output assembly or binary outside of JSON in combined JSON mode."
); );
} }
}
if self.standard_json { if self.standard_json {
if self.output_assembly || self.output_binary { if self.output_assembly || self.output_binary {
+25 -18
View File
@@ -2,6 +2,7 @@
pub mod arguments; pub mod arguments;
use std::io::Write;
use std::str::FromStr; use std::str::FromStr;
use revive_solidity::Process; use revive_solidity::Process;
@@ -16,28 +17,27 @@ const RAYON_WORKER_STACK_SIZE: usize = 16 * 1024 * 1024;
#[global_allocator] #[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
/// The application entry point. fn main() -> anyhow::Result<()> {
fn main() {
std::process::exit(match main_inner() { std::process::exit(match main_inner() {
Ok(()) => revive_common::EXIT_CODE_SUCCESS, Ok(()) => revive_common::EXIT_CODE_SUCCESS,
Err(error) => { Err(error) => {
eprintln!("{error}"); writeln!(std::io::stderr(), "{error}")?;
revive_common::EXIT_CODE_FAILURE revive_common::EXIT_CODE_FAILURE
} }
}) })
} }
/// The auxiliary `main` function to facilitate the `?` error conversion operator.
fn main_inner() -> anyhow::Result<()> { fn main_inner() -> anyhow::Result<()> {
let arguments = Arguments::new(); let arguments = <Arguments as clap::Parser>::try_parse()?;
arguments.validate()?; arguments.validate()?;
if arguments.version { if arguments.version {
println!( writeln!(
std::io::stdout(),
"{} version {}", "{} version {}",
env!("CARGO_PKG_DESCRIPTION"), env!("CARGO_PKG_DESCRIPTION"),
revive_solidity::ResolcVersion::default().long revive_solidity::ResolcVersion::default().long
); )?;
return Ok(()); return Ok(());
} }
@@ -45,7 +45,7 @@ fn main_inner() -> anyhow::Result<()> {
let license_mit = include_str!("../../../../LICENSE-MIT"); let license_mit = include_str!("../../../../LICENSE-MIT");
let license_apache = include_str!("../../../../LICENSE-APACHE"); let license_apache = include_str!("../../../../LICENSE-APACHE");
println!("{}\n{}\n", license_mit, license_apache); writeln!(std::io::stdout(), "{}\n{}\n", license_mit, license_apache)?;
return Ok(()); return Ok(());
} }
@@ -103,7 +103,7 @@ fn main_inner() -> anyhow::Result<()> {
let mut solc = { let mut solc = {
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
{ {
revive_solidity::SoljsonCompiler { version: None } revive_solidity::SoljsonCompiler
} }
#[cfg(not(target_os = "emscripten"))] #[cfg(not(target_os = "emscripten"))]
@@ -157,7 +157,6 @@ fn main_inner() -> anyhow::Result<()> {
revive_solidity::standard_json( revive_solidity::standard_json(
&mut solc, &mut solc,
arguments.detect_missing_libraries, arguments.detect_missing_libraries,
arguments.force_evmla,
arguments.base_path, arguments.base_path,
arguments.include_paths, arguments.include_paths,
arguments.allow_paths, arguments.allow_paths,
@@ -173,7 +172,6 @@ fn main_inner() -> anyhow::Result<()> {
evm_version, evm_version,
!arguments.disable_solc_optimizer, !arguments.disable_solc_optimizer,
optimizer_settings, optimizer_settings,
arguments.force_evmla,
include_metadata_hash, include_metadata_hash,
arguments.base_path, arguments.base_path,
arguments.include_paths, arguments.include_paths,
@@ -193,7 +191,6 @@ fn main_inner() -> anyhow::Result<()> {
evm_version, evm_version,
!arguments.disable_solc_optimizer, !arguments.disable_solc_optimizer,
optimizer_settings, optimizer_settings,
arguments.force_evmla,
include_metadata_hash, include_metadata_hash,
arguments.base_path, arguments.base_path,
arguments.include_paths, arguments.include_paths,
@@ -214,26 +211,36 @@ fn main_inner() -> anyhow::Result<()> {
arguments.overwrite, arguments.overwrite,
)?; )?;
eprintln!( writeln!(
std::io::stderr(),
"Compiler run successful. Artifact(s) can be found in directory {output_directory:?}." "Compiler run successful. Artifact(s) can be found in directory {output_directory:?}."
); )?;
} else if arguments.output_assembly || arguments.output_binary { } else if arguments.output_assembly || arguments.output_binary {
for (path, contract) in build.contracts.into_iter() { for (path, contract) in build.contracts.into_iter() {
if arguments.output_assembly { if arguments.output_assembly {
let assembly_text = contract.build.assembly_text; let assembly_text = contract.build.assembly_text;
println!("Contract `{}` assembly:\n\n{}", path, assembly_text); writeln!(
std::io::stdout(),
"Contract `{}` assembly:\n\n{}",
path,
assembly_text
)?;
} }
if arguments.output_binary { if arguments.output_binary {
println!( writeln!(
std::io::stdout(),
"Contract `{}` bytecode: 0x{}", "Contract `{}` bytecode: 0x{}",
path, path,
hex::encode(contract.build.bytecode) hex::encode(contract.build.bytecode)
); )?;
} }
} }
} else { } else {
eprintln!("Compiler run successful. No output requested. Use --asm and --bin flags."); writeln!(
std::io::stderr(),
"Compiler run successful. No output requested. Use --asm and --bin flags."
)?;
} }
Ok(()) Ok(())
@@ -80,10 +80,9 @@ impl CombinedJson {
file_path.push(format!("combined.{}", revive_common::EXTENSION_JSON)); file_path.push(format!("combined.{}", revive_common::EXTENSION_JSON));
if file_path.exists() && !overwrite { if file_path.exists() && !overwrite {
eprintln!( anyhow::bail!(
"Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)." "Refusing to overwrite an existing file {file_path:?} (use --overwrite to force)."
); );
return Ok(());
} }
File::create(&file_path) File::create(&file_path)
+3 -18
View File
@@ -1,7 +1,6 @@
//! The Solidity compiler. //! The Solidity compiler.
pub mod combined_json; pub mod combined_json;
pub mod pipeline;
#[cfg(not(target_os = "emscripten"))] #[cfg(not(target_os = "emscripten"))]
pub mod solc_compiler; pub mod solc_compiler;
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
@@ -9,35 +8,22 @@ pub mod soljson_compiler;
pub mod standard_json; pub mod standard_json;
pub mod version; pub mod version;
use once_cell::sync::Lazy;
use semver::VersionReq;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use self::combined_json::CombinedJson; use self::combined_json::CombinedJson;
use self::pipeline::Pipeline;
use self::standard_json::input::Input as StandardJsonInput; use self::standard_json::input::Input as StandardJsonInput;
use self::standard_json::output::Output as StandardJsonOutput; use self::standard_json::output::Output as StandardJsonOutput;
use self::version::Version; use self::version::Version;
/// The first version of `solc` with the support of standard JSON interface. /// The first version of `solc` with the support of standard JSON interface.
pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 4, 12); pub const FIRST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 0);
/// 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`. /// The last supported version of `solc`.
pub const LAST_SUPPORTED_VERSION: semver::Version = semver::Version::new(0, 8, 28); 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 0.8.8 <https://github.com/ethereum/solidity/releases/tag/v0.8.8> /// `--include-path` was introduced in solc `0.8.8` <https://github.com/ethereum/solidity/releases/tag/v0.8.8>
pub static FIRST_SUPPORTS_INCLUDE_PATH: Lazy<VersionReq> = pub const FIRST_INCLUDE_PATH_VERSION: semver::Version = semver::Version::new(0, 8, 8);
Lazy::new(|| VersionReq::parse(">=0.8.8").unwrap());
/// The Solidity compiler. /// The Solidity compiler.
pub trait Compiler { pub trait Compiler {
@@ -45,7 +31,6 @@ pub trait Compiler {
fn standard_json( fn standard_json(
&mut self, &mut self,
input: StandardJsonInput, input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
-27
View File
@@ -1,27 +0,0 @@
//! 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
}
}
}
+20 -55
View File
@@ -1,24 +1,20 @@
//! The Solidity compiler. //! The Solidity compiler solc interface.
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson; 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::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput; use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version; use crate::solc::version::Version;
use super::Compiler; use super::Compiler;
use crate::solc::{FIRST_SUPPORTS_BASE_PATH, FIRST_SUPPORTS_INCLUDE_PATH};
/// The Solidity compiler. /// The Solidity compiler.
pub struct SolcCompiler { pub struct SolcCompiler {
/// The binary executable name. /// The binary executable name.
pub executable: String, pub executable: String,
/// The lazily-initialized compiler version.
pub version: Option<Version>,
} }
impl SolcCompiler { impl SolcCompiler {
@@ -35,10 +31,7 @@ impl SolcCompiler {
error error
); );
} }
Ok(Self { Ok(Self { executable })
executable,
version: None,
})
} }
} }
@@ -47,45 +40,31 @@ impl Compiler for SolcCompiler {
fn standard_json( fn standard_json(
&mut self, &mut self,
mut input: StandardJsonInput, mut input: StandardJsonInput,
pipeline: Pipeline,
base_path: Option<String>, base_path: Option<String>,
include_paths: Vec<String>, include_paths: Vec<String>,
allow_paths: Option<String>, allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> { ) -> anyhow::Result<StandardJsonOutput> {
let version = self.version()?; let version = self.version()?.validate(&include_paths)?.default;
let mut command = std::process::Command::new(self.executable.as_str()); let mut command = std::process::Command::new(self.executable.as_str());
command.stdin(std::process::Stdio::piped()); command.stdin(std::process::Stdio::piped());
command.stdout(std::process::Stdio::piped()); command.stdout(std::process::Stdio::piped());
command.arg("--standard-json"); 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() { for include_path in include_paths.into_iter() {
command.arg("--include-path"); command.arg("--include-path");
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 { if let Some(allow_paths) = allow_paths {
command.arg("--allow-paths"); command.arg("--allow-paths");
command.arg(allow_paths); command.arg(allow_paths);
} }
input.normalize(&version.default); input.normalize(&version);
let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default(); let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
@@ -129,7 +108,7 @@ impl Compiler for SolcCompiler {
), ),
) )
})?; })?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?; output.preprocess_ast(suppressed_warnings.as_slice())?;
Ok(output) Ok(output)
} }
@@ -163,8 +142,16 @@ impl Compiler for SolcCompiler {
anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error) anyhow::anyhow!("{} subprocess error: {:?}", self.executable, error)
})?; })?;
if !output.status.success() { if !output.status.success() {
println!("{}", String::from_utf8_lossy(output.stdout.as_slice())); writeln!(
println!("{}", String::from_utf8_lossy(output.stderr.as_slice())); std::io::stdout(),
"{}",
String::from_utf8_lossy(output.stdout.as_slice())
)?;
writeln!(
std::io::stdout(),
"{}",
String::from_utf8_lossy(output.stderr.as_slice())
)?;
anyhow::bail!( anyhow::bail!(
"{} error: {}", "{} error: {}",
self.executable, self.executable,
@@ -228,10 +215,6 @@ impl Compiler for SolcCompiler {
/// The `solc --version` mini-parser. /// The `solc --version` mini-parser.
fn version(&mut self) -> anyhow::Result<Version> { 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()); let mut command = std::process::Command::new(self.executable.as_str());
command.arg("--version"); command.arg("--version");
let output = command.output().map_err(|error| { let output = command.output().map_err(|error| {
@@ -277,24 +260,6 @@ impl Compiler for SolcCompiler {
.and_then(|line| line.split('-').nth(1)) .and_then(|line| line.split('-').nth(1))
.and_then(|version| version.parse().ok()); .and_then(|version| version.parse().ok());
let version = Version::new(long, default, l2_revision); Ok(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)
} }
} }
+20 -34
View File
@@ -1,10 +1,9 @@
//! The Solidity compiler. //! The Solidity compiler solJson interface.
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use crate::solc::combined_json::CombinedJson; 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::input::Input as StandardJsonInput;
use crate::solc::standard_json::output::Output as StandardJsonOutput; use crate::solc::standard_json::output::Output as StandardJsonOutput;
use crate::solc::version::Version; use crate::solc::version::Version;
@@ -19,23 +18,30 @@ extern "C" {
} }
/// The Solidity compiler. /// The Solidity compiler.
pub struct SoljsonCompiler { pub struct SoljsonCompiler;
/// The lazily-initialized compiler version.
pub version: Option<Version>,
}
impl Compiler for SoljsonCompiler { impl Compiler for SoljsonCompiler {
/// Compiles the Solidity `--standard-json` input into Yul IR. /// Compiles the Solidity `--standard-json` input into Yul IR.
fn standard_json( fn standard_json(
&mut self, &mut self,
mut input: StandardJsonInput, mut input: StandardJsonInput,
pipeline: Pipeline, base_path: Option<String>,
_base_path: Option<String>, include_paths: Vec<String>,
_include_paths: Vec<String>, allow_paths: Option<String>,
_allow_paths: Option<String>,
) -> anyhow::Result<StandardJsonOutput> { ) -> anyhow::Result<StandardJsonOutput> {
let version = self.version()?; if !include_paths.is_empty() {
input.normalize(&version.default); 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 suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default(); let suppressed_warnings = input.suppressed_warnings.take().unwrap_or_default();
let input_json = serde_json::to_string(&input).expect("Always valid"); let input_json = serde_json::to_string(&input).expect("Always valid");
@@ -50,7 +56,7 @@ impl Compiler for SoljsonCompiler {
.unwrap_or_else(|_| String::from_utf8_lossy(out.as_bytes()).to_string()), .unwrap_or_else(|_| String::from_utf8_lossy(out.as_bytes()).to_string()),
) )
})?; })?;
output.preprocess_ast(&version, pipeline, suppressed_warnings.as_slice())?; output.preprocess_ast(suppressed_warnings.as_slice())?;
Ok(output) Ok(output)
} }
@@ -76,31 +82,11 @@ impl Compiler for SoljsonCompiler {
.ok_or_else(|| anyhow::anyhow!("Soljson version parsing: metadata dropping"))? .ok_or_else(|| anyhow::anyhow!("Soljson version parsing: metadata dropping"))?
.parse() .parse()
.map_err(|error| anyhow::anyhow!("Soljson version parsing: {}", error))?; .map_err(|error| anyhow::anyhow!("Soljson version parsing: {}", error))?;
let l2_revision: Option<semver::Version> = version let l2_revision: Option<semver::Version> = version
.split('-') .split('-')
.nth(1) .nth(1)
.and_then(|version| version.parse().ok()); .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,7 +13,6 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; 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::metadata::Metadata as SolcStandardJsonInputSettingsMetadata;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
@@ -40,13 +39,13 @@ pub struct Input {
impl Input { impl Input {
/// A shortcut constructor from stdin. /// A shortcut constructor from stdin.
pub fn try_from_stdin(solc_pipeline: SolcPipeline) -> anyhow::Result<Self> { pub fn try_from_stdin() -> anyhow::Result<Self> {
let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?; let mut input: Self = serde_json::from_reader(std::io::BufReader::new(std::io::stdin()))?;
input input
.settings .settings
.output_selection .output_selection
.get_or_insert_with(SolcStandardJsonInputSettingsSelection::default) .get_or_insert_with(SolcStandardJsonInputSettingsSelection::default)
.extend_with_required(solc_pipeline); .extend_with_required();
Ok(input) Ok(input)
} }
@@ -61,15 +60,16 @@ impl Input {
output_selection: SolcStandardJsonInputSettingsSelection, output_selection: SolcStandardJsonInputSettingsSelection,
optimizer: SolcStandardJsonInputSettingsOptimizer, optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>, metadata: Option<SolcStandardJsonInputSettingsMetadata>,
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
#[cfg(feature = "parallel")] let mut paths: BTreeSet<PathBuf> = paths.iter().cloned().collect();
let iter = paths.into_par_iter(); // Parallel iterator let libraries = Settings::parse_libraries(library_map)?;
for library_file in libraries.keys() {
paths.insert(PathBuf::from(library_file));
}
#[cfg(not(feature = "parallel"))] let sources = paths
let iter = paths.iter(); // Sequential iterator .iter()
let sources = iter
.map(|path| { .map(|path| {
let source = Source::try_from(path.as_path()).unwrap_or_else(|error| { let source = Source::try_from(path.as_path()).unwrap_or_else(|error| {
panic!("Source code file {path:?} reading error: {error}") panic!("Source code file {path:?} reading error: {error}")
@@ -78,8 +78,6 @@ impl Input {
}) })
.collect(); .collect();
let libraries = Settings::parse_libraries(library_map)?;
Ok(Self { Ok(Self {
language, language,
sources, sources,
@@ -88,7 +86,6 @@ impl Input {
libraries, libraries,
remappings, remappings,
output_selection, output_selection,
via_ir,
optimizer, optimizer,
metadata, metadata,
), ),
@@ -107,7 +104,6 @@ impl Input {
output_selection: SolcStandardJsonInputSettingsSelection, output_selection: SolcStandardJsonInputSettingsSelection,
optimizer: SolcStandardJsonInputSettingsOptimizer, optimizer: SolcStandardJsonInputSettingsOptimizer,
metadata: Option<SolcStandardJsonInputSettingsMetadata>, metadata: Option<SolcStandardJsonInputSettingsMetadata>,
via_ir: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
@@ -127,7 +123,6 @@ impl Input {
libraries, libraries,
remappings, remappings,
output_selection, output_selection,
via_ir,
optimizer, optimizer,
metadata, metadata,
), ),
@@ -51,7 +51,6 @@ impl Settings {
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
output_selection: Selection, output_selection: Selection,
via_ir: bool,
optimizer: Optimizer, optimizer: Optimizer,
metadata: Option<Metadata>, metadata: Option<Metadata>,
) -> Self { ) -> Self {
@@ -60,9 +59,9 @@ impl Settings {
libraries: Some(libraries), libraries: Some(libraries),
remappings, remappings,
output_selection: Some(output_selection), output_selection: Some(output_selection),
via_ir: if via_ir { Some(true) } else { None },
optimizer, optimizer,
metadata, metadata,
via_ir: Some(true),
} }
} }
@@ -3,12 +3,8 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
/// The `solc --standard-json` expected output selection flag. /// The `solc --standard-json` expected output selection flag.
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
pub enum Flag { pub enum Flag {
/// The ABI JSON. /// The ABI JSON.
#[serde(rename = "abi")] #[serde(rename = "abi")]
@@ -46,15 +42,6 @@ pub enum Flag {
Assembly, 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 { impl std::fmt::Display for Flag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
@@ -7,8 +7,6 @@ use std::collections::HashSet;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use self::flag::Flag as SelectionFlag; use self::flag::Flag as SelectionFlag;
/// The `solc --standard-json` output file selection. /// The `solc --standard-json` output file selection.
@@ -24,7 +22,7 @@ pub struct File {
impl File { impl File {
/// Creates the selection required by our compilation process. /// Creates the selection required by our compilation process.
pub fn new_required(pipeline: SolcPipeline) -> Self { pub fn new_required() -> Self {
Self { Self {
per_file: Some(HashSet::from_iter([SelectionFlag::AST])), per_file: Some(HashSet::from_iter([SelectionFlag::AST])),
per_contract: Some(HashSet::from_iter([ per_contract: Some(HashSet::from_iter([
@@ -32,14 +30,14 @@ impl File {
SelectionFlag::EVMDBC, SelectionFlag::EVMDBC,
SelectionFlag::MethodIdentifiers, SelectionFlag::MethodIdentifiers,
SelectionFlag::Metadata, SelectionFlag::Metadata,
SelectionFlag::from(pipeline), SelectionFlag::Yul,
])), ])),
} }
} }
/// Extends the user's output selection with flag required by our compilation process. /// Extends the user's output selection with flag required by our compilation process.
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self { pub fn extend_with_required(&mut self) -> &mut Self {
let required = Self::new_required(pipeline); let required = Self::new_required();
self.per_file self.per_file
.get_or_insert_with(HashSet::default) .get_or_insert_with(HashSet::default)
@@ -5,8 +5,6 @@ pub mod file;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use self::file::File as FileSelection; use self::file::File as FileSelection;
/// The `solc --standard-json` output selection. /// The `solc --standard-json` output selection.
@@ -19,17 +17,17 @@ pub struct Selection {
impl Selection { impl Selection {
/// Creates the selection required by our compilation process. /// Creates the selection required by our compilation process.
pub fn new_required(pipeline: SolcPipeline) -> Self { pub fn new_required() -> Self {
Self { Self {
all: Some(FileSelection::new_required(pipeline)), all: Some(FileSelection::new_required()),
} }
} }
/// Extends the user's output selection with flag required by our compilation process. /// Extends the user's output selection with flag required by our compilation process.
pub fn extend_with_required(&mut self, pipeline: SolcPipeline) -> &mut Self { pub fn extend_with_required(&mut self) -> &mut Self {
self.all self.all
.get_or_insert_with(|| FileSelection::new_required(pipeline)) .get_or_insert_with(FileSelection::new_required)
.extend_with_required(pipeline); .extend_with_required();
self self
} }
} }
@@ -1,46 +0,0 @@
//! 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
}
}
@@ -1,22 +0,0 @@
//! 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,27 +1,20 @@
//! The `solc --standard-json` output contract EVM data. //! The `solc --standard-json` output contract EVM data.
pub mod bytecode; pub mod bytecode;
pub mod extra_metadata;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::evmla::assembly::Assembly;
use self::bytecode::Bytecode; use self::bytecode::Bytecode;
use self::bytecode::DeployedBytecode; use self::bytecode::DeployedBytecode;
use self::extra_metadata::ExtraMetadata;
/// The `solc --standard-json` output contract EVM data. /// The `solc --standard-json` output contract EVM data.
/// It is replaced by PolkaVM data after compiling. /// It is replaced by PolkaVM data after compiling.
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct EVM { 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. /// The contract PolkaVM assembly code.
#[serde(rename = "assembly", skip_serializing_if = "Option::is_none")] #[serde(rename = "assembly", skip_serializing_if = "Option::is_none")]
pub assembly_text: Option<String>, pub assembly_text: Option<String>,
@@ -37,9 +30,6 @@ pub struct EVM {
/// The contract function signatures. /// The contract function signatures.
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub method_identifiers: Option<BTreeMap<String, String>>, 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 { impl EVM {
@@ -33,12 +33,11 @@ impl Error {
/// Returns the `ecrecover` function usage warning. /// Returns the `ecrecover` function usage warning.
pub fn message_ecrecover(src: Option<&str>) -> Self { pub fn message_ecrecover(src: Option<&str>) -> Self {
let message = r#" let message = r#"
Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.
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
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
to rely on the fact that the account has an ECDSA private key attached to it since accounts might implement other signature schemes.
implement other signature schemes. "#
"#
.to_owned(); .to_owned();
Self { Self {
@@ -55,16 +54,15 @@ impl Error {
/// Returns the `<address payable>`'s `send` and `transfer` methods usage error. /// Returns the `<address payable>`'s `send` and `transfer` methods usage error.
pub fn message_send_and_transfer(src: Option<&str>) -> Self { pub fn message_send_and_transfer(src: Option<&str>) -> Self {
let message = r#" let message = r#"
Warning: It looks like you are using '<address payable>.send/transfer(<X>)'.
Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing Using '<address payable>.send/transfer(<X>)' is deprecated and strongly discouraged!
the gas amount. Such calls will fail depending on the pubdata costs. The resolc compiler uses a heuristic to detect '<address payable>.send/transfer(<X>)' calls,
This might be a false positive if you are using an interface (like IERC20) instead of the which disables call re-entrancy and supplies all remaining gas instead of the 2300 gas stipend.
native Solidity `send/transfer`. However, detection is not guaranteed. You are advised to carefully test this, employ
Please use 'payable(<address>).call{value: <X>}("")' instead, but be careful with the reentrancy re-entrancy guards or use the withdrawal pattern instead!
attack. `send` and `transfer` send limited amount of gas that prevents reentrancy, whereas Learn more on https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy
`<address>.call{value: <X>}` sends all gas to the callee. Learn more on and https://docs.soliditylang.org/en/latest/common-patterns.html#withdrawal-from-contracts
https://docs.soliditylang.org/en/latest/security-considerations.html#reentrancy │ "#
"#
.to_owned(); .to_owned();
Self { Self {
@@ -81,15 +79,14 @@ impl Error {
/// Returns the `extcodesize` instruction usage warning. /// Returns the `extcodesize` instruction usage warning.
pub fn message_extcodesize(src: Option<&str>) -> Self { pub fn message_extcodesize(src: Option<&str>) -> Self {
let message = r#" let message = r#"
Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is
Warning: Your code or one of its dependencies uses the 'extcodesize' instruction, which is usually needed in the following cases:
usually needed in the following cases: 1. To detect whether an address belongs to a smart contract.
1. To detect whether an address belongs to a smart contract. 2. To detect whether the deploy code execution has finished.
2. To detect whether the deploy code execution has finished. Polkadot comes with native account abstraction support (so smart contracts are just accounts
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
coverned by code), and you should avoid differentiating between contracts and non-contract | addresses.
| addresses. "#
"#
.to_owned(); .to_owned();
Self { Self {
@@ -106,12 +103,11 @@ impl Error {
/// Returns the `origin` instruction usage warning. /// Returns the `origin` instruction usage warning.
pub fn message_tx_origin(src: Option<&str>) -> Self { pub fn message_tx_origin(src: Option<&str>) -> Self {
let message = r#" let message = r#"
Warning: You are checking for 'tx.origin' in your code, which might lead to unexpected behavior.
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
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
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 rely on tx.origin, but use msg.sender instead. "#
"#
.to_owned(); .to_owned();
Self { Self {
@@ -125,26 +121,6 @@ impl Error {
} }
} }
/// 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.. /// Appends the contract path to the message..
pub fn push_contract_path(&mut self, path: &str) { pub fn push_contract_path(&mut self, path: &str) {
self.formatted_message self.formatted_message
@@ -10,12 +10,9 @@ use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use sha3::Digest; 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::ir::IR as ProjectContractIR;
use crate::project::contract::Contract as ProjectContract; use crate::project::contract::Contract as ProjectContract;
use crate::project::Project; use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::version::Version as SolcVersion; use crate::solc::version::Version as SolcVersion;
use crate::warning::Warning; use crate::warning::Warning;
use crate::yul::lexer::Lexer; use crate::yul::lexer::Lexer;
@@ -53,14 +50,9 @@ impl Output {
&mut self, &mut self,
source_code_files: BTreeMap<String, String>, source_code_files: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
solc_version: &SolcVersion, solc_version: &SolcVersion,
debug_config: &revive_llvm_context::DebugConfig, debug_config: &revive_llvm_context::DebugConfig,
) -> anyhow::Result<Project> { ) -> anyhow::Result<Project> {
if let SolcPipeline::EVMLA = pipeline {
self.preprocess_dependencies()?;
}
let files = match self.contracts.as_ref() { let files = match self.contracts.as_ref() {
Some(files) => files, Some(files) => files,
None => match &self.errors { None => match &self.errors {
@@ -76,8 +68,6 @@ impl Output {
for (name, contract) in contracts.iter() { for (name, contract) in contracts.iter() {
let full_path = format!("{path}:{name}"); let full_path = format!("{path}:{name}");
let source = match pipeline {
SolcPipeline::Yul => {
let ir_optimized = match contract.ir_optimized.to_owned() { let ir_optimized = match contract.ir_optimized.to_owned() {
Some(ir_optimized) => ir_optimized, Some(ir_optimized) => ir_optimized,
None => continue, None => continue,
@@ -93,21 +83,7 @@ impl Output {
anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error) anyhow::anyhow!("Contract `{}` parsing error: {:?}", full_path, error)
})?; })?;
ProjectContractIR::new_yul(ir_optimized.to_owned(), object) let source = 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)
}
};
let source_code = source_code_files let source_code = source_code_files
.get(path.as_str()) .get(path.as_str())
@@ -133,12 +109,7 @@ impl Output {
} }
/// Traverses the AST and returns the list of additional errors and warnings. /// Traverses the AST and returns the list of additional errors and warnings.
pub fn preprocess_ast( pub fn preprocess_ast(&mut self, suppressed_warnings: &[Warning]) -> anyhow::Result<()> {
&mut self,
version: &SolcVersion,
pipeline: SolcPipeline,
suppressed_warnings: &[Warning],
) -> anyhow::Result<()> {
let sources = match self.sources.as_ref() { let sources = match self.sources.as_ref() {
Some(sources) => sources, Some(sources) => sources,
None => return Ok(()), None => return Ok(()),
@@ -147,8 +118,7 @@ impl Output {
let mut messages = Vec::new(); let mut messages = Vec::new();
for (path, source) in sources.iter() { for (path, source) in sources.iter() {
if let Some(ast) = source.ast.as_ref() { if let Some(ast) = source.ast.as_ref() {
let mut polkavm_messages = let mut polkavm_messages = Source::get_messages(ast, suppressed_warnings);
Source::get_messages(ast, version, pipeline, suppressed_warnings);
for message in polkavm_messages.iter_mut() { for message in polkavm_messages.iter_mut() {
message.push_contract_path(path.as_str()); message.push_contract_path(path.as_str());
} }
@@ -165,83 +135,4 @@ impl Output {
Ok(()) 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,9 +3,7 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError; use crate::solc::standard_json::output::error::Error as SolcStandardJsonOutputError;
use crate::solc::version::Version as SolcVersion;
use crate::warning::Warning; use crate::warning::Warning;
/// The `solc --standard-json` output source. /// The `solc --standard-json` output source.
@@ -132,37 +130,9 @@ 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. /// Returns the list of messages for some specific parts of the AST.
pub fn get_messages( pub fn get_messages(
ast: &serde_json::Value, ast: &serde_json::Value,
version: &SolcVersion,
pipeline: SolcPipeline,
suppressed_warnings: &[Warning], suppressed_warnings: &[Warning],
) -> Vec<SolcStandardJsonOutputError> { ) -> Vec<SolcStandardJsonOutputError> {
let mut messages = Vec::new(); let mut messages = Vec::new();
@@ -189,31 +159,16 @@ impl Source {
messages.push(message); 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 { match ast {
serde_json::Value::Array(array) => { serde_json::Value::Array(array) => {
for element in array.iter() { for element in array.iter() {
messages.extend(Self::get_messages( messages.extend(Self::get_messages(element, suppressed_warnings));
element,
version,
pipeline,
suppressed_warnings,
));
} }
} }
serde_json::Value::Object(object) => { serde_json::Value::Object(object) => {
for (_key, value) in object.iter() { for (_key, value) in object.iter() {
messages.extend(Self::get_messages( messages.extend(Self::get_messages(value, suppressed_warnings));
value,
version,
pipeline,
suppressed_warnings,
));
} }
} }
_ => {} _ => {}
+22
View File
@@ -36,4 +36,26 @@ impl Version {
l2_revision: None, 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)
}
} }
+15 -38
View File
@@ -8,7 +8,6 @@ use std::sync::Mutex;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::project::Project; use crate::project::Project;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::solc::solc_compiler::SolcCompiler; use crate::solc::solc_compiler::SolcCompiler;
use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer; use crate::solc::standard_json::input::settings::optimizer::Optimizer as SolcStandardJsonInputSettingsOptimizer;
use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection; use crate::solc::standard_json::input::settings::selection::Selection as SolcStandardJsonInputSettingsSelection;
@@ -30,8 +29,8 @@ const DEBUG_CONFIG: revive_llvm_context::DebugConfig =
#[derive(Hash, PartialEq, Eq)] #[derive(Hash, PartialEq, Eq)]
struct CachedBlob { struct CachedBlob {
contract_name: String, contract_name: String,
solidity: String,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
} }
/// Checks if the required executables are present in `${PATH}`. /// Checks if the required executables are present in `${PATH}`.
@@ -54,17 +53,9 @@ pub fn build_solidity(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
) -> anyhow::Result<SolcStandardJsonOutput> { ) -> anyhow::Result<SolcStandardJsonOutput> {
build_solidity_with_options( build_solidity_with_options(sources, libraries, remappings, optimizer_settings, true)
sources,
libraries,
remappings,
pipeline,
optimizer_settings,
true,
)
} }
/// Builds the Solidity project and returns the standard JSON output. /// Builds the Solidity project and returns the standard JSON output.
@@ -74,7 +65,6 @@ pub fn build_solidity_with_options(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
optimizer_settings: revive_llvm_context::OptimizerSettings, optimizer_settings: revive_llvm_context::OptimizerSettings,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
) -> anyhow::Result<SolcStandardJsonOutput> { ) -> anyhow::Result<SolcStandardJsonOutput> {
@@ -93,7 +83,7 @@ pub fn build_solidity_with_options(
sources.clone(), sources.clone(),
libraries.clone(), libraries.clone(),
remappings, remappings,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new( SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled, solc_optimizer_enabled,
None, None,
@@ -101,14 +91,12 @@ pub fn build_solidity_with_options(
false, false,
), ),
None, None,
pipeline == SolcPipeline::Yul,
None, None,
)?; )?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, None, vec![], None)?;
let project = let project = output.try_to_project(sources, libraries, &solc_version, &DEBUG_CONFIG)?;
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let build: crate::Build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?; let build: crate::Build = project.compile(optimizer_settings, false, DEBUG_CONFIG)?;
build.write_to_standard_json(&mut output, &solc_version)?; build.write_to_standard_json(&mut output, &solc_version)?;
@@ -121,7 +109,6 @@ pub fn build_solidity_with_options_evm(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
remappings: Option<BTreeSet<String>>, remappings: Option<BTreeSet<String>>,
pipeline: SolcPipeline,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
) -> anyhow::Result<BTreeMap<String, (Bytecode, DeployedBytecode)>> { ) -> anyhow::Result<BTreeMap<String, (Bytecode, DeployedBytecode)>> {
check_dependencies(); check_dependencies();
@@ -139,7 +126,7 @@ pub fn build_solidity_with_options_evm(
sources.clone(), sources.clone(),
libraries.clone(), libraries.clone(),
remappings, remappings,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new( SolcStandardJsonInputSettingsOptimizer::new(
solc_optimizer_enabled, solc_optimizer_enabled,
None, None,
@@ -147,11 +134,10 @@ pub fn build_solidity_with_options_evm(
false, false,
), ),
None, None,
pipeline == SolcPipeline::Yul,
None, None,
)?; )?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, None, vec![], None)?;
let mut contracts = BTreeMap::new(); let mut contracts = BTreeMap::new();
if let Some(files) = output.contracts.as_mut() { if let Some(files) = output.contracts.as_mut() {
@@ -176,7 +162,6 @@ pub fn build_solidity_with_options_evm(
pub fn build_solidity_and_detect_missing_libraries( pub fn build_solidity_and_detect_missing_libraries(
sources: BTreeMap<String, String>, sources: BTreeMap<String, String>,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
) -> anyhow::Result<SolcStandardJsonOutput> { ) -> anyhow::Result<SolcStandardJsonOutput> {
check_dependencies(); check_dependencies();
@@ -193,17 +178,15 @@ pub fn build_solidity_and_detect_missing_libraries(
sources.clone(), sources.clone(),
libraries.clone(), libraries.clone(),
None, None,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false), SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None, None,
pipeline == SolcPipeline::Yul,
None, None,
)?; )?;
let mut output = solc.standard_json(input, pipeline, None, vec![], None)?; let mut output = solc.standard_json(input, None, vec![], None)?;
let project = let project = output.try_to_project(sources, libraries, &solc_version, &DEBUG_CONFIG)?;
output.try_to_project(sources, libraries, pipeline, &solc_version, &DEBUG_CONFIG)?;
let missing_libraries = project.get_missing_libraries(); let missing_libraries = project.get_missing_libraries();
missing_libraries.write_to_standard_json(&mut output, &solc.version()?)?; missing_libraries.write_to_standard_json(&mut output, &solc.version()?)?;
@@ -234,7 +217,6 @@ pub fn check_solidity_warning(
source_code: &str, source_code: &str,
warning_substring: &str, warning_substring: &str,
libraries: BTreeMap<String, BTreeMap<String, String>>, libraries: BTreeMap<String, BTreeMap<String, String>>,
pipeline: SolcPipeline,
skip_for_revive_edition: bool, skip_for_revive_edition: bool,
suppressed_warnings: Option<Vec<Warning>>, suppressed_warnings: Option<Vec<Warning>>,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
@@ -253,14 +235,13 @@ pub fn check_solidity_warning(
sources.clone(), sources.clone(),
libraries, libraries,
None, None,
SolcStandardJsonInputSettingsSelection::new_required(pipeline), SolcStandardJsonInputSettingsSelection::new_required(),
SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false), SolcStandardJsonInputSettingsOptimizer::new(true, None, &solc_version.default, false),
None, None,
pipeline == SolcPipeline::Yul,
suppressed_warnings, suppressed_warnings,
)?; )?;
let output = solc.standard_json(input, pipeline, None, vec![], None)?; let output = solc.standard_json(input, None, vec![], None)?;
let contains_warning = output let contains_warning = output
.errors .errors
.ok_or_else(|| anyhow::anyhow!("Solidity compiler messages not found"))? .ok_or_else(|| anyhow::anyhow!("Solidity compiler messages not found"))?
@@ -273,7 +254,7 @@ pub fn check_solidity_warning(
/// Compile the blob of `contract_name` found in given `source_code`. /// Compile the blob of `contract_name` found in given `source_code`.
/// The `solc` optimizer will be enabled /// The `solc` optimizer will be enabled
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> { pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
compile_blob_with_options(contract_name, source_code, true, SolcPipeline::Yul) compile_blob_with_options(contract_name, source_code, true)
} }
/// Compile the EVM bin-runtime of `contract_name` found in given `source_code`. /// Compile the EVM bin-runtime of `contract_name` found in given `source_code`.
@@ -298,10 +279,9 @@ fn compile_evm(
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
runtime: bool, runtime: bool,
) -> Vec<u8> { ) -> Vec<u8> {
let pipeline = SolcPipeline::Yul;
let id = CachedBlob { let id = CachedBlob {
contract_name: contract_name.to_owned(), contract_name: contract_name.to_owned(),
pipeline, solidity: source_code.to_owned(),
solc_optimizer_enabled, solc_optimizer_enabled,
}; };
@@ -319,7 +299,6 @@ fn compile_evm(
[(file_name.into(), source_code.into())].into(), [(file_name.into(), source_code.into())].into(),
Default::default(), Default::default(),
None, None,
pipeline,
solc_optimizer_enabled, solc_optimizer_enabled,
) )
.expect("source should compile"); .expect("source should compile");
@@ -343,12 +322,11 @@ pub fn compile_blob_with_options(
contract_name: &str, contract_name: &str,
source_code: &str, source_code: &str,
solc_optimizer_enabled: bool, solc_optimizer_enabled: bool,
pipeline: SolcPipeline,
) -> Vec<u8> { ) -> Vec<u8> {
let id = CachedBlob { let id = CachedBlob {
contract_name: contract_name.to_owned(), contract_name: contract_name.to_owned(),
solidity: source_code.to_owned(),
solc_optimizer_enabled, solc_optimizer_enabled,
pipeline,
}; };
if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) { if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) {
@@ -360,7 +338,6 @@ pub fn compile_blob_with_options(
[(file_name.into(), source_code.into())].into(), [(file_name.into(), source_code.into())].into(),
Default::default(), Default::default(),
None, None,
pipeline,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
solc_optimizer_enabled, solc_optimizer_enabled,
) )
@@ -0,0 +1,66 @@
<?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", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.11", "@types/jest": "^29.5.14",
"@types/shelljs": "^0.8.15", "@types/shelljs": "^0.8.15",
"jest": "^29.7.0", "jest": "^29.7.0",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"ts-jest": "^29.1.1", "ts-jest": "^29.2.5",
"typescript": "^5.3.3" "typescript": "^5.7.3"
} }
} }
@@ -10,7 +10,7 @@ describe("Run resolc without any options", () => {
const result = executeCommand(command); const result = executeCommand(command);
it("Info with help is presented", () => { it("Info with help is presented", () => {
expect(result.output).toMatch(/(No input sources specified|Error(s) found.)/i); expect(result.output).toMatch(/(Usage: resolc)/i);
}); });
it("Exit code = 1", () => { it("Exit code = 1", () => {
@@ -28,7 +28,7 @@ describe("Run resolc without any options", () => {
//#1713 //#1713
describe("Default run a command from the help", () => { describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command); const result = executeCommand(command);
it("Compiler run successful", () => { it("Compiler run successful", () => {
@@ -54,7 +54,7 @@ describe("Default run a command from the help", () => {
//#1818 //#1818
describe("Default run a command from the help", () => { describe("Default run a command from the help", () => {
const command = `resolc ${paths.pathToBasicSolContract} -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd const command = `resolc ${paths.pathToBasicSolContract} --overwrite -O3 --bin --asm --output-dir "${paths.pathToOutputDir}"`; // potential issue on resolc with full path on Windows cmd
const result = executeCommand(command); const result = executeCommand(command);
it("Compiler run successful", () => { it("Compiler run successful", () => {
@@ -81,8 +81,8 @@ describe("Default run a command from the help", () => {
describe("Run resolc with source debug information", () => { describe("Run resolc with source debug information", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"`, `resolc -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`,
`resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --bin --asm --output-dir "${paths.pathToOutputDir}"` `resolc --disable-solc-optimizer -g ${paths.pathToBasicSolContract} --overwrite --bin --asm --output-dir "${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { 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", () => { describe("Run resolc with source debug information, check LLVM debug-info", () => {
const commands = [ const commands = [
`resolc -g ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"`, `resolc -g ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`,
`resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --debug-output-dir="${paths.pathToOutputDir}"` `resolc -g --disable-solc-optimizer ${paths.pathToBasicSolContract} --overwrite --debug-output-dir="${paths.pathToOutputDir}"`
]; // potential issue on resolc with full path on Windows cmd`; ]; // potential issue on resolc with full path on Windows cmd`;
for (var idx in commands) { for (var idx in commands) {
@@ -0,0 +1,153 @@
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,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const MAIN_CODE: &str = r#" pub const MAIN_CODE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -51,7 +49,6 @@ fn default() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Build failure"); .expect("Build failure");
-74
View File
@@ -5,8 +5,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
#[test] #[test]
fn yul() { fn yul() {
let source_code = r#" let source_code = r#"
@@ -27,7 +25,6 @@ contract Test {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");
@@ -45,75 +42,4 @@ contract Test {
.is_some(), .is_some(),
"Yul IR is missing" "Yul IR is missing"
); );
assert!(
build
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("Test")
.expect("Always exists")
.evm
.as_ref()
.expect("EVM object is missing")
.assembly
.is_none(),
"EVMLA IR is present although not requested"
);
}
#[test]
fn evmla() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Test {
function main() public view returns (uint) {
return 42;
}
}
"#;
let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), source_code.to_owned());
let build = super::build_solidity(
sources,
BTreeMap::new(),
None,
SolcPipeline::EVMLA,
revive_llvm_context::OptimizerSettings::cycles(),
)
.expect("Test failure");
assert!(
build
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("Test")
.expect("Always exists")
.evm
.as_ref()
.expect("EVM object is missing")
.assembly
.is_some(),
"EVMLA IR is missing",
);
assert!(
build
.contracts
.as_ref()
.expect("Always exists")
.get("test.sol")
.expect("Always exists")
.get("Test")
.expect("Always exists")
.ir_optimized
.is_none(),
"Yul IR is present although not requested",
);
} }
+4 -16
View File
@@ -4,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const LIBRARY_TEST_SOURCE: &str = r#" pub const LIBRARY_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
@@ -38,12 +36,8 @@ fn not_specified() {
let mut sources = BTreeMap::new(); let mut sources = BTreeMap::new();
sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned()); sources.insert("test.sol".to_owned(), LIBRARY_TEST_SOURCE.to_owned());
for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] { let output =
let output = super::build_solidity_and_detect_missing_libraries( super::build_solidity_and_detect_missing_libraries(sources.clone(), BTreeMap::new())
sources.clone(),
BTreeMap::new(),
pipeline,
)
.expect("Test failure"); .expect("Test failure");
assert!( assert!(
output output
@@ -61,7 +55,6 @@ fn not_specified() {
"Missing library not detected" "Missing library not detected"
); );
} }
}
#[test] #[test]
fn specified() { fn specified() {
@@ -75,12 +68,8 @@ fn specified() {
.entry("SimpleLibrary".to_string()) .entry("SimpleLibrary".to_string())
.or_insert("0x00000000000000000000000000000000DEADBEEF".to_string()); .or_insert("0x00000000000000000000000000000000DEADBEEF".to_string());
for pipeline in [SolcPipeline::EVMLA, SolcPipeline::Yul] { let output =
let output = super::build_solidity_and_detect_missing_libraries( super::build_solidity_and_detect_missing_libraries(sources.clone(), libraries.clone())
sources.clone(),
libraries.clone(),
pipeline,
)
.expect("Test failure"); .expect("Test failure");
assert!( assert!(
output output
@@ -99,4 +88,3 @@ fn specified() {
"The list of missing libraries must be empty" "The list of missing libraries must be empty"
); );
} }
}
+19 -160
View File
@@ -4,7 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
use crate::warning::Warning; use crate::warning::Warning;
pub const ECRECOVER_TEST_SOURCE: &str = r#" pub const ECRECOVER_TEST_SOURCE: &str = r#"
@@ -30,7 +29,6 @@ fn ecrecover() {
ECRECOVER_TEST_SOURCE, ECRECOVER_TEST_SOURCE,
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.", "Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
).expect("Test failure") ).expect("Test failure")
@@ -44,7 +42,6 @@ fn ecrecover_suppressed() {
ECRECOVER_TEST_SOURCE, ECRECOVER_TEST_SOURCE,
"Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.", "Warning: It looks like you are using 'ecrecover' to validate a signature of a user account.",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::EcRecover]), Some(vec![Warning::EcRecover]),
).expect("Test failure") ).expect("Test failure")
@@ -69,32 +66,31 @@ contract SendExample {
} }
"#; "#;
pub const BALANCE_CALLS_MESSAGE: &str =
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)'";
#[test] #[test]
fn send() { fn send() {
assert!( assert!(super::check_solidity_warning(
super::check_solidity_warning(
SEND_TEST_SOURCE, SEND_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
).expect("Test failure") )
); .expect("Test failure"));
} }
#[test] #[test]
fn send_suppressed() { fn send_suppressed() {
assert!( assert!(!super::check_solidity_warning(
!super::check_solidity_warning(
SEND_TEST_SOURCE, SEND_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::SendTransfer]), Some(vec![Warning::SendTransfer]),
).expect("Test failure") )
); .expect("Test failure"));
} }
pub const TRANSFER_TEST_SOURCE: &str = r#" pub const TRANSFER_TEST_SOURCE: &str = r#"
@@ -116,30 +112,26 @@ contract TransferExample {
#[test] #[test]
fn transfer() { fn transfer() {
assert!( assert!(super::check_solidity_warning(
super::check_solidity_warning(
TRANSFER_TEST_SOURCE, TRANSFER_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
).expect("Test failure") )
); .expect("Test failure"));
} }
#[test] #[test]
fn transfer_suppressed() { fn transfer_suppressed() {
assert!( assert!(!super::check_solidity_warning(
!super::check_solidity_warning(
TRANSFER_TEST_SOURCE, TRANSFER_TEST_SOURCE,
"Warning: It looks like you are using '<address payable>.send/transfer(<X>)' without providing", BALANCE_CALLS_MESSAGE,
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::SendTransfer]), Some(vec![Warning::SendTransfer]),
).expect("Test failure") )
); .expect("Test failure"));
} }
pub const EXTCODESIZE_TEST_SOURCE: &str = r#" pub const EXTCODESIZE_TEST_SOURCE: &str = r#"
@@ -163,7 +155,6 @@ fn extcodesize() {
EXTCODESIZE_TEST_SOURCE, EXTCODESIZE_TEST_SOURCE,
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,", "Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
) )
@@ -176,7 +167,6 @@ fn extcodesize_suppressed() {
EXTCODESIZE_TEST_SOURCE, EXTCODESIZE_TEST_SOURCE,
"Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,", "Warning: Your code or one of its dependencies uses the 'extcodesize' instruction,",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::ExtCodeSize]), Some(vec![Warning::ExtCodeSize]),
) )
@@ -200,7 +190,6 @@ fn tx_origin() {
TX_ORIGIN_TEST_SOURCE, TX_ORIGIN_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
) )
@@ -213,7 +202,6 @@ fn tx_origin_suppressed() {
TX_ORIGIN_TEST_SOURCE, TX_ORIGIN_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::TxOrigin]), Some(vec![Warning::TxOrigin]),
) )
@@ -244,7 +232,6 @@ fn tx_origin_assembly() {
TX_ORIGIN_ASSEMBLY_TEST_SOURCE, TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
None, None,
) )
@@ -257,136 +244,8 @@ fn tx_origin_assembly_suppressed() {
TX_ORIGIN_ASSEMBLY_TEST_SOURCE, TX_ORIGIN_ASSEMBLY_TEST_SOURCE,
"Warning: You are checking for 'tx.origin' in your code, which might lead to", "Warning: You are checking for 'tx.origin' in your code, which might lead to",
BTreeMap::new(), BTreeMap::new(),
SolcPipeline::Yul,
false, false,
Some(vec![Warning::TxOrigin]), Some(vec![Warning::TxOrigin]),
) )
.expect("Test failure")); .expect("Test failure"));
} }
#[test]
fn internal_function_pointer_argument() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract InternalFunctionPointerExample {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
function executeOperation(
function (uint256, uint256) internal pure returns (uint256) operation,
uint256 a,
uint256 b
) private pure returns (uint256) {
return operation(a, b);
}
function testAdd(uint256 a, uint256 b) public pure returns (uint256) {
return executeOperation(add, a, b);
}
function testSub(uint256 a, uint256 b) public pure returns (uint256) {
return executeOperation(sub, a, b);
}
}
"#;
assert!(super::check_solidity_warning(
source_code,
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
BTreeMap::new(),
SolcPipeline::EVMLA,
true,
None,
)
.expect("Test failure"));
}
#[test]
fn internal_function_pointer_stack() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract StackFunctionPointerExample {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
function testAdd(uint256 a, uint256 b) public pure returns (uint256) {
function (uint256, uint256) internal pure returns (uint256) operation = add;
return operation(a, b);
}
function testSub(uint256 a, uint256 b) public pure returns (uint256) {
function (uint256, uint256) internal pure returns (uint256) operation = sub;
return operation(a, b);
}
}
"#;
assert!(super::check_solidity_warning(
source_code,
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
BTreeMap::new(),
SolcPipeline::EVMLA,
true,
None,
)
.expect("Test failure"));
}
#[test]
fn internal_function_pointer_storage() {
let source_code = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract StorageFunctionPointerExample {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return a - b;
}
function (uint256, uint256) internal pure returns (uint256) operation;
bool private isOperationSet = false;
function setOperation(bool isAdd) public {
if (isAdd) {
operation = add;
} else {
operation = sub;
}
isOperationSet = true;
}
function executeOperation(uint256 a, uint256 b) public view returns (uint256) {
require(isOperationSet, "Operation not set");
return operation(a, b);
}
}
"#;
assert!(super::check_solidity_warning(
source_code,
"Error: Internal function pointers are not supported in EVM legacy assembly pipeline.",
BTreeMap::new(),
SolcPipeline::EVMLA,
true,
None,
)
.expect("Test failure"));
}
-5
View File
@@ -4,8 +4,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const SOURCE_CODE: &str = r#" pub const SOURCE_CODE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -54,7 +52,6 @@ fn optimizer() {
sources.clone(), sources.clone(),
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::none(), revive_llvm_context::OptimizerSettings::none(),
) )
.expect("Build failure"); .expect("Build failure");
@@ -62,7 +59,6 @@ fn optimizer() {
sources.clone(), sources.clone(),
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Build failure"); .expect("Build failure");
@@ -70,7 +66,6 @@ fn optimizer() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
None, None,
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::size(), revive_llvm_context::OptimizerSettings::size(),
) )
.expect("Build failure"); .expect("Build failure");
-3
View File
@@ -5,8 +5,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use crate::solc::pipeline::Pipeline as SolcPipeline;
pub const CALLEE_TEST_SOURCE: &str = r#" pub const CALLEE_TEST_SOURCE: &str = r#"
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -46,7 +44,6 @@ fn default() {
sources, sources,
BTreeMap::new(), BTreeMap::new(),
Some(remappings), Some(remappings),
SolcPipeline::Yul,
revive_llvm_context::OptimizerSettings::cycles(), revive_llvm_context::OptimizerSettings::cycles(),
) )
.expect("Test failure"); .expect("Test failure");

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