diff --git a/.github/buildspec.yml b/.github/buildspec.yml
new file mode 100644
index 0000000000..9b6503bb5d
--- /dev/null
+++ b/.github/buildspec.yml
@@ -0,0 +1,36 @@
+version: 0.2
+
+phases:
+ pre_build:
+ commands:
+ - git submodule update --init
+ - echo Logging in to Dockerhub....
+ - docker login --username $DOCKERHUB_USERNAME --password $DOCKERHUB_PASSWORD
+ - aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin $REPOSITORY_URI
+ - COMMIT_HASH=$(git rev-parse --short=7 HEAD || echo "latest")
+ - VERSION_TAG=$(git tag --points-at HEAD | sed '/-/!s/$/_/' | sort -rV | sed 's/_$//' | head -n 1 | grep ^ || git show -s --pretty=%D | sed 's/, /\n/g' | grep -v '^origin/' |grep -v '^grafted\|HEAD\|master\|main$' || echo "dev")
+ - NITRO_VERSION=${VERSION_TAG}-${COMMIT_HASH}
+ - IMAGE_TAG=${NITRO_VERSION}
+ - NITRO_DATETIME=$(git show -s --date=iso-strict --format=%cd)
+ - NITRO_MODIFIED="false"
+ - echo ${NITRO_VERSION} > ./.nitro-tag.txt
+ build:
+ commands:
+ - echo Build started on `date`
+ - echo Building the Docker image ${NITRO_VERSION}...
+ - DOCKER_BUILDKIT=1 docker build . -t nitro-node-slim --target nitro-node-slim --build-arg version=$NITRO_VERSION --build-arg datetime=$NITRO_DATETIME --build-arg modified=$NITRO_MODIFIED
+ - DOCKER_BUILDKIT=1 docker build . -t nitro-node --target nitro-node --build-arg version=$NITRO_VERSION --build-arg datetime=$NITRO_DATETIME --build-arg modified=$NITRO_MODIFIED
+ - DOCKER_BUILDKIT=1 docker build . -t nitro-node-dev --target nitro-node-dev --build-arg version=$NITRO_VERSION --build-arg datetime=$NITRO_DATETIME --build-arg modified=$NITRO_MODIFIED
+ - DOCKER_BUILDKIT=1 docker build . -t nitro-node-validator --target nitro-node-validator --build-arg version=$NITRO_VERSION --build-arg datetime=$NITRO_DATETIME --build-arg modified=$NITRO_MODIFIED
+ - docker tag nitro-node:latest $REPOSITORY_URI:$IMAGE_TAG-$ARCH_TAG
+ - docker tag nitro-node-slim:latest $REPOSITORY_URI:$IMAGE_TAG-slim-$ARCH_TAG
+ - docker tag nitro-node-dev:latest $REPOSITORY_URI:$IMAGE_TAG-dev-$ARCH_TAG
+ - docker tag nitro-node-validator:latest $REPOSITORY_URI:$IMAGE_TAG-validator-$ARCH_TAG
+ post_build:
+ commands:
+ - echo Build completed on `date`
+ - echo pushing to repo
+ - docker push $REPOSITORY_URI:$IMAGE_TAG-$ARCH_TAG
+ - docker push $REPOSITORY_URI:$IMAGE_TAG-slim-$ARCH_TAG
+ - docker push $REPOSITORY_URI:$IMAGE_TAG-dev-$ARCH_TAG
+ - docker push $REPOSITORY_URI:$IMAGE_TAG-validator-$ARCH_TAG
diff --git a/.github/workflows/arbitrator-ci.yml b/.github/workflows/arbitrator-ci.yml
index adbf562e31..47646017ac 100644
--- a/.github/workflows/arbitrator-ci.yml
+++ b/.github/workflows/arbitrator-ci.yml
@@ -50,15 +50,13 @@ jobs:
- name: Install go
uses: actions/setup-go@v4
with:
- go-version: 1.21.x
+ go-version: 1.23.x
- name: Install custom go-ethereum
run: |
cd /tmp
- git clone --branch v1.13.8 --depth 1 https://github.com/ethereum/go-ethereum.git
+ git clone --branch v1.14.11 --depth 1 https://github.com/ethereum/go-ethereum.git
cd go-ethereum
- # Enable KZG point evaluation precompile early
- sed -i 's#var PrecompiledContractsBerlin = map\[common.Address\]PrecompiledContract{#\0 common.BytesToAddress([]byte{0x0a}): \&kzgPointEvaluation{},#g' core/vm/contracts.go
go build -o /usr/local/bin/geth ./cmd/geth
- name: Setup nodejs
@@ -78,10 +76,13 @@ jobs:
uses: dtolnay/rust-toolchain@nightly
id: install-rust-nightly
with:
- toolchain: 'nightly'
+ toolchain: 'nightly-2024-08-06'
targets: 'wasm32-wasi, wasm32-unknown-unknown'
components: 'rust-src, rustfmt, clippy'
+ - name: Set STYLUS_NIGHTLY_VER environment variable
+ run: echo "STYLUS_NIGHTLY_VER=+$(rustup toolchain list | grep '^nightly' | head -n1 | cut -d' ' -f1)" >> "$GITHUB_ENV"
+
- name: Cache Rust intermediate build products
uses: actions/cache@v3
with:
@@ -162,22 +163,13 @@ jobs:
run: cargo clippy --all --manifest-path arbitrator/Cargo.toml -- -D warnings
- name: Run rust tests
- uses: actions-rs/cargo@v1
- with:
- command: test
- args: -p arbutil -p prover -p jit -p stylus --release --manifest-path arbitrator/prover/Cargo.toml
+ run: cargo test -p arbutil -p prover -p jit -p stylus --release --manifest-path arbitrator/prover/Cargo.toml
- name: Rustfmt
- uses: actions-rs/cargo@v1
- with:
- command: fmt
- args: -p arbutil -p prover -p jit -p stylus --manifest-path arbitrator/Cargo.toml -- --check
+ run: cargo fmt -p arbutil -p prover -p jit -p stylus --manifest-path arbitrator/Cargo.toml -- --check
- name: Rustfmt - langs/rust
- uses: actions-rs/cargo@v1
- with:
- command: fmt
- args: --all --manifest-path arbitrator/langs/rust/Cargo.toml -- --check
+ run: cargo fmt --all --manifest-path arbitrator/langs/rust/Cargo.toml -- --check
- name: Make proofs from test cases
run: make -j test-gen-proofs
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b9fcfba7fc..8ed49634ad 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
- name: Install go
uses: actions/setup-go@v4
with:
- go-version: 1.21.x
+ go-version: 1.23.x
- name: Install wasm-ld
run: |
@@ -56,25 +56,23 @@ jobs:
- name: Install rust stable
uses: dtolnay/rust-toolchain@stable
with:
- targets: 'wasm32-unknown-unknown, wasm32-wasi'
-
- - name: Install Foundry
- uses: foundry-rs/foundry-toolchain@v1
+ toolchain: 'stable'
+ targets: 'wasm32-wasi, wasm32-unknown-unknown'
+ components: 'llvm-tools-preview, rustfmt, clippy'
- name: Install rust nightly
- uses: actions-rs/toolchain@v1
+ uses: dtolnay/rust-toolchain@nightly
id: install-rust-nightly
with:
- profile: minimal
- toolchain: "nightly"
+ toolchain: 'nightly-2024-08-06'
+ targets: 'wasm32-wasi, wasm32-unknown-unknown'
+ components: 'rust-src, rustfmt, clippy'
- - name: Install rust wasm targets
- run: rustup target add wasm32-wasi wasm32-unknown-unknown
+ - name: Set STYLUS_NIGHTLY_VER environment variable
+ run: echo "STYLUS_NIGHTLY_VER=+$(rustup toolchain list | grep '^nightly' | head -n1 | cut -d' ' -f1)" >> "$GITHUB_ENV"
- - name: Install nightly wasm targets
- run: |
- rustup component add rust-src --toolchain nightly
- rustup target add wasm32-unknown-unknown --toolchain nightly
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
- name: Cache Build Products
uses: actions/cache@v3
@@ -89,12 +87,12 @@ jobs:
uses: actions/cache@v3
with:
path: |
- ~/.cargo/registry/
- ~/.cargo/git/
+ ~/.cargo/
arbitrator/target/
arbitrator/wasm-libraries/target/
- arbitrator/wasm-libraries/soft-float/SoftFloat/build
+ arbitrator/wasm-libraries/soft-float/
target/etc/initial-machine-cache/
+ /home/runner/.rustup/toolchains/
key: ${{ runner.os }}-cargo-${{ steps.install-rust.outputs.rustc_hash }}-min-${{ hashFiles('arbitrator/Cargo.lock') }}-${{ matrix.test-mode }}
restore-keys: ${{ runner.os }}-cargo-${{ steps.install-rust.outputs.rustc_hash }}-
@@ -140,95 +138,69 @@ jobs:
echo "TMPDIR=$(pwd)/target/tmp/deadbeefbee" >> "$GITHUB_ENV"
echo "GOMEMLIMIT=6GiB" >> "$GITHUB_ENV"
echo "GOGC=80" >> "$GITHUB_ENV"
+ echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> "$GITHUB_ENV"
- name: run tests without race detection and path state scheme
if: matrix.test-mode == 'defaults'
env:
TEST_STATE_SCHEME: path
run: |
- packages=`go list ./...`
- for package in $packages; do
- echo running tests for $package
- if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -timeout 20m -tags=cionly > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then
- exit 1
- fi
- done
+ echo "Running tests with Path Scheme" >> full.log
+ ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 20m --cover
- name: run tests without race detection and hash state scheme
if: matrix.test-mode == 'defaults'
env:
TEST_STATE_SCHEME: hash
run: |
- packages=`go list ./...`
- for package in $packages; do
- echo running tests for $package
- if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -timeout 20m -tags=cionly; then
- exit 1
- fi
- done
-
- - name: run tests with race detection and path state scheme
- if: matrix.test-mode == 'race'
- env:
- TEST_STATE_SCHEME: path
- run: |
- packages=`go list ./...`
- for package in $packages; do
- echo running tests for $package
- if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -race -timeout 30m > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then
- exit 1
- fi
- done
+ echo "Running tests with Hash Scheme" >> full.log
+ ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 20m
- name: run tests with race detection and hash state scheme
if: matrix.test-mode == 'race'
env:
TEST_STATE_SCHEME: hash
run: |
- packages=`go list ./...`
- for package in $packages; do
- echo running tests for $package
- if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -race -timeout 30m; then
- exit 1
- fi
- done
+ echo "Running tests with Hash Scheme" >> full.log
+ ${{ github.workspace }}/.github/workflows/gotestsum.sh --race --timeout 30m
- name: run redis tests
if: matrix.test-mode == 'defaults'
- run: TEST_REDIS=redis://localhost:6379/0 gotestsum --format short-verbose -- -p 1 -run TestRedis ./arbnode/... ./system_tests/... -coverprofile=coverage-redis.txt -covermode=atomic -coverpkg=./...
+ run: |
+ echo "Running redis tests" >> full.log
+ TEST_REDIS=redis://localhost:6379/0 gotestsum --format short-verbose -- -p 1 -run TestRedis ./arbnode/... ./system_tests/... -coverprofile=coverage-redis.txt -covermode=atomic -coverpkg=./...
+
+ - name: create block input json file
+ if: matrix.test-mode == 'defaults'
+ run: |
+ gotestsum --format short-verbose -- -run TestProgramStorage$ ./system_tests/... --count 1 --recordBlockInputs.WithBaseDir="${{ github.workspace }}/target" --recordBlockInputs.WithTimestampDirEnabled=false --recordBlockInputs.WithBlockIdInFileNameEnabled=false
+
+ - name: run arbitrator prover on block input json
+ if: matrix.test-mode == 'defaults'
+ run: |
+ make build-prover-bin
+ target/bin/prover target/machines/latest/machine.wavm.br -b --json-inputs="${{ github.workspace }}/target/TestProgramStorage/block_inputs.json"
+
+ - name: run jit prover on block input json
+ if: matrix.test-mode == 'defaults'
+ run: |
+ make build-jit
+ if [ -n "$(target/bin/jit --binary target/machines/latest/replay.wasm --cranelift --json-inputs='${{ github.workspace }}/target/TestProgramStorage/block_inputs.json')" ]; then
+ echo "Error: Command produced output."
+ exit 1
+ fi
- name: run challenge tests
if: matrix.test-mode == 'challenge'
- run: |
- packages=`go list ./...`
- for package in $packages; do
- echo running tests for $package
- if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -tags=challengetest -run=TestChallenge > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then
- exit 1
- fi
- done
+ run: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags challengetest --run TestChallenge --cover
- name: run stylus tests
if: matrix.test-mode == 'stylus'
- run: |
- packages=`go list ./...`
- for package in $packages; do
- echo running tests for $package
- if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -tags=stylustest -run="TestProgramArbitrator" > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then
- exit 1
- fi
- done
+ run: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags stylustest --run TestProgramArbitrator --timeout 60m --cover
- name: run long stylus tests
if: matrix.test-mode == 'long'
- run: |
- packages=`go list ./...`
- for package in $packages; do
- echo running tests for $package
- if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -tags=stylustest -run="TestProgramLong" > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then
- exit 1
- fi
- done
+ run: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags stylustest --run TestProgramLong --timeout 60m --cover
- name: Archive detailed run log
uses: actions/upload-artifact@v3
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1cde8f06b9..26447947d4 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -73,7 +73,7 @@ jobs:
- name: Install go
uses: actions/setup-go@v4
with:
- go-version: 1.21.x
+ go-version: 1.23.x
- name: Install rust stable
uses: dtolnay/rust-toolchain@stable
diff --git a/.github/workflows/gotestsum.sh b/.github/workflows/gotestsum.sh
new file mode 100755
index 0000000000..ed631847b7
--- /dev/null
+++ b/.github/workflows/gotestsum.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+check_missing_value() {
+ if [[ $1 -eq 0 || $2 == -* ]]; then
+ echo "missing $3 argument value"
+ exit 1
+ fi
+}
+
+timeout=""
+tags=""
+run=""
+race=false
+cover=false
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --timeout)
+ shift
+ check_missing_value $# "$1" "--timeout"
+ timeout=$1
+ shift
+ ;;
+ --tags)
+ shift
+ check_missing_value $# "$1" "--tags"
+ tags=$1
+ shift
+ ;;
+ --run)
+ shift
+ check_missing_value $# "$1" "--run"
+ run=$1
+ shift
+ ;;
+ --race)
+ race=true
+ shift
+ ;;
+ --cover)
+ cover=true
+ shift
+ ;;
+ *)
+ echo "Invalid argument: $1"
+ exit 1
+ ;;
+ esac
+done
+
+packages=$(go list ./...)
+for package in $packages; do
+ cmd="stdbuf -oL gotestsum --format short-verbose --packages=\"$package\" --rerun-fails=2 --no-color=false --"
+
+ if [ "$timeout" != "" ]; then
+ cmd="$cmd -timeout $timeout"
+ fi
+
+ if [ "$tags" != "" ]; then
+ cmd="$cmd -tags=$tags"
+ fi
+
+ if [ "$run" != "" ]; then
+ cmd="$cmd -run=$run"
+ fi
+
+ if [ "$race" == true ]; then
+ cmd="$cmd -race"
+ fi
+
+ if [ "$cover" == true ]; then
+ cmd="$cmd -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/..."
+ fi
+
+ cmd="$cmd > >(stdbuf -oL tee -a full.log | grep -vE \"INFO|seal\")"
+
+ echo ""
+ echo running tests for "$package"
+ echo "$cmd"
+
+ if ! eval "$cmd"; then
+ exit 1
+ fi
+done
diff --git a/.github/workflows/shellcheck-ci.yml b/.github/workflows/shellcheck-ci.yml
new file mode 100644
index 0000000000..d1c7b58580
--- /dev/null
+++ b/.github/workflows/shellcheck-ci.yml
@@ -0,0 +1,30 @@
+name: ShellCheck CI
+run-name: ShellCheck CI triggered from @${{ github.actor }} of ${{ github.head_ref }}
+
+on:
+ workflow_dispatch:
+ merge_group:
+ pull_request:
+ push:
+ branches:
+ - master
+
+jobs:
+ shellcheck:
+ name: Run ShellCheck
+ runs-on: ubuntu-8
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Run ShellCheck
+ uses: ludeeus/action-shellcheck@master
+ with:
+ ignore_paths: >-
+ ./fastcache/**
+ ./contracts/**
+ ./safe-smart-account/**
+ ./go-ethereum/**
+ ./nitro-testnode/**
+ ./brotli/**
+ ./arbitrator/**
diff --git a/.github/workflows/submodule-pin-check.sh b/.github/workflows/submodule-pin-check.sh
deleted file mode 100755
index aecb287ce1..0000000000
--- a/.github/workflows/submodule-pin-check.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-
-declare -Ar exceptions=(
- [contracts]=origin/develop
- [nitro-testnode]=origin/master
-
- #TODO Rachel to check these are the intended branches.
- [arbitrator/langs/c]=origin/vm-storage-cache
- [arbitrator/tools/wasmer]=origin/adopt-v4.2.8
-)
-
-divergent=0
-for mod in `git submodule --quiet foreach 'echo $name'`; do
- branch=origin/HEAD
- if [[ -v exceptions[$mod] ]]; then
- branch=${exceptions[$mod]}
- fi
-
- if ! git -C $mod merge-base --is-ancestor HEAD $branch; then
- echo $mod diverges from $branch
- divergent=1
- fi
-done
-
-exit $divergent
-
diff --git a/.github/workflows/submodule-pin-check.yml b/.github/workflows/submodule-pin-check.yml
index e459bad34d..60dd8ad827 100644
--- a/.github/workflows/submodule-pin-check.yml
+++ b/.github/workflows/submodule-pin-check.yml
@@ -1,21 +1,70 @@
-name: Submodule Pin Check
+name: Merge Checks
on:
- pull_request:
+ pull_request_target:
branches: [ master ]
types: [synchronize, opened, reopened]
+permissions:
+ statuses: write
+
jobs:
submodule-pin-check:
- name: Submodule Pin Check
+ name: Check Submodule Pin
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- submodules: recursive
+ submodules: true
+ persist-credentials: false
+ ref: "${{ github.event.pull_request.head.sha }}"
- name: Check all submodules are ancestors of origin/HEAD or configured branch
- run: ${{ github.workspace }}/.github/workflows/submodule-pin-check.sh
+ run: |
+ status_state="pending"
+ declare -Ar exceptions=(
+ [contracts]=origin/develop
+ [nitro-testnode]=origin/master
+
+ #TODO Rachel to check these are the intended branches.
+ [arbitrator/langs/c]=origin/vm-storage-cache
+ [arbitrator/tools/wasmer]=origin/adopt-v4.2.8
+ )
+ divergent=0
+ for mod in `git submodule --quiet foreach 'echo $name'`; do
+ branch=origin/HEAD
+ if [[ -v exceptions[$mod] ]]; then
+ branch=${exceptions[$mod]}
+ fi
+
+ if ! git -C $mod merge-base --is-ancestor HEAD $branch; then
+ echo $mod diverges from $branch
+ divergent=1
+ fi
+ done
+ if [ $divergent -eq 0 ]; then
+ status_state="success"
+ else
+ resp="$(curl -sSL --fail-with-body \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "https://api.github.com/repos/$GITHUB_REPOSITORY/commits/${{ github.event.pull_request.head.sha }}/statuses")"
+ if ! jq -e '.[] | select(.context == "Submodule Pin Check")' > /dev/null <<< "$resp"; then
+ # Submodule pin check is failling and no status exists
+ # Keep it without a status to keep the green checkmark appearing
+ # Otherwise, the commit and PR's CI will appear to be indefinitely pending
+ # Merging will still be blocked until the required status appears
+ exit 0
+ fi
+ fi
+ curl -sSL --fail-with-body \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "https://api.github.com/repos/$GITHUB_REPOSITORY/statuses/${{ github.event.pull_request.head.sha }}" \
+ -d '{"context":"Submodule Pin Check","state":"'"$status_state"'"}'
diff --git a/Dockerfile b/Dockerfile
index ea473055aa..aba5432254 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -45,8 +45,8 @@ FROM wasm-base AS wasm-libs-builder
# clang / lld used by soft-float wasm
RUN apt-get update && \
apt-get install -y clang=1:14.0-55.7~deb12u1 lld=1:14.0-55.7~deb12u1 wabt
- # pinned rust 1.80.0
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.0 --target x86_64-unknown-linux-gnu wasm32-unknown-unknown wasm32-wasi
+ # pinned rust 1.80.1
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.1 --target x86_64-unknown-linux-gnu wasm32-unknown-unknown wasm32-wasi
COPY ./Makefile ./
COPY arbitrator/Cargo.* arbitrator/
COPY arbitrator/arbutil arbitrator/arbutil
@@ -66,7 +66,7 @@ COPY --from=wasm-libs-builder /workspace/ /
FROM wasm-base AS wasm-bin-builder
# pinned go version
-RUN curl -L https://golang.org/dl/go1.21.10.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf -
+RUN curl -L https://golang.org/dl/go1.23.1.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf -
COPY ./Makefile ./go.mod ./go.sum ./
COPY ./arbcompress ./arbcompress
COPY ./arbos ./arbos
@@ -94,7 +94,7 @@ COPY --from=contracts-builder workspace/contracts/node_modules/@offchainlabs/upg
COPY --from=contracts-builder workspace/.make/ .make/
RUN PATH="$PATH:/usr/local/go/bin" NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-wasm-bin
-FROM rust:1.80-slim-bookworm AS prover-header-builder
+FROM rust:1.80.1-slim-bookworm AS prover-header-builder
WORKDIR /workspace
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
@@ -120,7 +120,7 @@ RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-header
FROM scratch AS prover-header-export
COPY --from=prover-header-builder /workspace/target/ /
-FROM rust:1.80-slim-bookworm AS prover-builder
+FROM rust:1.80.1-slim-bookworm AS prover-builder
WORKDIR /workspace
RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
@@ -218,8 +218,9 @@ COPY ./scripts/download-machine.sh .
#RUN ./download-machine.sh consensus-v20 0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4
RUN ./download-machine.sh consensus-v30 0xb0de9cb89e4d944ae6023a3b62276e54804c242fd8c4c2d8e6cc4450f5fa8b1b && true
RUN ./download-machine.sh consensus-v31 0x260f5fa5c3176a856893642e149cf128b5a8de9f828afec8d11184415dd8dc69
+RUN ./download-machine.sh consensus-v32 0x184884e1eb9fefdc158f6c8ac912bb183bf3cf83f0090317e0bc4ac5860baa39
-FROM golang:1.21.10-bookworm AS node-builder
+FROM golang:1.23.1-bookworm AS node-builder
WORKDIR /workspace
ARG version=""
ARG datetime=""
@@ -264,6 +265,8 @@ COPY --from=node-builder /workspace/target/bin/relay /usr/local/bin/
COPY --from=node-builder /workspace/target/bin/nitro-val /usr/local/bin/
COPY --from=node-builder /workspace/target/bin/seq-coordinator-manager /usr/local/bin/
COPY --from=node-builder /workspace/target/bin/prover /usr/local/bin/
+COPY --from=node-builder /workspace/target/bin/dbconv /usr/local/bin/
+COPY ./scripts/convert-databases.bash /usr/local/bin/
COPY --from=machine-versions /workspace/machines /home/user/target/machines
COPY ./scripts/validate-wasm-module-root.sh .
RUN ./validate-wasm-module-root.sh /home/user/target/machines /usr/local/bin/prover
diff --git a/LICENSE.md b/LICENSE.md
index ea9a53da75..25768b3010 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -22,7 +22,7 @@ Additional Use Grant: You may use the Licensed Work in a production environment
Expansion Program Term of Use](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf). For purposes of this
Additional Use Grant, the "Covered Arbitrum Chains" are
(a) Arbitrum One (chainid:42161), Arbitrum Nova (chainid:42170),
- rbitrum Rinkeby testnet/Rinkarby (chainid:421611),Arbitrum Nitro
+ Arbitrum Rinkeby testnet/Rinkarby (chainid:421611),Arbitrum Nitro
Goerli testnet (chainid:421613), and Arbitrum Sepolia Testnet
(chainid:421614); (b) any future blockchains authorized to be
designated as Covered Arbitrum Chains by the decentralized autonomous
diff --git a/Makefile b/Makefile
index c05249466a..12dfb07cf8 100644
--- a/Makefile
+++ b/Makefile
@@ -31,6 +31,14 @@ ifneq ($(origin GOLANG_LDFLAGS),undefined)
GOLANG_PARAMS = -ldflags="-extldflags '-ldl' $(GOLANG_LDFLAGS)"
endif
+UNAME_S := $(shell uname -s)
+
+# In Mac OSX, there are a lot of warnings emitted if these environment variables aren't set.
+ifeq ($(UNAME_S), Darwin)
+ export MACOSX_DEPLOYMENT_TARGET := $(shell sw_vers -productVersion)
+ export CGO_LDFLAGS := -Wl,-no_warn_duplicate_libraries
+endif
+
precompile_names = AddressTable Aggregator BLS Debug FunctionTable GasInfo Info osTest Owner RetryableTx Statistics Sys
precompiles = $(patsubst %,./solgen/generated/%.go, $(precompile_names))
@@ -141,24 +149,33 @@ stylus_test_erc20_wasm = $(call get_stylus_test_wasm,erc20)
stylus_test_erc20_src = $(call get_stylus_test_rust,erc20)
stylus_test_read-return-data_wasm = $(call get_stylus_test_wasm,read-return-data)
stylus_test_read-return-data_src = $(call get_stylus_test_rust,read-return-data)
+stylus_test_hostio-test_wasm = $(call get_stylus_test_wasm,hostio-test)
+stylus_test_hostio-test_src = $(call get_stylus_test_rust,hostio-test)
-stylus_test_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_multicall_wasm) $(stylus_test_log_wasm) $(stylus_test_create_wasm) $(stylus_test_math_wasm) $(stylus_test_sdk-storage_wasm) $(stylus_test_erc20_wasm) $(stylus_test_read-return-data_wasm) $(stylus_test_evm-data_wasm) $(stylus_test_bfs:.b=.wasm)
+stylus_test_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_multicall_wasm) $(stylus_test_log_wasm) $(stylus_test_create_wasm) $(stylus_test_math_wasm) $(stylus_test_sdk-storage_wasm) $(stylus_test_erc20_wasm) $(stylus_test_read-return-data_wasm) $(stylus_test_evm-data_wasm) $(stylus_test_hostio-test_wasm) $(stylus_test_bfs:.b=.wasm)
stylus_benchmarks = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(stylus_test_wasms)
+CBROTLI_WASM_BUILD_ARGS ?=-d
+
# user targets
+.PHONY: push
push: lint test-go .make/fmt
@printf "%bdone building %s%b\n" $(color_pink) $$(expr $$(echo $? | wc -w) - 1) $(color_reset)
@printf "%bready for push!%b\n" $(color_pink) $(color_reset)
+.PHONY: all
all: build build-replay-env test-gen-proofs
@touch .make/all
-build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daserver datool seq-coordinator-invalidate nitro-val seq-coordinator-manager)
+.PHONY: build
+build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daserver datool seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv)
@printf $(done)
+.PHONY: build-node-deps
build-node-deps: $(go_source) build-prover-header build-prover-lib build-jit .make/solgen .make/cbrotli-lib
+.PHONY: test-go-deps
test-go-deps: \
build-replay-env \
$(stylus_test_wasms) \
@@ -166,59 +183,95 @@ test-go-deps: \
$(arbitrator_generated_header) \
$(patsubst %,$(arbitrator_cases)/%.wasm, global-state read-inboxmsg-10 global-state-wrapper const)
+.PHONY: build-prover-header
build-prover-header: $(arbitrator_generated_header)
+.PHONY: build-prover-lib
build-prover-lib: $(arbitrator_stylus_lib)
+.PHONY: build-prover-bin
build-prover-bin: $(prover_bin)
+.PHONY: build-jit
build-jit: $(arbitrator_jit)
+.PHONY: build-replay-env
build-replay-env: $(prover_bin) $(arbitrator_jit) $(arbitrator_wasm_libs) $(replay_wasm) $(output_latest)/machine.wavm.br
+.PHONY: build-wasm-libs
build-wasm-libs: $(arbitrator_wasm_libs)
+.PHONY: build-wasm-bin
build-wasm-bin: $(replay_wasm)
+.PHONY: build-solidity
build-solidity: .make/solidity
+.PHONY: contracts
contracts: .make/solgen
@printf $(done)
+.PHONY: format fmt
format fmt: .make/fmt
@printf $(done)
+.PHONY: lint
lint: .make/lint
@printf $(done)
+.PHONY: stylus-benchmarks
stylus-benchmarks: $(stylus_benchmarks)
cargo test --manifest-path $< --release --features benchmark benchmark_ -- --nocapture
@printf $(done)
+.PHONY: test-go
test-go: .make/test-go
@printf $(done)
+.PHONY: test-go-challenge
test-go-challenge: test-go-deps
- go test -v -timeout 120m ./system_tests/... -run TestChallenge -tags challengetest
+ gotestsum --format short-verbose --no-color=false -- -timeout 120m ./system_tests/... -run TestChallenge -tags challengetest
@printf $(done)
+.PHONY: test-go-stylus
test-go-stylus: test-go-deps
- go test -v -timeout 120m ./system_tests/... -run TestProgramArbitrator -tags stylustest
+ gotestsum --format short-verbose --no-color=false -- -timeout 120m ./system_tests/... -run TestProgramArbitrator -tags stylustest
@printf $(done)
+.PHONY: test-go-redis
test-go-redis: test-go-deps
- TEST_REDIS=redis://localhost:6379/0 go test -p 1 -run TestRedis ./system_tests/... ./arbnode/...
+ TEST_REDIS=redis://localhost:6379/0 gotestsum --format short-verbose --no-color=false -- -p 1 -run TestRedis ./system_tests/... ./arbnode/...
@printf $(done)
+.PHONY: test-gen-proofs
test-gen-proofs: \
$(arbitrator_test_wasms) \
$(patsubst $(arbitrator_cases)/%.wat,contracts/test/prover/proofs/%.json, $(arbitrator_tests_wat)) \
$(patsubst $(arbitrator_cases)/rust/src/bin/%.rs,contracts/test/prover/proofs/rust-%.json, $(arbitrator_tests_rust)) \
contracts/test/prover/proofs/go.json
+ @printf $(done)
+
+.PHONY: test-rust
+test-rust: .make/test-rust
+ @printf $(done)
+
+# Runs the fastest and most reliable and high-value tests.
+.PHONY: tests
+tests: test-go test-rust
+ @printf $(done)
+# Runs all tests, including slow and unreliable tests.
+# Currently, NOT including:
+# - test-go-redis (These testts require additional setup and are not as reliable)
+.PHONY: tests-all
+tests-all: tests test-go-challenge test-go-stylus test-gen-proofs
+ @printf $(done)
+
+.PHONY: wasm-ci-build
wasm-ci-build: $(arbitrator_wasm_libs) $(arbitrator_test_wasms) $(stylus_test_wasms) $(output_latest)/user_test.wasm
@printf $(done)
+.PHONY: clean
clean:
go clean -testcache
rm -rf $(arbitrator_cases)/rust/target
@@ -234,9 +287,11 @@ clean:
rm -f arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/*.a
rm -f arbitrator/wasm-libraries/forward/*.wat
rm -rf arbitrator/stylus/tests/*/target/ arbitrator/stylus/tests/*/*.wasm
+ rm -rf brotli/buildfiles
@rm -rf contracts/build contracts/cache solgen/go/
@rm -f .make/*
+.PHONY: docker
docker:
docker build -t nitro-node-slim --target nitro-node-slim .
docker build -t nitro-node --target nitro-node .
@@ -268,6 +323,9 @@ $(output_root)/bin/nitro-val: $(DEP_PREDICATE) build-node-deps
$(output_root)/bin/seq-coordinator-manager: $(DEP_PREDICATE) build-node-deps
go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/seq-coordinator-manager"
+$(output_root)/bin/dbconv: $(DEP_PREDICATE) build-node-deps
+ go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/dbconv"
+
# recompile wasm, but don't change timestamp unless files differ
$(replay_wasm): $(DEP_PREDICATE) $(go_source) .make/solgen
mkdir -p `dirname $(replay_wasm)`
@@ -428,6 +486,10 @@ $(stylus_test_erc20_wasm): $(stylus_test_erc20_src)
$(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo)
@touch -c $@ # cargo might decide to not rebuild the binary
+$(stylus_test_hostio-test_wasm): $(stylus_test_hostio-test_src)
+ $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo)
+ @touch -c $@ # cargo might decide to not rebuild the binary
+
contracts/test/prover/proofs/float%.json: $(arbitrator_cases)/float%.wasm $(prover_bin) $(output_latest)/soft-float.wasm
$(prover_bin) $< -l $(output_latest)/soft-float.wasm -o $@ -b --allow-hostapi --require-success
@@ -490,6 +552,10 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin)
gotestsum --format short-verbose --no-color=false
@touch $@
+.make/test-rust: $(DEP_PREDICATE) wasm-ci-build $(ORDER_ONLY_PREDICATE) .make
+ cargo test --manifest-path arbitrator/Cargo.toml --release
+ @touch $@
+
.make/solgen: $(DEP_PREDICATE) solgen/gen.go .make/solidity $(ORDER_ONLY_PREDICATE) .make
mkdir -p solgen/go/
go run solgen/gen.go
@@ -515,9 +581,9 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin)
@touch $@
.make/cbrotli-wasm: $(DEP_PREDICATE) $(ORDER_ONLY_PREDICATE) .make
- test -f target/lib-wasm/libbrotlicommon-static.a || ./scripts/build-brotli.sh -w -d
- test -f target/lib-wasm/libbrotlienc-static.a || ./scripts/build-brotli.sh -w -d
- test -f target/lib-wasm/libbrotlidec-static.a || ./scripts/build-brotli.sh -w -d
+ test -f target/lib-wasm/libbrotlicommon-static.a || ./scripts/build-brotli.sh -w $(CBROTLI_WASM_BUILD_ARGS)
+ test -f target/lib-wasm/libbrotlienc-static.a || ./scripts/build-brotli.sh -w $(CBROTLI_WASM_BUILD_ARGS)
+ test -f target/lib-wasm/libbrotlidec-static.a || ./scripts/build-brotli.sh -w $(CBROTLI_WASM_BUILD_ARGS)
@touch $@
.make/wasm-lib: $(DEP_PREDICATE) arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/softfloat.a $(ORDER_ONLY_PREDICATE) .make
@@ -537,4 +603,3 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin)
always: # use this to force other rules to always build
.DELETE_ON_ERROR: # causes a failure to delete its target
-.PHONY: push all build build-node-deps test-go-deps build-prover-header build-prover-lib build-prover-bin build-jit build-replay-env build-solidity build-wasm-libs contracts format fmt lint stylus-benchmarks test-go test-gen-proofs push clean docker
diff --git a/README.md b/README.md
index a07772628b..1f0e4ac81c 100644
--- a/README.md
+++ b/README.md
@@ -17,26 +17,26 @@
Nitro is the latest iteration of the Arbitrum technology. It is a fully integrated, complete
-layer 2 optimistic rollup system, including fraud proofs, the sequencer, the token bridges,
+layer 2 optimistic rollup system, including fraud proofs, the sequencer, the token bridges,
advanced calldata compression, and more.
See the live docs-site [here](https://developer.arbitrum.io/) (or [here](https://github.com/OffchainLabs/arbitrum-docs) for markdown docs source.)
-See [here](./audits) for security audit reports.
+See [here](https://docs.arbitrum.io/audit-reports) for security audit reports.
-The Nitro stack is built on several innovations. At its core is a new prover, which can do Arbitrum’s classic
-interactive fraud proofs over WASM code. That means the L2 Arbitrum engine can be written and compiled using
+The Nitro stack is built on several innovations. At its core is a new prover, which can do Arbitrum’s classic
+interactive fraud proofs over WASM code. That means the L2 Arbitrum engine can be written and compiled using
standard languages and tools, replacing the custom-designed language and compiler used in previous Arbitrum
-versions. In normal execution,
-validators and nodes run the Nitro engine compiled to native code, switching to WASM if a fraud proof is needed.
-We compile the core of Geth, the EVM engine that practically defines the Ethereum standard, right into Arbitrum.
+versions. In normal execution,
+validators and nodes run the Nitro engine compiled to native code, switching to WASM if a fraud proof is needed.
+We compile the core of Geth, the EVM engine that practically defines the Ethereum standard, right into Arbitrum.
So the previous custom-built EVM emulator is replaced by Geth, the most popular and well-supported Ethereum client.
-The last piece of the stack is a slimmed-down version of our ArbOS component, rewritten in Go, which provides the
-rest of what’s needed to run an L2 chain: things like cross-chain communication, and a new and improved batching
+The last piece of the stack is a slimmed-down version of our ArbOS component, rewritten in Go, which provides the
+rest of what’s needed to run an L2 chain: things like cross-chain communication, and a new and improved batching
and compression system to minimize L1 costs.
-Essentially, Nitro runs Geth at layer 2 on top of Ethereum, and can prove fraud over the core engine of Geth
+Essentially, Nitro runs Geth at layer 2 on top of Ethereum, and can prove fraud over the core engine of Geth
compiled to WASM.
Arbitrum One successfully migrated from the Classic Arbitrum stack onto Nitro on 8/31/22. (See [state migration](https://developer.arbitrum.io/migration/state-migration) and [dapp migration](https://developer.arbitrum.io/migration/dapp_migration) for more info).
@@ -45,14 +45,12 @@ Arbitrum One successfully migrated from the Classic Arbitrum stack onto Nitro on
Nitro is currently licensed under a [Business Source License](./LICENSE.md), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains.
-The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova.
+The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova.
-For those that prefer to deploy the Nitro software either directly on Ethereum (i.e. an L2) or have it settle to another Layer-2 on top of Ethereum, the [Arbitrum Expansion Program (the "AEP")](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf) was recently established. The AEP allows for the permissionless deployment in the aforementioned fashion provided that 10% of net revenue (as more fully described in the AEP) is contributed back to the Arbitrum community in accordance with the requirements of the AEP.
+For those that prefer to deploy the Nitro software either directly on Ethereum (i.e. an L2) or have it settle to another Layer-2 on top of Ethereum, the [Arbitrum Expansion Program (the "AEP")](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf) was recently established. The AEP allows for the permissionless deployment in the aforementioned fashion provided that 10% of net revenue (as more fully described in the AEP) is contributed back to the Arbitrum community in accordance with the requirements of the AEP.
## Contact
Discord - [Arbitrum](https://discord.com/invite/5KE54JwyTs)
Twitter: [Arbitrum](https://twitter.com/arbitrum)
-
-
diff --git a/arbcompress/compress_common.go b/arbcompress/compress_common.go
index a61dd9a171..997232e7cc 100644
--- a/arbcompress/compress_common.go
+++ b/arbcompress/compress_common.go
@@ -17,6 +17,8 @@ func compressedBufferSizeFor(length int) int {
return length + (length>>10)*8 + 64 // actual limit is: length + (length >> 14) * 4 + 6
}
-func CompressLevel(input []byte, level int) ([]byte, error) {
+func CompressLevel(input []byte, level uint64) ([]byte, error) {
+ // level is trusted and shouldn't be anything crazy
+ // #nosec G115
return Compress(input, uint32(level), EmptyDictionary)
}
diff --git a/arbcompress/native.go b/arbcompress/native.go
index 8244010979..f7b8f0b8e0 100644
--- a/arbcompress/native.go
+++ b/arbcompress/native.go
@@ -7,7 +7,7 @@
package arbcompress
/*
-#cgo CFLAGS: -g -Wall -I${SRCDIR}/../target/include/
+#cgo CFLAGS: -g -I${SRCDIR}/../target/include/
#cgo LDFLAGS: ${SRCDIR}/../target/lib/libstylus.a -lm
#include "arbitrator.h"
*/
diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock
index 79a9117a31..2b437968fa 100644
--- a/arbitrator/Cargo.lock
+++ b/arbitrator/Cargo.lock
@@ -215,7 +215,6 @@ dependencies = [
"prover",
"serde",
"serde_json",
- "serde_with 3.9.0",
]
[[package]]
@@ -496,6 +495,12 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+[[package]]
+name = "clru"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59"
+
[[package]]
name = "colorchoice"
version = "1.0.2"
@@ -705,38 +710,14 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "darling"
-version = "0.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
-dependencies = [
- "darling_core 0.13.4",
- "darling_macro 0.13.4",
-]
-
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
- "darling_core 0.20.10",
- "darling_macro 0.20.10",
-]
-
-[[package]]
-name = "darling_core"
-version = "0.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
-dependencies = [
- "fnv",
- "ident_case",
- "proc-macro2",
- "quote",
- "strsim 0.10.0",
- "syn 1.0.109",
+ "darling_core",
+ "darling_macro",
]
[[package]]
@@ -753,24 +734,13 @@ dependencies = [
"syn 2.0.72",
]
-[[package]]
-name = "darling_macro"
-version = "0.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
-dependencies = [
- "darling_core 0.13.4",
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
- "darling_core 0.20.10",
+ "darling_core",
"quote",
"syn 2.0.72",
]
@@ -928,7 +898,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242"
dependencies = [
- "darling 0.20.10",
+ "darling",
"proc-macro2",
"quote",
"syn 2.0.72",
@@ -1750,7 +1720,7 @@ dependencies = [
"rustc-demangle",
"serde",
"serde_json",
- "serde_with 1.14.0",
+ "serde_with",
"sha2 0.9.9",
"sha3 0.9.1",
"smallvec",
@@ -2073,16 +2043,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "serde_with"
-version = "1.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
-dependencies = [
- "serde",
- "serde_with_macros 1.5.2",
-]
-
[[package]]
name = "serde_with"
version = "3.9.0"
@@ -2097,29 +2057,17 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
- "serde_with_macros 3.9.0",
+ "serde_with_macros",
"time",
]
-[[package]]
-name = "serde_with_macros"
-version = "1.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
-dependencies = [
- "darling 0.13.4",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
[[package]]
name = "serde_with_macros"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
dependencies = [
- "darling 0.20.10",
+ "darling",
"proc-macro2",
"quote",
"syn 2.0.72",
@@ -2226,12 +2174,6 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-[[package]]
-name = "strsim"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
-
[[package]]
name = "strsim"
version = "0.11.1"
@@ -2270,13 +2212,13 @@ dependencies = [
"bincode",
"brotli",
"caller-env",
+ "clru",
"derivative",
"eyre",
"fnv",
"hex",
"lazy_static",
"libc",
- "lru",
"num-bigint",
"parking_lot",
"prover",
diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml
index 94ca08b0b5..eaafb6e439 100644
--- a/arbitrator/Cargo.toml
+++ b/arbitrator/Cargo.toml
@@ -24,9 +24,7 @@ repository = "https://github.com/OffchainLabs/nitro.git"
rust-version = "1.67"
[workspace.dependencies]
-cfg-if = "1.0.0"
lazy_static = "1.4.0"
-lru = "0.12.3"
num_enum = { version = "0.7.2", default-features = false }
ruint2 = "1.9.0"
wasmparser = "0.121"
diff --git a/arbitrator/arbutil/src/evm/api.rs b/arbitrator/arbutil/src/evm/api.rs
index 093e7f2984..0a603a3bb2 100644
--- a/arbitrator/arbutil/src/evm/api.rs
+++ b/arbitrator/arbutil/src/evm/api.rs
@@ -73,19 +73,103 @@ impl DataReader for VecReader {
}
}
+macro_rules! derive_math {
+ ($t:ident) => {
+ impl std::ops::Add for $t {
+ type Output = Self;
+
+ fn add(self, rhs: Self) -> Self {
+ Self(self.0 + rhs.0)
+ }
+ }
+
+ impl std::ops::AddAssign for $t {
+ fn add_assign(&mut self, rhs: Self) {
+ self.0 += rhs.0;
+ }
+ }
+
+ impl std::ops::Sub for $t {
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self {
+ Self(self.0 - rhs.0)
+ }
+ }
+
+ impl std::ops::SubAssign for $t {
+ fn sub_assign(&mut self, rhs: Self) {
+ self.0 -= rhs.0;
+ }
+ }
+
+ impl std::ops::Mul for $t {
+ type Output = Self;
+
+ fn mul(self, rhs: u64) -> Self {
+ Self(self.0 * rhs)
+ }
+ }
+
+ impl std::ops::Mul<$t> for u64 {
+ type Output = $t;
+
+ fn mul(self, rhs: $t) -> $t {
+ $t(self * rhs.0)
+ }
+ }
+
+ impl $t {
+ /// Equivalent to the Add trait, but const.
+ pub const fn add(self, rhs: Self) -> Self {
+ Self(self.0 + rhs.0)
+ }
+
+ /// Equivalent to the Sub trait, but const.
+ pub const fn sub(self, rhs: Self) -> Self {
+ Self(self.0 - rhs.0)
+ }
+
+ pub const fn saturating_add(self, rhs: Self) -> Self {
+ Self(self.0.saturating_add(rhs.0))
+ }
+
+ pub const fn saturating_sub(self, rhs: Self) -> Self {
+ Self(self.0.saturating_sub(rhs.0))
+ }
+
+ pub fn to_be_bytes(self) -> [u8; 8] {
+ self.0.to_be_bytes()
+ }
+ }
+ };
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
+#[must_use]
+pub struct Gas(pub u64);
+
+derive_math!(Gas);
+
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
+#[must_use]
+pub struct Ink(pub u64);
+
+derive_math!(Ink);
+
pub trait EvmApi: Send + 'static {
/// Reads the 32-byte value in the EVM state trie at offset `key`.
/// Returns the value and the access cost in gas.
/// Analogous to `vm.SLOAD`.
- fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64);
+ fn get_bytes32(&mut self, key: Bytes32, evm_api_gas_to_use: Gas) -> (Bytes32, Gas);
/// Stores the given value at the given key in Stylus VM's cache of the EVM state trie.
/// Note that the actual values only get written after calls to `set_trie_slots`.
- fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64;
+ fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Gas;
/// Persists any dirty values in the storage cache to the EVM state trie, dropping the cache entirely if requested.
/// Analogous to repeated invocations of `vm.SSTORE`.
- fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result;
+ fn flush_storage_cache(&mut self, clear: bool, gas_left: Gas) -> Result;
/// Reads the 32-byte value in the EVM's transient state trie at offset `key`.
/// Analogous to `vm.TLOAD`.
@@ -102,10 +186,10 @@ pub trait EvmApi: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
- gas_left: u64,
- gas_req: u64,
+ gas_left: Gas,
+ gas_req: Gas,
value: Bytes32,
- ) -> (u32, u64, UserOutcomeKind);
+ ) -> (u32, Gas, UserOutcomeKind);
/// Delegate-calls the contract at the given address.
/// Returns the EVM return data's length, the gas cost, and whether the call succeeded.
@@ -114,9 +198,9 @@ pub trait EvmApi: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
- gas_left: u64,
- gas_req: u64,
- ) -> (u32, u64, UserOutcomeKind);
+ gas_left: Gas,
+ gas_req: Gas,
+ ) -> (u32, Gas, UserOutcomeKind);
/// Static-calls the contract at the given address.
/// Returns the EVM return data's length, the gas cost, and whether the call succeeded.
@@ -125,9 +209,9 @@ pub trait EvmApi: Send + 'static {
&mut self,
contract: Bytes20,
calldata: &[u8],
- gas_left: u64,
- gas_req: u64,
- ) -> (u32, u64, UserOutcomeKind);
+ gas_left: Gas,
+ gas_req: Gas,
+ ) -> (u32, Gas, UserOutcomeKind);
/// Deploys a new contract using the init code provided.
/// Returns the new contract's address on success, or the error reason on failure.
@@ -137,8 +221,8 @@ pub trait EvmApi: Send + 'static {
&mut self,
code: Vec,
endowment: Bytes32,
- gas: u64,
- ) -> (eyre::Result, u32, u64);
+ gas: Gas,
+ ) -> (eyre::Result, u32, Gas);
/// Deploys a new contract using the init code provided, with an address determined in part by the `salt`.
/// Returns the new contract's address on success, or the error reason on failure.
@@ -149,8 +233,8 @@ pub trait EvmApi: Send + 'static {
code: Vec,
endowment: Bytes32,
salt: Bytes32,
- gas: u64,
- ) -> (eyre::Result, u32, u64);
+ gas: Gas,
+ ) -> (eyre::Result, u32, Gas);
/// Returns the EVM return data.
/// Analogous to `vm.RETURNDATACOPY`.
@@ -164,21 +248,21 @@ pub trait EvmApi: Send + 'static {
/// Gets the balance of the given account.
/// Returns the balance and the access cost in gas.
/// Analogous to `vm.BALANCE`.
- fn account_balance(&mut self, address: Bytes20) -> (Bytes32, u64);
+ fn account_balance(&mut self, address: Bytes20) -> (Bytes32, Gas);
/// Returns the code and the access cost in gas.
/// Analogous to `vm.EXTCODECOPY`.
- fn account_code(&mut self, address: Bytes20, gas_left: u64) -> (D, u64);
+ fn account_code(&mut self, address: Bytes20, gas_left: Gas) -> (D, Gas);
/// Gets the hash of the given address's code.
/// Returns the hash and the access cost in gas.
/// Analogous to `vm.EXTCODEHASH`.
- fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64);
+ fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, Gas);
/// Determines the cost in gas of allocating additional wasm pages.
/// Note: has the side effect of updating Geth's memory usage tracker.
/// Not analogous to any EVM opcode.
- fn add_pages(&mut self, pages: u16) -> u64;
+ fn add_pages(&mut self, pages: u16) -> Gas;
/// Captures tracing information for hostio invocations during native execution.
fn capture_hostio(
@@ -186,7 +270,7 @@ pub trait EvmApi: Send + 'static {
name: &str,
args: &[u8],
outs: &[u8],
- start_ink: u64,
- end_ink: u64,
+ start_ink: Ink,
+ end_ink: Ink,
);
}
diff --git a/arbitrator/arbutil/src/evm/mod.rs b/arbitrator/arbutil/src/evm/mod.rs
index 1671e67072..063194b0c6 100644
--- a/arbitrator/arbutil/src/evm/mod.rs
+++ b/arbitrator/arbutil/src/evm/mod.rs
@@ -2,6 +2,7 @@
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE
use crate::{Bytes20, Bytes32};
+use api::Gas;
pub mod api;
pub mod req;
@@ -9,74 +10,77 @@ pub mod storage;
pub mod user;
// params.SstoreSentryGasEIP2200
-pub const SSTORE_SENTRY_GAS: u64 = 2300;
+pub const SSTORE_SENTRY_GAS: Gas = Gas(2300);
// params.ColdAccountAccessCostEIP2929
-pub const COLD_ACCOUNT_GAS: u64 = 2600;
+pub const COLD_ACCOUNT_GAS: Gas = Gas(2600);
// params.ColdSloadCostEIP2929
-pub const COLD_SLOAD_GAS: u64 = 2100;
+pub const COLD_SLOAD_GAS: Gas = Gas(2100);
// params.WarmStorageReadCostEIP2929
-pub const WARM_SLOAD_GAS: u64 = 100;
+pub const WARM_SLOAD_GAS: Gas = Gas(100);
// params.WarmStorageReadCostEIP2929 (see enable1153 in jump_table.go)
-pub const TLOAD_GAS: u64 = WARM_SLOAD_GAS;
-pub const TSTORE_GAS: u64 = WARM_SLOAD_GAS;
+pub const TLOAD_GAS: Gas = WARM_SLOAD_GAS;
+pub const TSTORE_GAS: Gas = WARM_SLOAD_GAS;
// params.LogGas and params.LogDataGas
-pub const LOG_TOPIC_GAS: u64 = 375;
-pub const LOG_DATA_GAS: u64 = 8;
+pub const LOG_TOPIC_GAS: Gas = Gas(375);
+pub const LOG_DATA_GAS: Gas = Gas(8);
// params.CopyGas
-pub const COPY_WORD_GAS: u64 = 3;
+pub const COPY_WORD_GAS: Gas = Gas(3);
// params.Keccak256Gas
-pub const KECCAK_256_GAS: u64 = 30;
-pub const KECCAK_WORD_GAS: u64 = 6;
+pub const KECCAK_256_GAS: Gas = Gas(30);
+pub const KECCAK_WORD_GAS: Gas = Gas(6);
// vm.GasQuickStep (see gas.go)
-pub const GAS_QUICK_STEP: u64 = 2;
+pub const GAS_QUICK_STEP: Gas = Gas(2);
// vm.GasQuickStep (see jump_table.go)
-pub const ADDRESS_GAS: u64 = GAS_QUICK_STEP;
+pub const ADDRESS_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see eips.go)
-pub const BASEFEE_GAS: u64 = GAS_QUICK_STEP;
+pub const BASEFEE_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see eips.go)
-pub const CHAINID_GAS: u64 = GAS_QUICK_STEP;
+pub const CHAINID_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const COINBASE_GAS: u64 = GAS_QUICK_STEP;
+pub const COINBASE_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const GASLIMIT_GAS: u64 = GAS_QUICK_STEP;
+pub const GASLIMIT_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const NUMBER_GAS: u64 = GAS_QUICK_STEP;
+pub const NUMBER_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const TIMESTAMP_GAS: u64 = GAS_QUICK_STEP;
+pub const TIMESTAMP_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const GASLEFT_GAS: u64 = GAS_QUICK_STEP;
+pub const GASLEFT_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const CALLER_GAS: u64 = GAS_QUICK_STEP;
+pub const CALLER_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const CALLVALUE_GAS: u64 = GAS_QUICK_STEP;
+pub const CALLVALUE_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const GASPRICE_GAS: u64 = GAS_QUICK_STEP;
+pub const GASPRICE_GAS: Gas = GAS_QUICK_STEP;
// vm.GasQuickStep (see jump_table.go)
-pub const ORIGIN_GAS: u64 = GAS_QUICK_STEP;
+pub const ORIGIN_GAS: Gas = GAS_QUICK_STEP;
+
+pub const ARBOS_VERSION_STYLUS_CHARGING_FIXES: u64 = 32;
#[derive(Clone, Copy, Debug, Default)]
#[repr(C)]
pub struct EvmData {
+ pub arbos_version: u64,
pub block_basefee: Bytes32,
pub chainid: u64,
pub block_coinbase: Bytes20,
diff --git a/arbitrator/arbutil/src/evm/req.rs b/arbitrator/arbutil/src/evm/req.rs
index b1c8d99972..621f41e951 100644
--- a/arbitrator/arbutil/src/evm/req.rs
+++ b/arbitrator/arbutil/src/evm/req.rs
@@ -7,15 +7,15 @@ use crate::{
storage::{StorageCache, StorageWord},
user::UserOutcomeKind,
},
- format::Utf8OrHex,
- pricing::EVM_API_INK,
Bytes20, Bytes32,
};
use eyre::{bail, eyre, Result};
use std::collections::hash_map::Entry;
+use super::api::{Gas, Ink};
+
pub trait RequestHandler: Send + 'static {
- fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, u64);
+ fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, Gas);
}
pub struct EvmApiRequestor> {
@@ -35,7 +35,7 @@ impl> EvmApiRequestor {
}
}
- fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, u64) {
+ fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, Gas) {
self.handler.request(req_type, req_data)
}
@@ -45,10 +45,10 @@ impl> EvmApiRequestor {
call_type: EvmApiMethod,
contract: Bytes20,
input: &[u8],
- gas_left: u64,
- gas_req: u64,
+ gas_left: Gas,
+ gas_req: Gas,
value: Bytes32,
- ) -> (u32, u64, UserOutcomeKind) {
+ ) -> (u32, Gas, UserOutcomeKind) {
let mut request = Vec::with_capacity(20 + 32 + 8 + 8 + input.len());
request.extend(contract);
request.extend(value);
@@ -73,8 +73,8 @@ impl> EvmApiRequestor {
code: Vec,
endowment: Bytes32,
salt: Option,
- gas: u64,
- ) -> (Result, u32, u64) {
+ gas: Gas,
+ ) -> (Result, u32, Gas) {
let mut request = Vec::with_capacity(8 + 2 * 32 + code.len());
request.extend(gas.to_be_bytes());
request.extend(endowment);
@@ -100,19 +100,19 @@ impl> EvmApiRequestor {
}
impl> EvmApi for EvmApiRequestor {
- fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) {
+ fn get_bytes32(&mut self, key: Bytes32, evm_api_gas_to_use: Gas) -> (Bytes32, Gas) {
let cache = &mut self.storage_cache;
let mut cost = cache.read_gas();
let value = cache.entry(key).or_insert_with(|| {
let (res, _, gas) = self.handler.request(EvmApiMethod::GetBytes32, key);
- cost = cost.saturating_add(gas).saturating_add(EVM_API_INK);
+ cost = cost.saturating_add(gas).saturating_add(evm_api_gas_to_use);
StorageWord::known(res.try_into().unwrap())
});
(value.value, cost)
}
- fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 {
+ fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Gas {
let cost = self.storage_cache.write_gas();
match self.storage_cache.entry(key) {
Entry::Occupied(mut key) => key.get_mut().value = value,
@@ -121,7 +121,7 @@ impl> EvmApi for EvmApiRequestor {
cost
}
- fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result {
+ fn flush_storage_cache(&mut self, clear: bool, gas_left: Gas) -> Result {
let mut data = Vec::with_capacity(64 * self.storage_cache.len() + 8);
data.extend(gas_left.to_be_bytes());
@@ -136,12 +136,17 @@ impl> EvmApi for EvmApiRequestor {
self.storage_cache.clear();
}
if data.len() == 8 {
- return Ok(0); // no need to make request
+ return Ok(Gas(0)); // no need to make request
}
let (res, _, cost) = self.request(EvmApiMethod::SetTrieSlots, data);
- if res[0] != EvmApiStatus::Success.into() {
- bail!("{}", String::from_utf8_or_hex(res));
+ let status = res
+ .first()
+ .copied()
+ .map(EvmApiStatus::from)
+ .unwrap_or(EvmApiStatus::Failure);
+ if status != EvmApiStatus::Success {
+ bail!("{:?}", status);
}
Ok(cost)
}
@@ -156,8 +161,13 @@ impl> EvmApi for EvmApiRequestor {
data.extend(key);
data.extend(value);
let (res, ..) = self.request(EvmApiMethod::SetTransientBytes32, data);
- if res[0] != EvmApiStatus::Success.into() {
- bail!("{}", String::from_utf8_or_hex(res));
+ let status = res
+ .first()
+ .copied()
+ .map(EvmApiStatus::from)
+ .unwrap_or(EvmApiStatus::Failure);
+ if status != EvmApiStatus::Success {
+ bail!("{:?}", status);
}
Ok(())
}
@@ -166,10 +176,10 @@ impl> EvmApi for EvmApiRequestor {
&mut self,
contract: Bytes20,
input: &[u8],
- gas_left: u64,
- gas_req: u64,
+ gas_left: Gas,
+ gas_req: Gas,
value: Bytes32,
- ) -> (u32, u64, UserOutcomeKind) {
+ ) -> (u32, Gas, UserOutcomeKind) {
self.call_request(
EvmApiMethod::ContractCall,
contract,
@@ -184,9 +194,9 @@ impl> EvmApi for EvmApiRequestor {
&mut self,
contract: Bytes20,
input: &[u8],
- gas_left: u64,
- gas_req: u64,
- ) -> (u32, u64, UserOutcomeKind) {
+ gas_left: Gas,
+ gas_req: Gas,
+ ) -> (u32, Gas, UserOutcomeKind) {
self.call_request(
EvmApiMethod::DelegateCall,
contract,
@@ -201,9 +211,9 @@ impl> EvmApi for EvmApiRequestor {
&mut self,
contract: Bytes20,
input: &[u8],
- gas_left: u64,
- gas_req: u64,
- ) -> (u32, u64, UserOutcomeKind) {
+ gas_left: Gas,
+ gas_req: Gas,
+ ) -> (u32, Gas, UserOutcomeKind) {
self.call_request(
EvmApiMethod::StaticCall,
contract,
@@ -218,8 +228,8 @@ impl> EvmApi for EvmApiRequestor {
&mut self,
code: Vec,
endowment: Bytes32,
- gas: u64,
- ) -> (Result, u32, u64) {
+ gas: Gas,
+ ) -> (Result, u32, Gas) {
self.create_request(EvmApiMethod::Create1, code, endowment, None, gas)
}
@@ -228,8 +238,8 @@ impl> EvmApi for EvmApiRequestor {
code: Vec,
endowment: Bytes32,
salt: Bytes32,
- gas: u64,
- ) -> (Result, u32, u64) {
+ gas: Gas,
+ ) -> (Result, u32, Gas) {
self.create_request(EvmApiMethod::Create2, code, endowment, Some(salt), gas)
}
@@ -250,15 +260,15 @@ impl> EvmApi for EvmApiRequestor {
Ok(())
}
- fn account_balance(&mut self, address: Bytes20) -> (Bytes32, u64) {
+ fn account_balance(&mut self, address: Bytes20) -> (Bytes32, Gas) {
let (res, _, cost) = self.request(EvmApiMethod::AccountBalance, address);
(res.try_into().unwrap(), cost)
}
- fn account_code(&mut self, address: Bytes20, gas_left: u64) -> (D, u64) {
+ fn account_code(&mut self, address: Bytes20, gas_left: Gas) -> (D, Gas) {
if let Some((stored_address, data)) = self.last_code.as_ref() {
if address == *stored_address {
- return (data.clone(), 0);
+ return (data.clone(), Gas(0));
}
}
let mut req = Vec::with_capacity(20 + 8);
@@ -270,12 +280,12 @@ impl> EvmApi for EvmApiRequestor {
(data, cost)
}
- fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64) {
+ fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, Gas) {
let (res, _, cost) = self.request(EvmApiMethod::AccountCodeHash, address);
(res.try_into().unwrap(), cost)
}
- fn add_pages(&mut self, pages: u16) -> u64 {
+ fn add_pages(&mut self, pages: u16) -> Gas {
self.request(EvmApiMethod::AddPages, pages.to_be_bytes()).2
}
@@ -284,18 +294,20 @@ impl> EvmApi for EvmApiRequestor {
name: &str,
args: &[u8],
outs: &[u8],
- start_ink: u64,
- end_ink: u64,
+ start_ink: Ink,
+ end_ink: Ink,
) {
let mut request = Vec::with_capacity(2 * 8 + 3 * 2 + name.len() + args.len() + outs.len());
request.extend(start_ink.to_be_bytes());
request.extend(end_ink.to_be_bytes());
- request.extend((name.len() as u16).to_be_bytes());
- request.extend((args.len() as u16).to_be_bytes());
- request.extend((outs.len() as u16).to_be_bytes());
+ // u32 is enough to represent the slices lengths because the WASM environment runs in 32 bits.
+ request.extend((name.len() as u32).to_be_bytes());
+ request.extend((args.len() as u32).to_be_bytes());
+ request.extend((outs.len() as u32).to_be_bytes());
request.extend(name.as_bytes());
request.extend(args);
request.extend(outs);
- self.request(EvmApiMethod::CaptureHostIO, request);
+ // ignore response (including gas) as we're just tracing
+ _ = self.request(EvmApiMethod::CaptureHostIO, request);
}
}
diff --git a/arbitrator/arbutil/src/evm/storage.rs b/arbitrator/arbutil/src/evm/storage.rs
index 32b60dd21b..5f688364d7 100644
--- a/arbitrator/arbutil/src/evm/storage.rs
+++ b/arbitrator/arbutil/src/evm/storage.rs
@@ -5,6 +5,8 @@ use crate::Bytes32;
use fnv::FnvHashMap as HashMap;
use std::ops::{Deref, DerefMut};
+use super::api::Gas;
+
/// Represents the EVM word at a given key.
#[derive(Debug)]
pub struct StorageWord {
@@ -37,23 +39,23 @@ pub struct StorageCache {
}
impl StorageCache {
- pub const REQUIRED_ACCESS_GAS: u64 = 10;
+ pub const REQUIRED_ACCESS_GAS: Gas = Gas(10);
- pub fn read_gas(&mut self) -> u64 {
+ pub fn read_gas(&mut self) -> Gas {
self.reads += 1;
match self.reads {
- 0..=32 => 0,
- 33..=128 => 2,
- _ => 10,
+ 0..=32 => Gas(0),
+ 33..=128 => Gas(2),
+ _ => Gas(10),
}
}
- pub fn write_gas(&mut self) -> u64 {
+ pub fn write_gas(&mut self) -> Gas {
self.writes += 1;
match self.writes {
- 0..=8 => 0,
- 9..=64 => 7,
- _ => 10,
+ 0..=8 => Gas(0),
+ 9..=64 => Gas(7),
+ _ => Gas(10),
}
}
}
diff --git a/arbitrator/arbutil/src/pricing.rs b/arbitrator/arbutil/src/pricing.rs
index 4614b02a2a..4d6bf827be 100644
--- a/arbitrator/arbutil/src/pricing.rs
+++ b/arbitrator/arbutil/src/pricing.rs
@@ -1,20 +1,22 @@
// Copyright 2023, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE
+use crate::evm::api::Ink;
+
/// For hostios that may return something.
-pub const HOSTIO_INK: u64 = 8400;
+pub const HOSTIO_INK: Ink = Ink(8400);
/// For hostios that include pointers.
-pub const PTR_INK: u64 = 13440 - HOSTIO_INK;
+pub const PTR_INK: Ink = Ink(13440).sub(HOSTIO_INK);
/// For hostios that involve an API cost.
-pub const EVM_API_INK: u64 = 59673;
+pub const EVM_API_INK: Ink = Ink(59673);
/// For hostios that involve a div or mod.
-pub const DIV_INK: u64 = 20000;
+pub const DIV_INK: Ink = Ink(20000);
/// For hostios that involve a mulmod.
-pub const MUL_MOD_INK: u64 = 24100;
+pub const MUL_MOD_INK: Ink = Ink(24100);
/// For hostios that involve an addmod.
-pub const ADD_MOD_INK: u64 = 21000;
+pub const ADD_MOD_INK: Ink = Ink(21000);
diff --git a/arbitrator/arbutil/src/types.rs b/arbitrator/arbutil/src/types.rs
index 6cf1d6cdf7..722a89b81e 100644
--- a/arbitrator/arbutil/src/types.rs
+++ b/arbitrator/arbutil/src/types.rs
@@ -8,6 +8,7 @@ use std::{
borrow::Borrow,
fmt,
ops::{Deref, DerefMut},
+ str::FromStr,
};
// These values must be kept in sync with `arbutil/preimage_type.go`,
@@ -83,6 +84,32 @@ impl From for Bytes32 {
}
}
+impl FromStr for Bytes32 {
+ type Err = &'static str;
+
+ fn from_str(s: &str) -> Result {
+ // Remove the "0x" prefix if present
+ let s = s.strip_prefix("0x").unwrap_or(s);
+
+ // Pad with leading zeros if the string is shorter than 64 characters (32 bytes)
+ let padded = format!("{:0>64}", s);
+
+ // Decode the hex string using the hex crate
+ let decoded_bytes = hex::decode(padded).map_err(|_| "Invalid hex string")?;
+
+ // Ensure the decoded bytes is exactly 32 bytes
+ if decoded_bytes.len() != 32 {
+ return Err("Hex string too long for Bytes32");
+ }
+
+ // Create a 32-byte array and fill it with the decoded bytes.
+ let mut b = [0u8; 32];
+ b.copy_from_slice(&decoded_bytes);
+
+ Ok(Bytes32(b))
+ }
+}
+
impl TryFrom<&[u8]> for Bytes32 {
type Error = std::array::TryFromSliceError;
@@ -249,3 +276,77 @@ impl From for Bytes20 {
<[u8; 20]>::from(x).into()
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_bytes32() {
+ let b = Bytes32::from(0x12345678u32);
+ let expected = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x12, 0x34, 0x56, 0x78,
+ ];
+ assert_eq!(b, Bytes32(expected));
+ }
+
+ #[test]
+ fn test_from_str_short() {
+ // Short hex string
+ let b = Bytes32::from_str("0x12345678").unwrap();
+ let expected = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x12, 0x34, 0x56, 0x78,
+ ];
+ assert_eq!(b, Bytes32(expected));
+ }
+
+ #[test]
+ fn test_from_str_very_short() {
+ // Short hex string
+ let b = Bytes32::from_str("0x1").unwrap();
+ let expected = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0x1,
+ ];
+ assert_eq!(b, Bytes32(expected));
+ }
+
+ #[test]
+ fn test_from_str_no_prefix() {
+ // Short hex string
+ let b = Bytes32::from_str("12345678").unwrap();
+ let expected = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x12, 0x34, 0x56, 0x78,
+ ];
+ assert_eq!(b, Bytes32(expected));
+ }
+
+ #[test]
+ fn test_from_str_full() {
+ // Full-length hex string
+ let b =
+ Bytes32::from_str("0x0000000000000000000000000000000000000000000000000000000012345678")
+ .unwrap();
+ let expected = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x12, 0x34, 0x56, 0x78,
+ ];
+ assert_eq!(b, Bytes32(expected));
+ }
+
+ #[test]
+ fn test_from_str_invalid_non_hex() {
+ let s = "0x123g5678"; // Invalid character 'g'
+ assert!(Bytes32::from_str(s).is_err());
+ }
+
+ #[test]
+ fn test_from_str_too_big() {
+ let s =
+ "0123456789ABCDEF0123456789ABCDEF01234567890123456789ABCDEF01234567890123456789ABCDEF0"; // 65 characters
+ assert!(Bytes32::from_str(s).is_err());
+ }
+}
diff --git a/arbitrator/bench/Cargo.toml b/arbitrator/bench/Cargo.toml
index 3ab5b99b08..74b948aca8 100644
--- a/arbitrator/bench/Cargo.toml
+++ b/arbitrator/bench/Cargo.toml
@@ -3,10 +3,6 @@ name = "bench"
version = "0.1.0"
edition = "2021"
-[lib]
-name = "bench"
-path = "src/lib.rs"
-
[[bin]]
name = "benchbin"
path = "src/bin.rs"
@@ -20,7 +16,6 @@ clap = { version = "4.4.8", features = ["derive"] }
gperftools = { version = "0.2.0", optional = true }
serde = { version = "1.0.130", features = ["derive", "rc"] }
serde_json = "1.0.67"
-serde_with = { version = "3.8.1", features = ["base64"] }
[features]
counters = []
diff --git a/arbitrator/bench/src/bin.rs b/arbitrator/bench/src/bin.rs
index f7e69f5373..f9bd85ce53 100644
--- a/arbitrator/bench/src/bin.rs
+++ b/arbitrator/bench/src/bin.rs
@@ -1,6 +1,5 @@
use std::{path::PathBuf, time::Duration};
-use bench::prepare::*;
use clap::Parser;
use eyre::bail;
@@ -10,21 +9,22 @@ use gperftools::profiler::PROFILER;
#[cfg(feature = "heapprof")]
use gperftools::heap_profiler::HEAP_PROFILER;
-use prover::machine::MachineStatus;
-
#[cfg(feature = "counters")]
use prover::{machine, memory, merkle};
+use prover::machine::MachineStatus;
+use prover::prepare::prepare_machine;
+
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
- /// Path to a preimages text file
+ /// Path to a preimages json file
#[arg(short, long)]
- preimages_path: PathBuf,
+ json_inputs: PathBuf,
/// Path to a machine.wavm.br
#[arg(short, long)]
- machine_path: PathBuf,
+ binary: PathBuf,
}
fn main() -> eyre::Result<()> {
@@ -33,7 +33,7 @@ fn main() -> eyre::Result<()> {
println!("Running benchmark with always merkleize feature on");
for step_size in step_sizes {
- let mut machine = prepare_machine(args.preimages_path.clone(), args.machine_path.clone())?;
+ let mut machine = prepare_machine(args.json_inputs.clone(), args.binary.clone())?;
let _ = machine.hash();
let mut hash_times = vec![];
let mut step_times = vec![];
diff --git a/arbitrator/bench/src/lib.rs b/arbitrator/bench/src/lib.rs
deleted file mode 100644
index 5f7c024094..0000000000
--- a/arbitrator/bench/src/lib.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub mod parse_input;
-pub mod prepare;
diff --git a/arbitrator/bench/src/parse_input.rs b/arbitrator/bench/src/parse_input.rs
deleted file mode 100644
index decc67372a..0000000000
--- a/arbitrator/bench/src/parse_input.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-use arbutil::Bytes32;
-use serde::{Deserialize, Serialize};
-use serde_json;
-use serde_with::base64::Base64;
-use serde_with::As;
-use serde_with::DisplayFromStr;
-use std::{
- collections::HashMap,
- io::{self, BufRead},
-};
-
-mod prefixed_hex {
- use serde::{self, Deserialize, Deserializer, Serializer};
-
- pub fn serialize(bytes: &Vec, serializer: S) -> Result
- where
- S: Serializer,
- {
- serializer.serialize_str(&format!("0x{}", hex::encode(bytes)))
- }
-
- pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error>
- where
- D: Deserializer<'de>,
- {
- let s = String::deserialize(deserializer)?;
- if let Some(s) = s.strip_prefix("0x") {
- hex::decode(s).map_err(serde::de::Error::custom)
- } else {
- Err(serde::de::Error::custom("missing 0x prefix"))
- }
- }
-}
-
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct PreimageMap(HashMap>);
-
-#[derive(Debug, Clone, Deserialize, Serialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct BatchInfo {
- pub number: u64,
- #[serde(with = "As::")]
- pub data_b64: Vec,
-}
-
-#[derive(Debug, Deserialize, Serialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct StartState {
- #[serde(with = "prefixed_hex")]
- pub block_hash: Vec,
- #[serde(with = "prefixed_hex")]
- pub send_root: Vec,
- pub batch: u64,
- pub pos_in_batch: u64,
-}
-
-#[derive(Debug, Deserialize, Serialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct FileData {
- pub id: u64,
- pub has_delayed_msg: bool,
- pub delayed_msg_nr: u64,
- #[serde(with = "As::>>")]
- pub preimages_b64: HashMap>>,
- pub batch_info: Vec,
- #[serde(with = "As::")]
- pub delayed_msg_b64: Vec,
- pub start_state: StartState,
-}
-
-impl FileData {
- pub fn from_reader(mut reader: R) -> io::Result {
- let data = serde_json::from_reader(&mut reader)?;
- Ok(data)
- }
-}
diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs
index 2a3c5c5616..0d74c74ef6 100644
--- a/arbitrator/jit/src/machine.rs
+++ b/arbitrator/jit/src/machine.rs
@@ -2,8 +2,8 @@
// For license information, see https://github.com/nitro/blob/master/LICENSE
use crate::{
- arbcompress, caller_env::GoRuntimeState, program, socket, stylus_backend::CothreadHandler,
- wasip1_stub, wavmio, Opts,
+ arbcompress, caller_env::GoRuntimeState, prepare::prepare_env, program, socket,
+ stylus_backend::CothreadHandler, wasip1_stub, wavmio, Opts,
};
use arbutil::{Bytes32, Color, PreimageType};
use eyre::{bail, ErrReport, Result, WrapErr};
@@ -129,7 +129,9 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto
"send_response" => func!(program::send_response),
"create_stylus_config" => func!(program::create_stylus_config),
"create_evm_data" => func!(program::create_evm_data),
+ "create_evm_data_v2" => func!(program::create_evm_data_v2),
"activate" => func!(program::activate),
+ "activate_v2" => func!(program::activate_v2),
},
};
@@ -213,72 +215,76 @@ pub struct WasmEnv {
impl WasmEnv {
pub fn cli(opts: &Opts) -> Result {
- let mut env = WasmEnv::default();
- env.process.forks = opts.forks;
- env.process.debug = opts.debug;
+ if let Some(json_inputs) = opts.json_inputs.clone() {
+ prepare_env(json_inputs, opts.debug)
+ } else {
+ let mut env = WasmEnv::default();
+ env.process.forks = opts.forks;
+ env.process.debug = opts.debug;
- let mut inbox_position = opts.inbox_position;
- let mut delayed_position = opts.delayed_inbox_position;
+ let mut inbox_position = opts.inbox_position;
+ let mut delayed_position = opts.delayed_inbox_position;
- for path in &opts.inbox {
- let mut msg = vec![];
- File::open(path)?.read_to_end(&mut msg)?;
- env.sequencer_messages.insert(inbox_position, msg);
- inbox_position += 1;
- }
- for path in &opts.delayed_inbox {
- let mut msg = vec![];
- File::open(path)?.read_to_end(&mut msg)?;
- env.delayed_messages.insert(delayed_position, msg);
- delayed_position += 1;
- }
+ for path in &opts.inbox {
+ let mut msg = vec![];
+ File::open(path)?.read_to_end(&mut msg)?;
+ env.sequencer_messages.insert(inbox_position, msg);
+ inbox_position += 1;
+ }
+ for path in &opts.delayed_inbox {
+ let mut msg = vec![];
+ File::open(path)?.read_to_end(&mut msg)?;
+ env.delayed_messages.insert(delayed_position, msg);
+ delayed_position += 1;
+ }
- if let Some(path) = &opts.preimages {
- let mut file = BufReader::new(File::open(path)?);
- let mut preimages = Vec::new();
- let filename = path.to_string_lossy();
- loop {
- let mut size_buf = [0u8; 8];
- match file.read_exact(&mut size_buf) {
- Ok(()) => {}
- Err(err) if err.kind() == ErrorKind::UnexpectedEof => break,
- Err(err) => bail!("Failed to parse {filename}: {}", err),
+ if let Some(path) = &opts.preimages {
+ let mut file = BufReader::new(File::open(path)?);
+ let mut preimages = Vec::new();
+ let filename = path.to_string_lossy();
+ loop {
+ let mut size_buf = [0u8; 8];
+ match file.read_exact(&mut size_buf) {
+ Ok(()) => {}
+ Err(err) if err.kind() == ErrorKind::UnexpectedEof => break,
+ Err(err) => bail!("Failed to parse {filename}: {}", err),
+ }
+ let size = u64::from_le_bytes(size_buf) as usize;
+ let mut buf = vec![0u8; size];
+ file.read_exact(&mut buf)?;
+ preimages.push(buf);
+ }
+ let keccak_preimages = env.preimages.entry(PreimageType::Keccak256).or_default();
+ for preimage in preimages {
+ let mut hasher = Keccak256::new();
+ hasher.update(&preimage);
+ let hash = hasher.finalize().into();
+ keccak_preimages.insert(hash, preimage);
}
- let size = u64::from_le_bytes(size_buf) as usize;
- let mut buf = vec![0u8; size];
- file.read_exact(&mut buf)?;
- preimages.push(buf);
- }
- let keccak_preimages = env.preimages.entry(PreimageType::Keccak256).or_default();
- for preimage in preimages {
- let mut hasher = Keccak256::new();
- hasher.update(&preimage);
- let hash = hasher.finalize().into();
- keccak_preimages.insert(hash, preimage);
}
- }
- fn parse_hex(arg: &Option, name: &str) -> Result {
- match arg {
- Some(arg) => {
- let mut arg = arg.as_str();
- if arg.starts_with("0x") {
- arg = &arg[2..];
+ fn parse_hex(arg: &Option, name: &str) -> Result {
+ match arg {
+ Some(arg) => {
+ let mut arg = arg.as_str();
+ if arg.starts_with("0x") {
+ arg = &arg[2..];
+ }
+ let mut bytes32 = [0u8; 32];
+ hex::decode_to_slice(arg, &mut bytes32)
+ .wrap_err_with(|| format!("failed to parse {} contents", name))?;
+ Ok(bytes32.into())
}
- let mut bytes32 = [0u8; 32];
- hex::decode_to_slice(arg, &mut bytes32)
- .wrap_err_with(|| format!("failed to parse {} contents", name))?;
- Ok(bytes32.into())
+ None => Ok(Bytes32::default()),
}
- None => Ok(Bytes32::default()),
}
- }
- let last_block_hash = parse_hex(&opts.last_block_hash, "--last-block-hash")?;
- let last_send_root = parse_hex(&opts.last_send_root, "--last-send-root")?;
- env.small_globals = [opts.inbox_position, opts.position_within_message];
- env.large_globals = [last_block_hash, last_send_root];
- Ok(env)
+ let last_block_hash = parse_hex(&opts.last_block_hash, "--last-block-hash")?;
+ let last_send_root = parse_hex(&opts.last_send_root, "--last-send-root")?;
+ env.small_globals = [opts.inbox_position, opts.position_within_message];
+ env.large_globals = [last_block_hash, last_send_root];
+ Ok(env)
+ }
}
pub fn send_results(&mut self, error: Option, memory_used: Pages) {
diff --git a/arbitrator/jit/src/main.rs b/arbitrator/jit/src/main.rs
index e432dc215c..6e44500215 100644
--- a/arbitrator/jit/src/main.rs
+++ b/arbitrator/jit/src/main.rs
@@ -10,6 +10,7 @@ use structopt::StructOpt;
mod arbcompress;
mod caller_env;
mod machine;
+mod prepare;
mod program;
mod socket;
mod stylus_backend;
@@ -46,6 +47,10 @@ pub struct Opts {
debug: bool,
#[structopt(long)]
require_success: bool,
+ // JSON inputs supercede any of the command-line inputs which could
+ // be specified in the JSON file.
+ #[structopt(long)]
+ json_inputs: Option,
}
fn main() -> Result<()> {
diff --git a/arbitrator/jit/src/prepare.rs b/arbitrator/jit/src/prepare.rs
new file mode 100644
index 0000000000..e7a7ba0f4d
--- /dev/null
+++ b/arbitrator/jit/src/prepare.rs
@@ -0,0 +1,73 @@
+// Copyright 2022-2024, Offchain Labs, Inc.
+// For license information, see https://github.com/nitro/blob/master/LICENSE
+
+use crate::WasmEnv;
+use arbutil::{Bytes32, PreimageType};
+use eyre::Ok;
+use prover::parse_input::FileData;
+use std::env;
+use std::fs::File;
+use std::io::BufReader;
+use std::path::PathBuf;
+
+// local_target matches rawdb.LocalTarget() on the go side.
+// While generating json_inputs file, one should make sure user_wasms map
+// has entry for the system's arch that jit validation is being run on
+pub fn local_target() -> String {
+ if env::consts::OS == "linux" {
+ match env::consts::ARCH {
+ "aarch64" => "arm64".to_string(),
+ "x86_64" => "amd64".to_string(),
+ _ => "host".to_string(),
+ }
+ } else {
+ "host".to_string()
+ }
+}
+
+pub fn prepare_env(json_inputs: PathBuf, debug: bool) -> eyre::Result {
+ let file = File::open(json_inputs)?;
+ let reader = BufReader::new(file);
+
+ let data = FileData::from_reader(reader)?;
+
+ let mut env = WasmEnv::default();
+ env.process.forks = false; // Should be set to false when using json_inputs
+ env.process.debug = debug;
+
+ let block_hash: [u8; 32] = data.start_state.block_hash.try_into().unwrap();
+ let block_hash: Bytes32 = block_hash.into();
+ let send_root: [u8; 32] = data.start_state.send_root.try_into().unwrap();
+ let send_root: Bytes32 = send_root.into();
+ let bytes32_vals: [Bytes32; 2] = [block_hash, send_root];
+ let u64_vals: [u64; 2] = [data.start_state.batch, data.start_state.pos_in_batch];
+ env.small_globals = u64_vals;
+ env.large_globals = bytes32_vals;
+
+ for batch_info in data.batch_info.iter() {
+ env.sequencer_messages
+ .insert(batch_info.number, batch_info.data_b64.clone());
+ }
+
+ if data.delayed_msg_nr != 0 && !data.delayed_msg_b64.is_empty() {
+ env.delayed_messages
+ .insert(data.delayed_msg_nr, data.delayed_msg_b64.clone());
+ }
+
+ for (ty, inner_map) in data.preimages_b64 {
+ let preimage_ty = PreimageType::try_from(ty as u8)?;
+ let map = env.preimages.entry(preimage_ty).or_default();
+ for (hash, preimage) in inner_map {
+ map.insert(hash, preimage);
+ }
+ }
+
+ if let Some(user_wasms) = data.user_wasms.get(&local_target()) {
+ for (module_hash, module_asm) in user_wasms.iter() {
+ env.module_asms
+ .insert(*module_hash, module_asm.as_vec().into());
+ }
+ }
+
+ Ok(env)
+}
diff --git a/arbitrator/jit/src/program.rs b/arbitrator/jit/src/program.rs
index c608a3cf85..f10a059748 100644
--- a/arbitrator/jit/src/program.rs
+++ b/arbitrator/jit/src/program.rs
@@ -6,6 +6,7 @@
use crate::caller_env::JitEnv;
use crate::machine::{Escape, MaybeEscape, WasmEnvMut};
use crate::stylus_backend::exec_wasm;
+use arbutil::evm::api::Gas;
use arbutil::Bytes32;
use arbutil::{evm::EvmData, format::DebugBytes, heapify};
use caller_env::{GuestPtr, MemAccess};
@@ -16,8 +17,45 @@ use prover::{
programs::{config::PricingParams, prelude::*},
};
-/// activates a user program
+const DEFAULT_STYLUS_ARBOS_VERSION: u64 = 31;
+
pub fn activate(
+ env: WasmEnvMut,
+ wasm_ptr: GuestPtr,
+ wasm_size: u32,
+ pages_ptr: GuestPtr,
+ asm_estimate_ptr: GuestPtr,
+ init_cost_ptr: GuestPtr,
+ cached_init_cost_ptr: GuestPtr,
+ stylus_version: u16,
+ debug: u32,
+ codehash: GuestPtr,
+ module_hash_ptr: GuestPtr,
+ gas_ptr: GuestPtr,
+ err_buf: GuestPtr,
+ err_buf_len: u32,
+) -> Result {
+ activate_v2(
+ env,
+ wasm_ptr,
+ wasm_size,
+ pages_ptr,
+ asm_estimate_ptr,
+ init_cost_ptr,
+ cached_init_cost_ptr,
+ stylus_version,
+ DEFAULT_STYLUS_ARBOS_VERSION,
+ debug,
+ codehash,
+ module_hash_ptr,
+ gas_ptr,
+ err_buf,
+ err_buf_len,
+ )
+}
+
+/// activates a user program
+pub fn activate_v2(
mut env: WasmEnvMut,
wasm_ptr: GuestPtr,
wasm_size: u32,
@@ -25,7 +63,8 @@ pub fn activate(
asm_estimate_ptr: GuestPtr,
init_cost_ptr: GuestPtr,
cached_init_cost_ptr: GuestPtr,
- version: u16,
+ stylus_version: u16,
+ arbos_version_for_gas: u64,
debug: u32,
codehash: GuestPtr,
module_hash_ptr: GuestPtr,
@@ -40,7 +79,15 @@ pub fn activate(
let page_limit = mem.read_u16(pages_ptr);
let gas_left = &mut mem.read_u64(gas_ptr);
- match Module::activate(&wasm, codehash, version, page_limit, debug, gas_left) {
+ match Module::activate(
+ &wasm,
+ codehash,
+ stylus_version,
+ arbos_version_for_gas,
+ page_limit,
+ debug,
+ gas_left,
+ ) {
Ok((module, data)) => {
mem.write_u64(gas_ptr, *gas_left);
mem.write_u16(pages_ptr, data.footprint);
@@ -85,7 +132,7 @@ pub fn new_program(
// buy ink
let pricing = config.stylus.pricing;
- let ink = pricing.gas_to_ink(gas);
+ let ink = pricing.gas_to_ink(Gas(gas));
let Some(module) = exec.module_asms.get(&compiled_hash).cloned() else {
return Err(Escape::Failure(format!(
@@ -171,7 +218,7 @@ pub fn set_response(
let raw_data = mem.read_slice(raw_data_ptr, raw_data_len as usize);
let thread = exec.threads.last_mut().unwrap();
- thread.set_response(id, result, raw_data, gas)
+ thread.set_response(id, result, raw_data, Gas(gas))
}
/// sends previos response
@@ -222,9 +269,47 @@ pub fn create_stylus_config(
Ok(res as u64)
}
-/// Creates an `EvmData` handler from its component parts.
pub fn create_evm_data(
+ env: WasmEnvMut,
+ block_basefee_ptr: GuestPtr,
+ chainid: u64,
+ block_coinbase_ptr: GuestPtr,
+ block_gas_limit: u64,
+ block_number: u64,
+ block_timestamp: u64,
+ contract_address_ptr: GuestPtr,
+ module_hash_ptr: GuestPtr,
+ msg_sender_ptr: GuestPtr,
+ msg_value_ptr: GuestPtr,
+ tx_gas_price_ptr: GuestPtr,
+ tx_origin_ptr: GuestPtr,
+ cached: u32,
+ reentrant: u32,
+) -> Result {
+ create_evm_data_v2(
+ env,
+ DEFAULT_STYLUS_ARBOS_VERSION,
+ block_basefee_ptr,
+ chainid,
+ block_coinbase_ptr,
+ block_gas_limit,
+ block_number,
+ block_timestamp,
+ contract_address_ptr,
+ module_hash_ptr,
+ msg_sender_ptr,
+ msg_value_ptr,
+ tx_gas_price_ptr,
+ tx_origin_ptr,
+ cached,
+ reentrant,
+ )
+}
+
+/// Creates an `EvmData` handler from its component parts.
+pub fn create_evm_data_v2(
mut env: WasmEnvMut,
+ arbos_version: u64,
block_basefee_ptr: GuestPtr,
chainid: u64,
block_coinbase_ptr: GuestPtr,
@@ -243,6 +328,7 @@ pub fn create_evm_data(
let (mut mem, _) = env.jit_env();
let evm_data = EvmData {
+ arbos_version,
block_basefee: mem.read_bytes32(block_basefee_ptr),
cached: cached != 0,
chainid,
diff --git a/arbitrator/jit/src/stylus_backend.rs b/arbitrator/jit/src/stylus_backend.rs
index 61dbf258d4..0d8c477c6c 100644
--- a/arbitrator/jit/src/stylus_backend.rs
+++ b/arbitrator/jit/src/stylus_backend.rs
@@ -4,7 +4,7 @@
#![allow(clippy::too_many_arguments)]
use crate::machine::{Escape, MaybeEscape};
-use arbutil::evm::api::VecReader;
+use arbutil::evm::api::{Gas, Ink, VecReader};
use arbutil::evm::{
api::{EvmApiMethod, EVM_API_METHOD_REQ_OFFSET},
req::EvmApiRequestor,
@@ -28,7 +28,7 @@ use stylus::{native::NativeInstance, run::RunProgram};
struct MessageToCothread {
result: Vec,
raw_data: Vec,
- cost: u64,
+ cost: Gas,
}
#[derive(Clone)]
@@ -47,7 +47,7 @@ impl RequestHandler for CothreadRequestor {
&mut self,
req_type: EvmApiMethod,
req_data: impl AsRef<[u8]>,
- ) -> (Vec, VecReader, u64) {
+ ) -> (Vec, VecReader, Gas) {
let msg = MessageFromCothread {
req_type: req_type as u32 + EVM_API_METHOD_REQ_OFFSET,
req_data: req_data.as_ref().to_vec(),
@@ -104,7 +104,7 @@ impl CothreadHandler {
id: u32,
result: Vec,
raw_data: Vec,
- cost: u64,
+ cost: Gas,
) -> MaybeEscape {
let Some(msg) = self.last_request.clone() else {
return Escape::hostio("trying to set response but no message pending");
@@ -131,7 +131,7 @@ pub fn exec_wasm(
compile: CompileConfig,
config: StylusConfig,
evm_data: EvmData,
- ink: u64,
+ ink: Ink,
) -> Result {
let (tothread_tx, tothread_rx) = mpsc::sync_channel::(0);
let (fromthread_tx, fromthread_rx) = mpsc::sync_channel::(0);
@@ -150,7 +150,7 @@ pub fn exec_wasm(
let outcome = instance.run_main(&calldata, config, ink);
let ink_left = match outcome.as_ref() {
- Ok(UserOutcome::OutOfStack) => 0, // take all ink when out of stack
+ Ok(UserOutcome::OutOfStack) => Ink(0), // take all ink when out of stack
_ => instance.ink_left().into(),
};
diff --git a/arbitrator/jit/src/wavmio.rs b/arbitrator/jit/src/wavmio.rs
index 062d18d8e9..0ca666d3b2 100644
--- a/arbitrator/jit/src/wavmio.rs
+++ b/arbitrator/jit/src/wavmio.rs
@@ -8,8 +8,6 @@ use crate::{
};
use arbutil::{Color, PreimageType};
use caller_env::{GuestPtr, MemAccess};
-use sha2::Sha256;
-use sha3::{Digest, Keccak256};
use std::{
io,
io::{BufReader, BufWriter, ErrorKind},
@@ -170,19 +168,25 @@ pub fn resolve_preimage_impl(
error!("Missing requested preimage for hash {hash_hex} in {name}")
};
- // Check if preimage rehashes to the provided hash. Exclude blob preimages
- let calculated_hash: [u8; 32] = match preimage_type {
- PreimageType::Keccak256 => Keccak256::digest(preimage).into(),
- PreimageType::Sha2_256 => Sha256::digest(preimage).into(),
- PreimageType::EthVersionedHash => *hash,
- };
- if calculated_hash != *hash {
- error!(
- "Calculated hash {} of preimage {} does not match provided hash {}",
- hex::encode(calculated_hash),
- hex::encode(preimage),
- hex::encode(*hash)
- );
+ #[cfg(debug_assertions)]
+ {
+ use sha2::Sha256;
+ use sha3::{Digest, Keccak256};
+
+ // Check if preimage rehashes to the provided hash. Exclude blob preimages
+ let calculated_hash: [u8; 32] = match preimage_type {
+ PreimageType::Keccak256 => Keccak256::digest(preimage).into(),
+ PreimageType::Sha2_256 => Sha256::digest(preimage).into(),
+ PreimageType::EthVersionedHash => *hash,
+ };
+ if calculated_hash != *hash {
+ error!(
+ "Calculated hash {} of preimage {} does not match provided hash {}",
+ hex::encode(calculated_hash),
+ hex::encode(preimage),
+ hex::encode(*hash)
+ );
+ }
}
if offset % 32 != 0 {
diff --git a/arbitrator/prover/Cargo.toml b/arbitrator/prover/Cargo.toml
index 5475647765..da329b1cb5 100644
--- a/arbitrator/prover/Cargo.toml
+++ b/arbitrator/prover/Cargo.toml
@@ -19,10 +19,10 @@ num = "0.4"
rustc-demangle = "0.1.21"
serde = { version = "1.0.130", features = ["derive", "rc"] }
serde_json = "1.0.67"
+serde_with = { version = "3.8.1", features = ["base64"] }
sha3 = "0.9.1"
static_assertions = "1.1.0"
structopt = "0.3.23"
-serde_with = "1.12.1"
parking_lot = "0.12.1"
lazy_static.workspace = true
itertools = "0.10.5"
diff --git a/arbitrator/prover/src/binary.rs b/arbitrator/prover/src/binary.rs
index aa5537476c..2260f6bf48 100644
--- a/arbitrator/prover/src/binary.rs
+++ b/arbitrator/prover/src/binary.rs
@@ -9,7 +9,9 @@ use crate::{
},
value::{ArbValueType, FunctionType, IntegerValType, Value},
};
-use arbutil::{math::SaturatingSum, Bytes32, Color, DebugColor};
+use arbutil::{
+ evm::ARBOS_VERSION_STYLUS_CHARGING_FIXES, math::SaturatingSum, Bytes32, Color, DebugColor,
+};
use eyre::{bail, ensure, eyre, Result, WrapErr};
use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet};
use nom::{
@@ -641,6 +643,7 @@ impl<'a> WasmBinary<'a> {
/// Parses and instruments a user wasm
pub fn parse_user(
wasm: &'a [u8],
+ arbos_version_for_gas: u64,
page_limit: u16,
compile: &CompileConfig,
codehash: &Bytes32,
@@ -678,6 +681,10 @@ impl<'a> WasmBinary<'a> {
limit!(65536, code.expr.len(), "opcodes in func body");
}
+ if arbos_version_for_gas >= ARBOS_VERSION_STYLUS_CHARGING_FIXES {
+ limit!(513, bin.imports.len(), "imports")
+ }
+
let table_entries = bin.tables.iter().map(|x| x.initial).saturating_sum();
limit!(4096, table_entries, "table entries");
diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs
index 0f537478eb..08473c2598 100644
--- a/arbitrator/prover/src/lib.rs
+++ b/arbitrator/prover/src/lib.rs
@@ -11,6 +11,8 @@ pub mod machine;
/// cbindgen:ignore
pub mod memory;
pub mod merkle;
+pub mod parse_input;
+pub mod prepare;
mod print;
pub mod programs;
mod reinterpret;
diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs
index 358876bd25..dec355ac7c 100644
--- a/arbitrator/prover/src/machine.rs
+++ b/arbitrator/prover/src/machine.rs
@@ -371,13 +371,16 @@ impl Module {
for import in &bin.imports {
let module = import.module;
let have_ty = &bin.types[import.offset as usize];
- let (forward, import_name) = match import.name.strip_prefix(Module::FORWARDING_PREFIX) {
- Some(name) => (true, name),
- None => (false, import.name),
- };
+ // allow_hostapi is only set for system modules like the
+ // forwarder. We restrict stripping the prefix for user modules.
+ let (forward, import_name) =
+ if allow_hostapi && import.name.starts_with(Self::FORWARDING_PREFIX) {
+ (true, &import.name[Self::FORWARDING_PREFIX.len()..])
+ } else {
+ (false, import.name)
+ };
- let mut qualified_name = format!("{module}__{import_name}");
- qualified_name = qualified_name.replace(&['/', '.', '-'] as &[char], "_");
+ let qualified_name = format!("{module}__{import_name}");
let func = if let Some(import) = available_imports.get(&qualified_name) {
let call = match forward {
@@ -1813,7 +1816,12 @@ impl Machine {
}
#[cfg(feature = "native")]
- pub fn call_user_func(&mut self, func: &str, args: Vec, ink: u64) -> Result> {
+ pub fn call_user_func(
+ &mut self,
+ func: &str,
+ args: Vec,
+ ink: arbutil::evm::api::Ink,
+ ) -> Result> {
self.set_ink(ink);
self.call_function("user", func, args)
}
diff --git a/arbitrator/prover/src/main.rs b/arbitrator/prover/src/main.rs
index dba32e0e72..a889cc60f3 100644
--- a/arbitrator/prover/src/main.rs
+++ b/arbitrator/prover/src/main.rs
@@ -8,6 +8,7 @@ use eyre::{eyre, Context, Result};
use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet};
use prover::{
machine::{GlobalState, InboxIdentifier, Machine, MachineStatus, PreimageResolver, ProofInfo},
+ prepare::prepare_machine,
utils::{file_bytes, hash_preimage, CBytes},
wavm::Opcode,
};
@@ -86,6 +87,10 @@ struct Opts {
skip_until_host_io: bool,
#[structopt(long)]
max_steps: Option,
+ // JSON inputs supercede any of the command-line inputs which could
+ // be specified in the JSON file.
+ #[structopt(long)]
+ json_inputs: Option,
}
fn file_with_stub_header(path: &Path, headerlength: usize) -> Result> {
@@ -135,83 +140,8 @@ fn main() -> Result<()> {
}
}
}
- let mut inbox_contents = HashMap::default();
- let mut inbox_position = opts.inbox_position;
- let mut delayed_position = opts.delayed_inbox_position;
- let inbox_header_len;
- let delayed_header_len;
- if opts.inbox_add_stub_headers {
- inbox_header_len = INBOX_HEADER_LEN;
- delayed_header_len = DELAYED_HEADER_LEN + 1;
- } else {
- inbox_header_len = 0;
- delayed_header_len = 0;
- }
-
- for path in opts.inbox {
- inbox_contents.insert(
- (InboxIdentifier::Sequencer, inbox_position),
- file_with_stub_header(&path, inbox_header_len)?,
- );
- println!("read file {:?} to seq. inbox {}", &path, inbox_position);
- inbox_position += 1;
- }
- for path in opts.delayed_inbox {
- inbox_contents.insert(
- (InboxIdentifier::Delayed, delayed_position),
- file_with_stub_header(&path, delayed_header_len)?,
- );
- delayed_position += 1;
- }
- let mut preimages: HashMap> = HashMap::default();
- if let Some(path) = opts.preimages {
- let mut file = BufReader::new(File::open(path)?);
- loop {
- let mut ty_buf = [0u8; 1];
- match file.read_exact(&mut ty_buf) {
- Ok(()) => {}
- Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
- Err(e) => return Err(e.into()),
- }
- let preimage_ty: PreimageType = ty_buf[0].try_into()?;
-
- let mut size_buf = [0u8; 8];
- file.read_exact(&mut size_buf)?;
- let size = u64::from_le_bytes(size_buf) as usize;
- let mut buf = vec![0u8; size];
- file.read_exact(&mut buf)?;
-
- let hash = hash_preimage(&buf, preimage_ty)?;
- preimages
- .entry(preimage_ty)
- .or_default()
- .insert(hash.into(), buf.as_slice().into());
- }
- }
- let preimage_resolver =
- Arc::new(move |_, ty, hash| preimages.get(&ty).and_then(|m| m.get(&hash)).cloned())
- as PreimageResolver;
-
- let last_block_hash = decode_hex_arg(&opts.last_block_hash, "--last-block-hash")?;
- let last_send_root = decode_hex_arg(&opts.last_send_root, "--last-send-root")?;
-
- let global_state = GlobalState {
- u64_vals: [opts.inbox_position, opts.position_within_message],
- bytes32_vals: [last_block_hash, last_send_root],
- };
-
- let mut mach = Machine::from_paths(
- &opts.libraries,
- &opts.binary,
- true,
- opts.allow_hostapi,
- opts.debug_funcs,
- true,
- global_state,
- inbox_contents,
- preimage_resolver,
- )?;
+ let mut mach = initialize_machine(&opts)?;
for path in &opts.stylus_modules {
let err = || eyre!("failed to read module at {}", path.to_string_lossy().red());
@@ -414,6 +344,13 @@ fn main() -> Result<()> {
});
}
+ println!(
+ "End GlobalState:\n BlockHash: {:?}\n SendRoot: {:?}\n Batch: {}\n PosInBatch: {}",
+ mach.get_global_state().bytes32_vals[0],
+ mach.get_global_state().bytes32_vals[1],
+ mach.get_global_state().u64_vals[0],
+ mach.get_global_state().u64_vals[1]
+ );
println!("End machine status: {:?}", mach.get_status());
println!("End machine hash: {}", mach.hash());
println!("End machine stack: {:?}", mach.get_data_stack());
@@ -462,7 +399,6 @@ fn main() -> Result<()> {
}
}
}
-
let opts_binary = opts.binary;
let opts_libraries = opts.libraries;
let format_pc = |module_num: usize, func_num: usize| -> (String, String) {
@@ -543,3 +479,87 @@ fn main() -> Result<()> {
}
Ok(())
}
+
+fn initialize_machine(opts: &Opts) -> eyre::Result {
+ if let Some(json_inputs) = opts.json_inputs.clone() {
+ prepare_machine(json_inputs, opts.binary.clone())
+ } else {
+ let mut inbox_contents = HashMap::default();
+ let mut inbox_position = opts.inbox_position;
+ let mut delayed_position = opts.delayed_inbox_position;
+ let inbox_header_len;
+ let delayed_header_len;
+ if opts.inbox_add_stub_headers {
+ inbox_header_len = INBOX_HEADER_LEN;
+ delayed_header_len = DELAYED_HEADER_LEN + 1;
+ } else {
+ inbox_header_len = 0;
+ delayed_header_len = 0;
+ }
+
+ for path in opts.inbox.clone() {
+ inbox_contents.insert(
+ (InboxIdentifier::Sequencer, inbox_position),
+ file_with_stub_header(&path, inbox_header_len)?,
+ );
+ println!("read file {:?} to seq. inbox {}", &path, inbox_position);
+ inbox_position += 1;
+ }
+ for path in opts.delayed_inbox.clone() {
+ inbox_contents.insert(
+ (InboxIdentifier::Delayed, delayed_position),
+ file_with_stub_header(&path, delayed_header_len)?,
+ );
+ delayed_position += 1;
+ }
+
+ let mut preimages: HashMap> = HashMap::default();
+ if let Some(path) = opts.preimages.clone() {
+ let mut file = BufReader::new(File::open(path)?);
+ loop {
+ let mut ty_buf = [0u8; 1];
+ match file.read_exact(&mut ty_buf) {
+ Ok(()) => {}
+ Err(e) if e.kind() == ErrorKind::UnexpectedEof => break,
+ Err(e) => return Err(e.into()),
+ }
+ let preimage_ty: PreimageType = ty_buf[0].try_into()?;
+
+ let mut size_buf = [0u8; 8];
+ file.read_exact(&mut size_buf)?;
+ let size = u64::from_le_bytes(size_buf) as usize;
+ let mut buf = vec![0u8; size];
+ file.read_exact(&mut buf)?;
+
+ let hash = hash_preimage(&buf, preimage_ty)?;
+ preimages
+ .entry(preimage_ty)
+ .or_default()
+ .insert(hash.into(), buf.as_slice().into());
+ }
+ }
+ let preimage_resolver =
+ Arc::new(move |_, ty, hash| preimages.get(&ty).and_then(|m| m.get(&hash)).cloned())
+ as PreimageResolver;
+
+ let last_block_hash = decode_hex_arg(&opts.last_block_hash, "--last-block-hash")?;
+ let last_send_root = decode_hex_arg(&opts.last_send_root, "--last-send-root")?;
+
+ let global_state = GlobalState {
+ u64_vals: [opts.inbox_position, opts.position_within_message],
+ bytes32_vals: [last_block_hash, last_send_root],
+ };
+
+ Machine::from_paths(
+ &opts.libraries,
+ &opts.binary,
+ true,
+ opts.allow_hostapi,
+ opts.debug_funcs,
+ true,
+ global_state,
+ inbox_contents,
+ preimage_resolver,
+ )
+ }
+}
diff --git a/arbitrator/prover/src/merkle.rs b/arbitrator/prover/src/merkle.rs
index 4a1278b4cb..fbd704dfc6 100644
--- a/arbitrator/prover/src/merkle.rs
+++ b/arbitrator/prover/src/merkle.rs
@@ -549,8 +549,7 @@ mod test {
let mut empty_node = Bytes32([
57, 29, 211, 154, 252, 227, 18, 99, 65, 126, 203, 166, 252, 232, 32, 3, 98, 194, 254,
186, 118, 14, 139, 192, 101, 156, 55, 194, 101, 11, 11, 168,
- ])
- .clone();
+ ]);
for _ in 0..64 {
print!("Bytes32([");
for i in 0..32 {
@@ -607,7 +606,7 @@ mod test {
for layer in 0..64 {
// empty_hash_at is just a lookup, but empty_hash is calculated iteratively.
assert_eq!(empty_hash_at(ty, layer), &empty_hash);
- empty_hash = hash_node(ty, &empty_hash, &empty_hash);
+ empty_hash = hash_node(ty, empty_hash, empty_hash);
}
}
}
diff --git a/arbitrator/prover/src/parse_input.rs b/arbitrator/prover/src/parse_input.rs
new file mode 100644
index 0000000000..fa7adb4c41
--- /dev/null
+++ b/arbitrator/prover/src/parse_input.rs
@@ -0,0 +1,112 @@
+use arbutil::Bytes32;
+use serde::Deserialize;
+use serde_json;
+use serde_with::base64::Base64;
+use serde_with::As;
+use serde_with::DisplayFromStr;
+use std::{
+ collections::HashMap,
+ io::{self, BufRead},
+};
+
+/// prefixed_hex deserializes hex strings which are prefixed with `0x`
+///
+/// The default hex deserializer does not support prefixed hex strings.
+///
+/// It is an error to use this deserializer on a string that does not
+/// begin with `0x`.
+mod prefixed_hex {
+ use serde::{self, Deserialize, Deserializer};
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let s = String::deserialize(deserializer)?;
+ if let Some(s) = s.strip_prefix("0x") {
+ hex::decode(s).map_err(serde::de::Error::custom)
+ } else {
+ Err(serde::de::Error::custom("missing 0x prefix"))
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct UserWasm(Vec);
+
+/// UserWasm is a wrapper around Vec
+///
+/// It is useful for decompressing a brotli-compressed wasm module.
+///
+/// Note: The wrapped Vec is already Base64 decoded before
+/// from(Vec) is called by serde.
+impl UserWasm {
+ /// as_vec returns the decompressed wasm module as a Vec
+ pub fn as_vec(&self) -> Vec {
+ self.0.clone()
+ }
+}
+
+impl AsRef<[u8]> for UserWasm {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+/// The Vec is compressed using brotli, and must be decompressed before use.
+impl From> for UserWasm {
+ fn from(data: Vec) -> Self {
+ let decompressed = brotli::decompress(&data, brotli::Dictionary::Empty).unwrap();
+ Self(decompressed)
+ }
+}
+
+#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct BatchInfo {
+ pub number: u64,
+ #[serde(with = "As::")]
+ pub data_b64: Vec,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct StartState {
+ #[serde(with = "prefixed_hex")]
+ pub block_hash: Vec,
+ #[serde(with = "prefixed_hex")]
+ pub send_root: Vec,
+ pub batch: u64,
+ pub pos_in_batch: u64,
+}
+
+/// FileData is the deserialized form of the input JSON file.
+///
+/// The go JSON library in json.go uses some custom serialization and
+/// compression logic that needs to be reversed when deserializing the
+/// JSON in rust.
+///
+/// Note: It is important to change this file whenever the go JSON
+/// serialization changes.
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct FileData {
+ pub id: u64,
+ pub has_delayed_msg: bool,
+ pub delayed_msg_nr: u64,
+ #[serde(with = "As::>>")]
+ pub preimages_b64: HashMap>>,
+ pub batch_info: Vec,
+ #[serde(with = "As::")]
+ pub delayed_msg_b64: Vec,
+ pub start_state: StartState,
+ #[serde(with = "As::>>")]
+ pub user_wasms: HashMap>,
+}
+
+impl FileData {
+ pub fn from_reader(mut reader: R) -> io::Result {
+ let data = serde_json::from_reader(&mut reader)?;
+ Ok(data)
+ }
+}
diff --git a/arbitrator/bench/src/prepare.rs b/arbitrator/prover/src/prepare.rs
similarity index 85%
rename from arbitrator/bench/src/prepare.rs
rename to arbitrator/prover/src/prepare.rs
index 741a7350ac..a485267f39 100644
--- a/arbitrator/bench/src/prepare.rs
+++ b/arbitrator/prover/src/prepare.rs
@@ -1,13 +1,13 @@
use arbutil::{Bytes32, PreimageType};
-use prover::machine::{argument_data_to_inbox, GlobalState, Machine};
-use prover::utils::CBytes;
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::sync::Arc;
+use crate::machine::{argument_data_to_inbox, GlobalState, Machine};
use crate::parse_input::*;
+use crate::utils::CBytes;
pub fn prepare_machine(preimages: PathBuf, machines: PathBuf) -> eyre::Result {
let file = File::open(preimages)?;
@@ -40,6 +40,15 @@ pub fn prepare_machine(preimages: PathBuf, machines: PathBuf) -> eyre::Result u64 {
- gas.saturating_mul(self.ink_price.into())
+ pub fn gas_to_ink(&self, gas: Gas) -> Ink {
+ Ink(gas.0.saturating_mul(self.ink_price.into()))
}
- pub fn ink_to_gas(&self, ink: u64) -> u64 {
- ink / self.ink_price as u64 // never 0
+ pub fn ink_to_gas(&self, ink: Ink) -> Gas {
+ Gas(ink.0 / self.ink_price as u64) // ink_price is never 0
}
}
@@ -180,17 +181,19 @@ impl CompileConfig {
}
#[cfg(feature = "native")]
- pub fn store(&self) -> Store {
- let mut compiler: Box = match self.debug.cranelift {
+ pub fn engine(&self, target: Target) -> Engine {
+ use wasmer::sys::EngineBuilder;
+
+ let mut wasmer_config: Box = match self.debug.cranelift {
true => {
- let mut compiler = Cranelift::new();
- compiler.opt_level(CraneliftOptLevel::Speed);
- Box::new(compiler)
+ let mut wasmer_config = Cranelift::new();
+ wasmer_config.opt_level(CraneliftOptLevel::Speed);
+ Box::new(wasmer_config)
}
false => Box::new(Singlepass::new()),
};
- compiler.canonicalize_nans(true);
- compiler.enable_verifier();
+ wasmer_config.canonicalize_nans(true);
+ wasmer_config.enable_verifier();
let start = MiddlewareWrapper::new(StartMover::new(self.debug.debug_info));
let meter = MiddlewareWrapper::new(Meter::new(&self.pricing));
@@ -200,22 +203,24 @@ impl CompileConfig {
// add the instrumentation in the order of application
// note: this must be consistent with the prover
- compiler.push_middleware(Arc::new(start));
- compiler.push_middleware(Arc::new(meter));
- compiler.push_middleware(Arc::new(dygas));
- compiler.push_middleware(Arc::new(depth));
- compiler.push_middleware(Arc::new(bound));
+ wasmer_config.push_middleware(Arc::new(start));
+ wasmer_config.push_middleware(Arc::new(meter));
+ wasmer_config.push_middleware(Arc::new(dygas));
+ wasmer_config.push_middleware(Arc::new(depth));
+ wasmer_config.push_middleware(Arc::new(bound));
if self.debug.count_ops {
let counter = Counter::new();
- compiler.push_middleware(Arc::new(MiddlewareWrapper::new(counter)));
+ wasmer_config.push_middleware(Arc::new(MiddlewareWrapper::new(counter)));
}
- Store::new(compiler)
+ EngineBuilder::new(wasmer_config)
+ .set_target(Some(target))
+ .into()
}
#[cfg(feature = "native")]
- pub fn engine(&self) -> Engine {
- self.store().engine().clone()
+ pub fn store(&self, target: Target) -> Store {
+ Store::new(self.engine(target))
}
}
diff --git a/arbitrator/prover/src/programs/memory.rs b/arbitrator/prover/src/programs/memory.rs
index 7253b59dc4..82c4d4469d 100644
--- a/arbitrator/prover/src/programs/memory.rs
+++ b/arbitrator/prover/src/programs/memory.rs
@@ -1,6 +1,8 @@
// Copyright 2023, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE
+use arbutil::evm::api::Gas;
+
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MemoryModel {
@@ -28,20 +30,20 @@ impl MemoryModel {
}
/// Determines the gas cost of allocating `new` pages given `open` are active and `ever` have ever been.
- pub fn gas_cost(&self, new: u16, open: u16, ever: u16) -> u64 {
+ pub fn gas_cost(&self, new: u16, open: u16, ever: u16) -> Gas {
let new_open = open.saturating_add(new);
let new_ever = ever.max(new_open);
// free until expansion beyond the first few
if new_ever <= self.free_pages {
- return 0;
+ return Gas(0);
}
let credit = |pages: u16| pages.saturating_sub(self.free_pages);
let adding = credit(new_open).saturating_sub(credit(open)) as u64;
let linear = adding.saturating_mul(self.page_gas.into());
let expand = Self::exp(new_ever) - Self::exp(ever);
- linear.saturating_add(expand)
+ Gas(linear.saturating_add(expand))
}
fn exp(pages: u16) -> u64 {
@@ -81,14 +83,14 @@ fn test_model() {
let model = MemoryModel::new(2, 1000);
for jump in 1..=128 {
- let mut total = 0;
+ let mut total = Gas(0);
let mut pages = 0;
while pages < 128 {
let jump = jump.min(128 - pages);
total += model.gas_cost(jump, pages, pages);
pages += jump;
}
- assert_eq!(total, 31999998);
+ assert_eq!(total, Gas(31999998));
}
for jump in 1..=128 {
@@ -98,7 +100,7 @@ fn test_model() {
let mut adds = 0;
while ever < 128 {
let jump = jump.min(128 - open);
- total += model.gas_cost(jump, open, ever);
+ total += model.gas_cost(jump, open, ever).0;
open += jump;
ever = ever.max(open);
@@ -114,12 +116,12 @@ fn test_model() {
}
// check saturation
- assert_eq!(u64::MAX, model.gas_cost(129, 0, 0));
- assert_eq!(u64::MAX, model.gas_cost(u16::MAX, 0, 0));
+ assert_eq!(Gas(u64::MAX), model.gas_cost(129, 0, 0));
+ assert_eq!(Gas(u64::MAX), model.gas_cost(u16::MAX, 0, 0));
// check free pages
let model = MemoryModel::new(128, 1000);
- assert_eq!(0, model.gas_cost(128, 0, 0));
- assert_eq!(0, model.gas_cost(128, 0, 128));
- assert_eq!(u64::MAX, model.gas_cost(129, 0, 0));
+ assert_eq!(Gas(0), model.gas_cost(128, 0, 0));
+ assert_eq!(Gas(0), model.gas_cost(128, 0, 128));
+ assert_eq!(Gas(u64::MAX), model.gas_cost(129, 0, 0));
}
diff --git a/arbitrator/prover/src/programs/meter.rs b/arbitrator/prover/src/programs/meter.rs
index ab069fd911..0d7b3151d7 100644
--- a/arbitrator/prover/src/programs/meter.rs
+++ b/arbitrator/prover/src/programs/meter.rs
@@ -9,7 +9,14 @@ use crate::{
value::FunctionType,
Machine,
};
-use arbutil::{evm, operator::OperatorInfo, Bytes32};
+use arbutil::{
+ evm::{
+ self,
+ api::{Gas, Ink},
+ },
+ operator::OperatorInfo,
+ Bytes32,
+};
use derivative::Derivative;
use eyre::Result;
use fnv::FnvHashMap as HashMap;
@@ -188,15 +195,15 @@ impl<'a, F: OpcodePricer> FuncMiddleware<'a> for FuncMeter<'a, F> {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MachineMeter {
- Ready(u64),
+ Ready(Ink),
Exhausted,
}
impl MachineMeter {
- pub fn ink(self) -> u64 {
+ pub fn ink(self) -> Ink {
match self {
Self::Ready(ink) => ink,
- Self::Exhausted => 0,
+ Self::Exhausted => Ink(0),
}
}
@@ -210,8 +217,8 @@ impl MachineMeter {
/// We don't implement `From` since it's unclear what 0 would map to
#[allow(clippy::from_over_into)]
-impl Into for MachineMeter {
- fn into(self) -> u64 {
+impl Into for MachineMeter {
+ fn into(self) -> Ink {
self.ink()
}
}
@@ -219,7 +226,7 @@ impl Into for MachineMeter {
impl Display for MachineMeter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Self::Ready(ink) => write!(f, "{ink} ink"),
+ Self::Ready(ink) => write!(f, "{} ink", ink.0),
Self::Exhausted => write!(f, "exhausted"),
}
}
@@ -241,7 +248,7 @@ pub trait MeteredMachine {
fn ink_left(&self) -> MachineMeter;
fn set_meter(&mut self, meter: MachineMeter);
- fn set_ink(&mut self, ink: u64) {
+ fn set_ink(&mut self, ink: Ink) {
self.set_meter(MachineMeter::Ready(ink));
}
@@ -250,14 +257,14 @@ pub trait MeteredMachine {
Err(OutOfInkError)
}
- fn ink_ready(&mut self) -> Result {
+ fn ink_ready(&mut self) -> Result {
let MachineMeter::Ready(ink_left) = self.ink_left() else {
return self.out_of_ink();
};
Ok(ink_left)
}
- fn buy_ink(&mut self, ink: u64) -> Result<(), OutOfInkError> {
+ fn buy_ink(&mut self, ink: Ink) -> Result<(), OutOfInkError> {
let ink_left = self.ink_ready()?;
if ink_left < ink {
return self.out_of_ink();
@@ -267,7 +274,7 @@ pub trait MeteredMachine {
}
/// Checks if the user has enough ink, but doesn't burn any
- fn require_ink(&mut self, ink: u64) -> Result<(), OutOfInkError> {
+ fn require_ink(&mut self, ink: Ink) -> Result<(), OutOfInkError> {
let ink_left = self.ink_ready()?;
if ink_left < ink {
return self.out_of_ink();
@@ -277,18 +284,18 @@ pub trait MeteredMachine {
/// Pays for a write into the client.
fn pay_for_write(&mut self, bytes: u32) -> Result<(), OutOfInkError> {
- self.buy_ink(sat_add_mul(5040, 30, bytes.saturating_sub(32)))
+ self.buy_ink(Ink(sat_add_mul(5040, 30, bytes.saturating_sub(32))))
}
/// Pays for a read into the host.
fn pay_for_read(&mut self, bytes: u32) -> Result<(), OutOfInkError> {
- self.buy_ink(sat_add_mul(16381, 55, bytes.saturating_sub(32)))
+ self.buy_ink(Ink(sat_add_mul(16381, 55, bytes.saturating_sub(32))))
}
/// Pays for both I/O and keccak.
fn pay_for_keccak(&mut self, bytes: u32) -> Result<(), OutOfInkError> {
let words = evm::evm_words(bytes).saturating_sub(2);
- self.buy_ink(sat_add_mul(121800, 21000, words))
+ self.buy_ink(Ink(sat_add_mul(121800, 21000, words)))
}
/// Pays for copying bytes from geth.
@@ -305,14 +312,14 @@ pub trait MeteredMachine {
false => break,
}
}
- self.buy_ink(3000 + exp * 17500)
+ self.buy_ink(Ink(3000 + exp * 17500))
}
}
pub trait GasMeteredMachine: MeteredMachine {
fn pricing(&self) -> PricingParams;
- fn gas_left(&self) -> Result {
+ fn gas_left(&self) -> Result {
let pricing = self.pricing();
match self.ink_left() {
MachineMeter::Ready(ink) => Ok(pricing.ink_to_gas(ink)),
@@ -320,13 +327,13 @@ pub trait GasMeteredMachine: MeteredMachine {
}
}
- fn buy_gas(&mut self, gas: u64) -> Result<(), OutOfInkError> {
+ fn buy_gas(&mut self, gas: Gas) -> Result<(), OutOfInkError> {
let pricing = self.pricing();
self.buy_ink(pricing.gas_to_ink(gas))
}
/// Checks if the user has enough gas, but doesn't burn any
- fn require_gas(&mut self, gas: u64) -> Result<(), OutOfInkError> {
+ fn require_gas(&mut self, gas: Gas) -> Result<(), OutOfInkError> {
let pricing = self.pricing();
self.require_ink(pricing.gas_to_ink(gas))
}
@@ -350,7 +357,7 @@ impl MeteredMachine for Machine {
}};
}
- let ink = || convert!(self.get_global(STYLUS_INK_LEFT));
+ let ink = || Ink(convert!(self.get_global(STYLUS_INK_LEFT)));
let status: u32 = convert!(self.get_global(STYLUS_INK_STATUS));
match status {
@@ -362,7 +369,7 @@ impl MeteredMachine for Machine {
fn set_meter(&mut self, meter: MachineMeter) {
let ink = meter.ink();
let status = meter.status();
- self.set_global(STYLUS_INK_LEFT, ink.into()).unwrap();
+ self.set_global(STYLUS_INK_LEFT, ink.0.into()).unwrap();
self.set_global(STYLUS_INK_STATUS, status.into()).unwrap();
}
}
diff --git a/arbitrator/prover/src/programs/mod.rs b/arbitrator/prover/src/programs/mod.rs
index a5df2e31a8..a35308e7ff 100644
--- a/arbitrator/prover/src/programs/mod.rs
+++ b/arbitrator/prover/src/programs/mod.rs
@@ -8,7 +8,7 @@ use crate::{
programs::config::CompileConfig,
value::{FunctionType as ArbFunctionType, Value},
};
-use arbutil::{math::SaturatingSum, Bytes32, Color};
+use arbutil::{evm::ARBOS_VERSION_STYLUS_CHARGING_FIXES, math::SaturatingSum, Bytes32, Color};
use eyre::{bail, eyre, Report, Result, WrapErr};
use fnv::FnvHashMap as HashMap;
use std::fmt::Debug;
@@ -418,58 +418,64 @@ impl Module {
pub fn activate(
wasm: &[u8],
codehash: &Bytes32,
- version: u16,
+ stylus_version: u16,
+ arbos_version_for_gas: u64, // must only be used for activation gas
page_limit: u16,
debug: bool,
gas: &mut u64,
) -> Result<(Self, StylusData)> {
- // converts a number of microseconds to gas
- // TODO: collapse to a single value after finalizing factors
- let us_to_gas = |us: u64| {
- let fudge = 2;
- let sync_rate = 1_000_000 / 2;
- let speed = 7_000_000;
- us.saturating_mul(fudge * speed) / sync_rate
- };
-
- macro_rules! pay {
- ($us:expr) => {
- let amount = us_to_gas($us);
- if *gas < amount {
- *gas = 0;
- bail!("out of gas");
- }
- *gas -= amount;
+ let compile = CompileConfig::version(stylus_version, debug);
+ let (bin, stylus_data) =
+ WasmBinary::parse_user(wasm, arbos_version_for_gas, page_limit, &compile, codehash)
+ .wrap_err("failed to parse wasm")?;
+
+ if arbos_version_for_gas > 0 {
+ // converts a number of microseconds to gas
+ // TODO: collapse to a single value after finalizing factors
+ let us_to_gas = |us: u64| {
+ let fudge = 2;
+ let sync_rate = 1_000_000 / 2;
+ let speed = 7_000_000;
+ us.saturating_mul(fudge * speed) / sync_rate
};
- }
-
- // pay for wasm
- let wasm_len = wasm.len() as u64;
- pay!(wasm_len.saturating_mul(31_733 / 100_000));
-
- let compile = CompileConfig::version(version, debug);
- let (bin, stylus_data) = WasmBinary::parse_user(wasm, page_limit, &compile, codehash)
- .wrap_err("failed to parse wasm")?;
- // pay for funcs
- let funcs = bin.functions.len() as u64;
- pay!(funcs.saturating_mul(17_263) / 100_000);
-
- // pay for data
- let data = bin.datas.iter().map(|x| x.data.len()).saturating_sum() as u64;
- pay!(data.saturating_mul(17_376) / 100_000);
-
- // pay for elements
- let elems = bin.elements.iter().map(|x| x.range.len()).saturating_sum() as u64;
- pay!(elems.saturating_mul(17_376) / 100_000);
-
- // pay for memory
- let mem = bin.memories.first().map(|x| x.initial).unwrap_or_default();
- pay!(mem.saturating_mul(2217));
-
- // pay for code
- let code = bin.codes.iter().map(|x| x.expr.len()).saturating_sum() as u64;
- pay!(code.saturating_mul(535) / 1_000);
+ macro_rules! pay {
+ ($us:expr) => {
+ let amount = us_to_gas($us);
+ if *gas < amount {
+ *gas = 0;
+ bail!("out of gas");
+ }
+ *gas -= amount;
+ };
+ }
+
+ // pay for wasm
+ if arbos_version_for_gas >= ARBOS_VERSION_STYLUS_CHARGING_FIXES {
+ let wasm_len = wasm.len() as u64;
+ pay!(wasm_len.saturating_mul(31_733) / 100_000);
+ }
+
+ // pay for funcs
+ let funcs = bin.functions.len() as u64;
+ pay!(funcs.saturating_mul(17_263) / 100_000);
+
+ // pay for data
+ let data = bin.datas.iter().map(|x| x.data.len()).saturating_sum() as u64;
+ pay!(data.saturating_mul(17_376) / 100_000);
+
+ // pay for elements
+ let elems = bin.elements.iter().map(|x| x.range.len()).saturating_sum() as u64;
+ pay!(elems.saturating_mul(17_376) / 100_000);
+
+ // pay for memory
+ let mem = bin.memories.first().map(|x| x.initial).unwrap_or_default();
+ pay!(mem.saturating_mul(2217));
+
+ // pay for code
+ let code = bin.codes.iter().map(|x| x.expr.len()).saturating_sum() as u64;
+ pay!(code.saturating_mul(535) / 1_000);
+ }
let module = Self::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data))
.wrap_err("failed to build user module")?;
diff --git a/arbitrator/prover/src/test.rs b/arbitrator/prover/src/test.rs
index 97170441ff..4fd739342e 100644
--- a/arbitrator/prover/src/test.rs
+++ b/arbitrator/prover/src/test.rs
@@ -1,8 +1,6 @@
// Copyright 2022-2024, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
-#![cfg(test)]
-
use crate::binary;
use brotli::Dictionary;
use eyre::Result;
@@ -64,7 +62,7 @@ pub fn test_compress() -> Result<()> {
let deflate = brotli::compress(data, 11, 22, dict).unwrap();
let inflate = brotli::decompress(&deflate, dict).unwrap();
assert_eq!(hex::encode(inflate), hex::encode(data));
- assert!(&deflate != &last);
+ assert!(deflate != last);
last = deflate;
}
Ok(())
diff --git a/arbitrator/stylus/Cargo.toml b/arbitrator/stylus/Cargo.toml
index 4717bd631a..ea1d878ea0 100644
--- a/arbitrator/stylus/Cargo.toml
+++ b/arbitrator/stylus/Cargo.toml
@@ -21,11 +21,11 @@ thiserror = "1.0.33"
bincode = "1.3.3"
lazy_static.workspace = true
libc = "0.2.108"
-lru.workspace = true
eyre = "0.6.5"
rand = "0.8.5"
fnv = "1.0.7"
hex = "0.4.3"
+clru = "0.6.2"
[dev-dependencies]
num-bigint = "0.4.4"
diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs
index 06739f2219..9b788a45db 100644
--- a/arbitrator/stylus/src/cache.rs
+++ b/arbitrator/stylus/src/cache.rs
@@ -2,16 +2,19 @@
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE
use arbutil::Bytes32;
+use clru::{CLruCache, CLruCacheConfig, WeightScale};
use eyre::Result;
use lazy_static::lazy_static;
-use lru::LruCache;
use parking_lot::Mutex;
use prover::programs::config::CompileConfig;
+use std::hash::RandomState;
use std::{collections::HashMap, num::NonZeroUsize};
use wasmer::{Engine, Module, Store};
+use crate::target_cache::target_native;
+
lazy_static! {
- static ref INIT_CACHE: Mutex = Mutex::new(InitCache::new(256));
+ static ref INIT_CACHE: Mutex = Mutex::new(InitCache::new(256 * 1024 * 1024));
}
macro_rules! cache {
@@ -20,9 +23,24 @@ macro_rules! cache {
};
}
+pub struct LruCounters {
+ pub hits: u32,
+ pub misses: u32,
+ pub does_not_fit: u32,
+}
+
+pub struct LongTermCounters {
+ pub hits: u32,
+ pub misses: u32,
+}
+
pub struct InitCache {
long_term: HashMap,
- lru: LruCache,
+ long_term_size_bytes: usize,
+ long_term_counters: LongTermCounters,
+
+ lru: CLruCache,
+ lru_counters: LruCounters,
}
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
@@ -46,11 +64,16 @@ impl CacheKey {
struct CacheItem {
module: Module,
engine: Engine,
+ entry_size_estimate_bytes: usize,
}
impl CacheItem {
- fn new(module: Module, engine: Engine) -> Self {
- Self { module, engine }
+ fn new(module: Module, engine: Engine, entry_size_estimate_bytes: usize) -> Self {
+ Self {
+ module,
+ engine,
+ entry_size_estimate_bytes,
+ }
}
fn data(&self) -> (Module, Store) {
@@ -58,39 +81,121 @@ impl CacheItem {
}
}
+struct CustomWeightScale;
+impl WeightScale for CustomWeightScale {
+ fn weight(&self, _key: &CacheKey, val: &CacheItem) -> usize {
+ // clru defines that each entry consumes (weight + 1) of the cache capacity.
+ // We subtract 1 since we only want to use the weight as the size of the entry.
+ val.entry_size_estimate_bytes.saturating_sub(1)
+ }
+}
+
+#[repr(C)]
+pub struct LruCacheMetrics {
+ pub size_bytes: u64,
+ pub count: u32,
+ pub hits: u32,
+ pub misses: u32,
+ pub does_not_fit: u32,
+}
+
+#[repr(C)]
+pub struct LongTermCacheMetrics {
+ pub size_bytes: u64,
+ pub count: u32,
+ pub hits: u32,
+ pub misses: u32,
+}
+
+#[repr(C)]
+pub struct CacheMetrics {
+ pub lru: LruCacheMetrics,
+ pub long_term: LongTermCacheMetrics,
+}
+
+pub fn deserialize_module(
+ module: &[u8],
+ version: u16,
+ debug: bool,
+) -> Result<(Module, Engine, usize)> {
+ let engine = CompileConfig::version(version, debug).engine(target_native());
+ let module = unsafe { Module::deserialize_unchecked(&engine, module)? };
+
+ let asm_size_estimate_bytes = module.serialize()?.len();
+ // add 128 bytes for the cache item overhead
+ let entry_size_estimate_bytes = asm_size_estimate_bytes + 128;
+
+ Ok((module, engine, entry_size_estimate_bytes))
+}
+
impl InitCache {
// current implementation only has one tag that stores to the long_term
// future implementations might have more, but 0 is a reserved tag
// that will never modify long_term state
const ARBOS_TAG: u32 = 1;
- fn new(size: usize) -> Self {
+ const DOES_NOT_FIT_MSG: &'static str = "Failed to insert into LRU cache, item too large";
+
+ fn new(size_bytes: usize) -> Self {
Self {
long_term: HashMap::new(),
- lru: LruCache::new(NonZeroUsize::new(size).unwrap()),
+ long_term_size_bytes: 0,
+ long_term_counters: LongTermCounters { hits: 0, misses: 0 },
+
+ lru: CLruCache::with_config(
+ CLruCacheConfig::new(NonZeroUsize::new(size_bytes).unwrap())
+ .with_scale(CustomWeightScale),
+ ),
+ lru_counters: LruCounters {
+ hits: 0,
+ misses: 0,
+ does_not_fit: 0,
+ },
}
}
- pub fn set_lru_size(size: u32) {
+ pub fn set_lru_capacity(capacity_bytes: u64) {
cache!()
.lru
- .resize(NonZeroUsize::new(size.try_into().unwrap()).unwrap())
+ .resize(NonZeroUsize::new(capacity_bytes.try_into().unwrap()).unwrap())
}
/// Retrieves a cached value, updating items as necessary.
- pub fn get(module_hash: Bytes32, version: u16, debug: bool) -> Option<(Module, Store)> {
- let mut cache = cache!();
+ /// If long_term_tag is 1 and the item is only in LRU will insert to long term cache.
+ pub fn get(
+ module_hash: Bytes32,
+ version: u16,
+ long_term_tag: u32,
+ debug: bool,
+ ) -> Option<(Module, Store)> {
let key = CacheKey::new(module_hash, version, debug);
+ let mut cache = cache!();
// See if the item is in the long term cache
if let Some(item) = cache.long_term.get(&key) {
- return Some(item.data());
+ let data = item.data();
+ cache.long_term_counters.hits += 1;
+ return Some(data);
+ }
+ if long_term_tag == Self::ARBOS_TAG {
+ // only count misses only when we can expect to find the item in long term cache
+ cache.long_term_counters.misses += 1;
}
// See if the item is in the LRU cache, promoting if so
- if let Some(item) = cache.lru.get(&key) {
- return Some(item.data());
+ if let Some(item) = cache.lru.peek(&key).cloned() {
+ cache.lru_counters.hits += 1;
+ if long_term_tag == Self::ARBOS_TAG {
+ cache.long_term_size_bytes += item.entry_size_estimate_bytes;
+ cache.long_term.insert(key, item.clone());
+ } else {
+ // only calls get to move the key to the head of the LRU list
+ cache.lru.get(&key);
+ }
+ return Some((item.module, Store::new(item.engine)));
}
+ cache.lru_counters.misses += 1;
+
None
}
@@ -113,23 +218,29 @@ impl InitCache {
if let Some(item) = cache.lru.peek(&key).cloned() {
if long_term_tag == Self::ARBOS_TAG {
cache.long_term.insert(key, item.clone());
+ cache.long_term_size_bytes += item.entry_size_estimate_bytes;
} else {
- cache.lru.promote(&key)
+ // only calls get to move the key to the head of the LRU list
+ cache.lru.get(&key);
}
return Ok(item.data());
}
drop(cache);
- let engine = CompileConfig::version(version, debug).engine();
- let module = unsafe { Module::deserialize_unchecked(&engine, module)? };
+ let (module, engine, entry_size_estimate_bytes) =
+ deserialize_module(module, version, debug)?;
- let item = CacheItem::new(module, engine);
+ let item = CacheItem::new(module, engine, entry_size_estimate_bytes);
let data = item.data();
let mut cache = cache!();
if long_term_tag != Self::ARBOS_TAG {
- cache.lru.put(key, item);
+ if cache.lru.put_with_weight(key, item).is_err() {
+ cache.lru_counters.does_not_fit += 1;
+ eprintln!("{}", Self::DOES_NOT_FIT_MSG);
+ };
} else {
cache.long_term.insert(key, item);
+ cache.long_term_size_bytes += entry_size_estimate_bytes;
}
Ok(data)
}
@@ -142,7 +253,10 @@ impl InitCache {
let key = CacheKey::new(module_hash, version, debug);
let mut cache = cache!();
if let Some(item) = cache.long_term.remove(&key) {
- cache.lru.put(key, item);
+ cache.long_term_size_bytes -= item.entry_size_estimate_bytes;
+ if cache.lru.put_with_weight(key, item).is_err() {
+ eprintln!("{}", Self::DOES_NOT_FIT_MSG);
+ }
}
}
@@ -153,7 +267,49 @@ impl InitCache {
let mut cache = cache!();
let cache = &mut *cache;
for (key, item) in cache.long_term.drain() {
- cache.lru.put(key, item); // not all will fit, just a heuristic
+ // not all will fit, just a heuristic
+ if cache.lru.put_with_weight(key, item).is_err() {
+ eprintln!("{}", Self::DOES_NOT_FIT_MSG);
+ }
}
+ cache.long_term_size_bytes = 0;
+ }
+
+ pub fn get_metrics(output: &mut CacheMetrics) {
+ let mut cache = cache!();
+
+ let lru_count = cache.lru.len();
+ // adds 1 to each entry to account that we subtracted 1 in the weight calculation
+ output.lru.size_bytes = (cache.lru.weight() + lru_count).try_into().unwrap();
+ output.lru.count = lru_count.try_into().unwrap();
+ output.lru.hits = cache.lru_counters.hits;
+ output.lru.misses = cache.lru_counters.misses;
+ output.lru.does_not_fit = cache.lru_counters.does_not_fit;
+
+ output.long_term.size_bytes = cache.long_term_size_bytes.try_into().unwrap();
+ output.long_term.count = cache.long_term.len().try_into().unwrap();
+ output.long_term.hits = cache.long_term_counters.hits;
+ output.long_term.misses = cache.long_term_counters.misses;
+
+ // Empty counters.
+ // go side, which is the only consumer of this function besides tests,
+ // will read those counters and increment its own prometheus counters with them.
+ cache.lru_counters = LruCounters {
+ hits: 0,
+ misses: 0,
+ does_not_fit: 0,
+ };
+ cache.long_term_counters = LongTermCounters { hits: 0, misses: 0 };
+ }
+
+ // only used for testing
+ pub fn clear_lru_cache() {
+ let mut cache = cache!();
+ cache.lru.clear();
+ cache.lru_counters = LruCounters {
+ hits: 0,
+ misses: 0,
+ does_not_fit: 0,
+ };
}
}
diff --git a/arbitrator/stylus/src/env.rs b/arbitrator/stylus/src/env.rs
index 69d542070d..a153fb5bf1 100644
--- a/arbitrator/stylus/src/env.rs
+++ b/arbitrator/stylus/src/env.rs
@@ -3,7 +3,7 @@
use arbutil::{
evm::{
- api::{DataReader, EvmApi},
+ api::{DataReader, EvmApi, Ink},
EvmData,
},
pricing,
@@ -74,7 +74,7 @@ impl> WasmEnv {
pub fn start<'a>(
env: &'a mut WasmEnvMut<'_, D, E>,
- ink: u64,
+ ink: Ink,
) -> Result, Escape> {
let mut info = Self::program(env)?;
info.buy_ink(pricing::HOSTIO_INK + ink)?;
@@ -88,7 +88,7 @@ impl> WasmEnv {
env,
memory,
store,
- start_ink: 0,
+ start_ink: Ink(0),
};
if info.env.evm_data.tracing {
info.start_ink = info.ink_ready()?;
@@ -114,16 +114,16 @@ pub struct MeterData {
}
impl MeterData {
- pub fn ink(&self) -> u64 {
- unsafe { self.ink_left.as_ref().val.u64 }
+ pub fn ink(&self) -> Ink {
+ Ink(unsafe { self.ink_left.as_ref().val.u64 })
}
pub fn status(&self) -> u32 {
unsafe { self.ink_status.as_ref().val.u32 }
}
- pub fn set_ink(&mut self, ink: u64) {
- unsafe { self.ink_left.as_mut().val = RawValue { u64: ink } }
+ pub fn set_ink(&mut self, ink: Ink) {
+ unsafe { self.ink_left.as_mut().val = RawValue { u64: ink.0 } }
}
pub fn set_status(&mut self, status: u32) {
@@ -140,7 +140,7 @@ pub struct HostioInfo<'a, D: DataReader, E: EvmApi> {
pub env: &'a mut WasmEnv,
pub memory: Memory,
pub store: StoreMut<'a>,
- pub start_ink: u64,
+ pub start_ink: Ink,
}
impl<'a, D: DataReader, E: EvmApi> HostioInfo<'a, D, E> {
diff --git a/arbitrator/stylus/src/evm_api.rs b/arbitrator/stylus/src/evm_api.rs
index d267372827..0dd27e3f8c 100644
--- a/arbitrator/stylus/src/evm_api.rs
+++ b/arbitrator/stylus/src/evm_api.rs
@@ -3,7 +3,7 @@
use crate::{GoSliceData, RustSlice};
use arbutil::evm::{
- api::{EvmApiMethod, EVM_API_METHOD_REQ_OFFSET},
+ api::{EvmApiMethod, Gas, EVM_API_METHOD_REQ_OFFSET},
req::RequestHandler,
};
@@ -31,7 +31,7 @@ impl RequestHandler for NativeRequestHandler {
&mut self,
req_type: EvmApiMethod,
req_data: impl AsRef<[u8]>,
- ) -> (Vec, GoSliceData, u64) {
+ ) -> (Vec, GoSliceData, Gas) {
let mut result = GoSliceData::null();
let mut raw_data = GoSliceData::null();
let mut cost = 0;
@@ -45,6 +45,6 @@ impl RequestHandler for NativeRequestHandler {
ptr!(raw_data),
)
};
- (result.slice().to_vec(), raw_data, cost)
+ (result.slice().to_vec(), raw_data, Gas(cost))
}
}
diff --git a/arbitrator/stylus/src/host.rs b/arbitrator/stylus/src/host.rs
index 1afc1b4e51..c72cafc316 100644
--- a/arbitrator/stylus/src/host.rs
+++ b/arbitrator/stylus/src/host.rs
@@ -6,7 +6,7 @@
use crate::env::{Escape, HostioInfo, MaybeEscape, WasmEnv, WasmEnvMut};
use arbutil::{
evm::{
- api::{DataReader, EvmApi},
+ api::{DataReader, EvmApi, Gas, Ink},
EvmData,
},
Color,
@@ -82,7 +82,7 @@ where
println!("{} {text}", "Stylus says:".yellow());
}
- fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: u64) {
+ fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: Ink) {
let start_ink = self.start_ink;
self.evm_api
.capture_hostio(name, args, outs, start_ink, end_ink);
@@ -168,7 +168,7 @@ pub(crate) fn call_contract>(
) -> Result {
hostio!(
env,
- call_contract(contract, data, data_len, value, gas, ret_len)
+ call_contract(contract, data, data_len, value, Gas(gas), ret_len)
)
}
@@ -182,7 +182,7 @@ pub(crate) fn delegate_call_contract>(
) -> Result {
hostio!(
env,
- delegate_call_contract(contract, data, data_len, gas, ret_len)
+ delegate_call_contract(contract, data, data_len, Gas(gas), ret_len)
)
}
@@ -196,7 +196,7 @@ pub(crate) fn static_call_contract>(
) -> Result {
hostio!(
env,
- static_call_contract(contract, data, data_len, gas, ret_len)
+ static_call_contract(contract, data, data_len, Gas(gas), ret_len)
)
}
@@ -334,13 +334,13 @@ pub(crate) fn contract_address>(
pub(crate) fn evm_gas_left>(
mut env: WasmEnvMut,
) -> Result {
- hostio!(env, evm_gas_left())
+ hostio!(env, evm_gas_left()).map(|g| g.0)
}
pub(crate) fn evm_ink_left>(
mut env: WasmEnvMut,
) -> Result {
- hostio!(env, evm_ink_left())
+ hostio!(env, evm_ink_left()).map(|i| i.0)
}
pub(crate) fn math_div>(
diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs
index 3c53359f8b..e7f10c2400 100644
--- a/arbitrator/stylus/src/lib.rs
+++ b/arbitrator/stylus/src/lib.rs
@@ -3,7 +3,7 @@
use arbutil::{
evm::{
- api::DataReader,
+ api::{DataReader, Gas, Ink},
req::EvmApiRequestor,
user::{UserOutcome, UserOutcomeKind},
EvmData,
@@ -11,13 +11,14 @@ use arbutil::{
format::DebugBytes,
Bytes32,
};
-use cache::InitCache;
+use cache::{deserialize_module, CacheMetrics, InitCache};
use evm_api::NativeRequestHandler;
use eyre::ErrReport;
use native::NativeInstance;
use prover::programs::{prelude::*, StylusData};
use run::RunProgram;
use std::{marker::PhantomData, mem, ptr};
+use target_cache::{target_cache_get, target_cache_set};
pub use brotli;
pub use prover;
@@ -29,6 +30,7 @@ pub mod run;
mod cache;
mod evm_api;
+mod target_cache;
mod util;
#[cfg(test)]
@@ -122,9 +124,9 @@ impl RustBytes {
}
}
-/// Instruments and "activates" a user wasm.
+/// "activates" a user wasm.
///
-/// The `output` is either the serialized asm & module pair or an error string.
+/// The `output` is either the module or an error string.
/// Returns consensus info such as the module hash and footprint on success.
///
/// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer.
@@ -137,10 +139,10 @@ impl RustBytes {
pub unsafe extern "C" fn stylus_activate(
wasm: GoSliceData,
page_limit: u16,
- version: u16,
+ stylus_version: u16,
+ arbos_version_for_gas: u64,
debug: bool,
output: *mut RustBytes,
- asm_len: *mut usize,
codehash: *const Bytes32,
module_hash: *mut Bytes32,
stylus_data: *mut StylusData,
@@ -152,18 +154,105 @@ pub unsafe extern "C" fn stylus_activate(
let codehash = &*codehash;
let gas = &mut *gas;
- let (asm, module, info) =
- match native::activate(wasm, codehash, version, page_limit, debug, gas) {
- Ok(val) => val,
- Err(err) => return output.write_err(err),
- };
- *asm_len = asm.len();
+ let (module, info) = match native::activate(
+ wasm,
+ codehash,
+ stylus_version,
+ arbos_version_for_gas,
+ page_limit,
+ debug,
+ gas,
+ ) {
+ Ok(val) => val,
+ Err(err) => return output.write_err(err),
+ };
+
*module_hash = module.hash();
*stylus_data = info;
- let mut data = asm;
- data.extend(&*module.into_bytes());
- output.write(data);
+ output.write(module.into_bytes());
+ UserOutcomeKind::Success
+}
+
+/// "compiles" a user wasm.
+///
+/// The `output` is either the asm or an error string.
+/// Returns consensus info such as the module hash and footprint on success.
+///
+/// # Safety
+///
+/// `output` must not be null.
+#[no_mangle]
+pub unsafe extern "C" fn stylus_compile(
+ wasm: GoSliceData,
+ version: u16,
+ debug: bool,
+ name: GoSliceData,
+ output: *mut RustBytes,
+) -> UserOutcomeKind {
+ let wasm = wasm.slice();
+ let output = &mut *output;
+ let name = match String::from_utf8(name.slice().to_vec()) {
+ Ok(val) => val,
+ Err(err) => return output.write_err(err.into()),
+ };
+ let target = match target_cache_get(&name) {
+ Ok(val) => val,
+ Err(err) => return output.write_err(err),
+ };
+
+ let asm = match native::compile(wasm, version, debug, target) {
+ Ok(val) => val,
+ Err(err) => return output.write_err(err),
+ };
+
+ output.write(asm);
+ UserOutcomeKind::Success
+}
+
+#[no_mangle]
+/// # Safety
+///
+/// `output` must not be null.
+pub unsafe extern "C" fn wat_to_wasm(wat: GoSliceData, output: *mut RustBytes) -> UserOutcomeKind {
+ let output = &mut *output;
+ let wasm = match wasmer::wat2wasm(wat.slice()) {
+ Ok(val) => val,
+ Err(err) => return output.write_err(err.into()),
+ };
+ output.write(wasm.into_owned());
+ UserOutcomeKind::Success
+}
+
+/// sets target index to a string
+///
+/// String format is: Triple+CpuFeature+CpuFeature..
+///
+/// # Safety
+///
+/// `output` must not be null.
+#[no_mangle]
+pub unsafe extern "C" fn stylus_target_set(
+ name: GoSliceData,
+ description: GoSliceData,
+ output: *mut RustBytes,
+ native: bool,
+) -> UserOutcomeKind {
+ let output = &mut *output;
+ let name = match String::from_utf8(name.slice().to_vec()) {
+ Ok(val) => val,
+ Err(err) => return output.write_err(err.into()),
+ };
+
+ let desc_str = match String::from_utf8(description.slice().to_vec()) {
+ Ok(val) => val,
+ Err(err) => return output.write_err(err.into()),
+ };
+
+ if let Err(err) = target_cache_set(name, desc_str, native) {
+ return output.write_err(err);
+ };
+
UserOutcomeKind::Success
}
@@ -190,7 +279,7 @@ pub unsafe extern "C" fn stylus_call(
let evm_api = EvmApiRequestor::new(req_handler);
let pricing = config.pricing;
let output = &mut *output;
- let ink = pricing.gas_to_ink(*gas);
+ let ink = pricing.gas_to_ink(Gas(*gas));
// Safety: module came from compile_user_wasm and we've paid for memory expansion
let instance = unsafe {
@@ -213,17 +302,17 @@ pub unsafe extern "C" fn stylus_call(
Ok(outcome) => output.write_outcome(outcome),
};
let ink_left = match status {
- UserOutcomeKind::OutOfStack => 0, // take all gas when out of stack
+ UserOutcomeKind::OutOfStack => Ink(0), // take all gas when out of stack
_ => instance.ink_left().into(),
};
- *gas = pricing.ink_to_gas(ink_left);
+ *gas = pricing.ink_to_gas(ink_left).0;
status
}
-/// resize lru
+/// set lru cache capacity
#[no_mangle]
-pub extern "C" fn stylus_cache_lru_resize(size: u32) {
- InitCache::set_lru_size(size);
+pub extern "C" fn stylus_set_cache_lru_capacity(capacity_bytes: u64) {
+ InitCache::set_lru_capacity(capacity_bytes);
}
/// Caches an activated user program.
@@ -274,3 +363,42 @@ pub unsafe extern "C" fn stylus_drop_vec(vec: RustBytes) {
mem::drop(vec.into_vec())
}
}
+
+/// Gets cache metrics.
+///
+/// # Safety
+///
+/// `output` must not be null.
+#[no_mangle]
+pub unsafe extern "C" fn stylus_get_cache_metrics(output: *mut CacheMetrics) {
+ let output = &mut *output;
+ InitCache::get_metrics(output);
+}
+
+/// Clears lru cache.
+/// Only used for testing purposes.
+#[no_mangle]
+pub extern "C" fn stylus_clear_lru_cache() {
+ InitCache::clear_lru_cache()
+}
+
+/// Clears long term cache (for arbos_tag = 1)
+/// Only used for testing purposes.
+#[no_mangle]
+pub extern "C" fn stylus_clear_long_term_cache() {
+ InitCache::clear_long_term(1);
+}
+
+/// Gets entry size in bytes.
+/// Only used for testing purposes.
+#[no_mangle]
+pub extern "C" fn stylus_get_entry_size_estimate_bytes(
+ module: GoSliceData,
+ version: u16,
+ debug: bool,
+) -> u64 {
+ match deserialize_module(module.slice(), version, debug) {
+ Err(error) => panic!("tried to get invalid asm!: {error}"),
+ Ok((_, _, entry_size_estimate_bytes)) => entry_size_estimate_bytes.try_into().unwrap(),
+ }
+}
diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs
index a7b996edf0..0fbdb342f3 100644
--- a/arbitrator/stylus/src/native.rs
+++ b/arbitrator/stylus/src/native.rs
@@ -4,11 +4,11 @@
use crate::{
cache::InitCache,
env::{MeterData, WasmEnv},
- host, util,
+ host,
};
use arbutil::{
evm::{
- api::{DataReader, EvmApi},
+ api::{DataReader, EvmApi, Ink},
EvmData,
},
operator::OperatorCode,
@@ -33,11 +33,13 @@ use std::{
ops::{Deref, DerefMut},
};
use wasmer::{
- imports, AsStoreMut, Function, FunctionEnv, Instance, Memory, Module, Pages, Store,
+ imports, AsStoreMut, Function, FunctionEnv, Instance, Memory, Module, Pages, Store, Target,
TypedFunction, Value, WasmTypeList,
};
use wasmer_vm::VMExtern;
+use crate::target_cache::target_native;
+
#[derive(Debug)]
pub struct NativeInstance> {
pub instance: Instance,
@@ -98,7 +100,7 @@ impl> NativeInstance {
evm_data: EvmData,
) -> Result {
let env = WasmEnv::new(compile, None, evm, evm_data);
- let store = env.compile.store();
+ let store = env.compile.store(target_native());
let module = unsafe { Module::deserialize_unchecked(&store, module)? };
Self::from_module(module, store, env)
}
@@ -119,13 +121,12 @@ impl> NativeInstance {
let compile = CompileConfig::version(version, debug);
let env = WasmEnv::new(compile, None, evm, evm_data);
let module_hash = env.evm_data.module_hash;
-
- if let Some((module, store)) = InitCache::get(module_hash, version, debug) {
- return Self::from_module(module, store, env);
- }
if !env.evm_data.cached {
long_term_tag = 0;
}
+ if let Some((module, store)) = InitCache::get(module_hash, version, long_term_tag, debug) {
+ return Self::from_module(module, store, env);
+ }
let (module, store) =
InitCache::insert(module_hash, module, version, long_term_tag, debug)?;
Self::from_module(module, store, env)
@@ -137,9 +138,10 @@ impl> NativeInstance {
evm_data: EvmData,
compile: &CompileConfig,
config: StylusConfig,
+ target: Target,
) -> Result {
let env = WasmEnv::new(compile.clone(), Some(config), evm_api, evm_data);
- let store = env.compile.store();
+ let store = env.compile.store(target);
let wat_or_wasm = std::fs::read(path)?;
let module = Module::new(&store, wat_or_wasm)?;
Self::from_module(module, store, env)
@@ -268,7 +270,7 @@ impl> NativeInstance {
global.set(store, value.into()).map_err(ErrReport::msg)
}
- pub fn call_func(&mut self, func: TypedFunction<(), R>, ink: u64) -> Result
+ pub fn call_func(&mut self, func: TypedFunction<(), R>, ink: Ink) -> Result
where
R: WasmTypeList,
{
@@ -347,8 +349,8 @@ impl> StartlessMachine for NativeInstance {
}
}
-pub fn module(wasm: &[u8], compile: CompileConfig) -> Result> {
- let mut store = compile.store();
+pub fn module(wasm: &[u8], compile: CompileConfig, target: Target) -> Result> {
+ let mut store = compile.store(target);
let module = Module::new(&store, wasm)?;
macro_rules! stub {
(u8 <- $($types:tt)+) => {
@@ -428,7 +430,6 @@ pub fn module(wasm: &[u8], compile: CompileConfig) -> Result> {
imports.define("console", "tee_f64", stub!(f64 <- |_: f64|));
imports.define("debug", "null_host", stub!(||));
}
- Instance::new(&mut store, &module, &imports)?;
let module = module.serialize()?;
Ok(module.to_vec())
@@ -437,18 +438,26 @@ pub fn module(wasm: &[u8], compile: CompileConfig) -> Result> {
pub fn activate(
wasm: &[u8],
codehash: &Bytes32,
- version: u16,
+ stylus_version: u16,
+ arbos_version_for_gas: u64,
page_limit: u16,
debug: bool,
gas: &mut u64,
-) -> Result<(Vec, ProverModule, StylusData)> {
- let compile = CompileConfig::version(version, debug);
- let (module, stylus_data) =
- ProverModule::activate(wasm, codehash, version, page_limit, debug, gas)?;
+) -> Result<(ProverModule, StylusData)> {
+ let (module, stylus_data) = ProverModule::activate(
+ wasm,
+ codehash,
+ stylus_version,
+ arbos_version_for_gas,
+ page_limit,
+ debug,
+ gas,
+ )?;
+
+ Ok((module, stylus_data))
+}
- let asm = match self::module(wasm, compile) {
- Ok(asm) => asm,
- Err(err) => util::panic_with_wasm(wasm, err),
- };
- Ok((asm, module, stylus_data))
+pub fn compile(wasm: &[u8], version: u16, debug: bool, target: Target) -> Result> {
+ let compile = CompileConfig::version(version, debug);
+ self::module(wasm, compile, target)
}
diff --git a/arbitrator/stylus/src/run.rs b/arbitrator/stylus/src/run.rs
index 8e673a25e5..6cbb0cfb42 100644
--- a/arbitrator/stylus/src/run.rs
+++ b/arbitrator/stylus/src/run.rs
@@ -4,18 +4,18 @@
#![allow(clippy::redundant_closure_call)]
use crate::{env::Escape, native::NativeInstance};
-use arbutil::evm::api::{DataReader, EvmApi};
+use arbutil::evm::api::{DataReader, EvmApi, Ink};
use arbutil::evm::user::UserOutcome;
use eyre::{eyre, Result};
use prover::machine::Machine;
use prover::programs::{prelude::*, STYLUS_ENTRY_POINT};
pub trait RunProgram {
- fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result;
+ fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: Ink) -> Result;
}
impl RunProgram for Machine {
- fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result {
+ fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: Ink) -> Result {
macro_rules! call {
($module:expr, $func:expr, $args:expr) => {
call!($module, $func, $args, |error| UserOutcome::Failure(error))
@@ -65,7 +65,7 @@ impl RunProgram for Machine {
}
impl> RunProgram for NativeInstance {
- fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result {
+ fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: Ink) -> Result {
use UserOutcome::*;
self.set_ink(ink);
diff --git a/arbitrator/stylus/src/target_cache.rs b/arbitrator/stylus/src/target_cache.rs
new file mode 100644
index 0000000000..a1d63829d6
--- /dev/null
+++ b/arbitrator/stylus/src/target_cache.rs
@@ -0,0 +1,81 @@
+// Copyright 2022-2024, Offchain Labs, Inc.
+// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE
+
+use eyre::{eyre, OptionExt, Result};
+use lazy_static::lazy_static;
+use parking_lot::RwLock;
+use std::{collections::HashMap, str::FromStr};
+use wasmer_types::{CpuFeature, Target, Triple};
+
+lazy_static! {
+ static ref TARGET_CACHE: RwLock> = RwLock::new(HashMap::new());
+ static ref TARGET_NATIVE: RwLock = RwLock::new(Target::default());
+}
+
+fn target_from_string(input: String) -> Result {
+ if input.is_empty() {
+ return Ok(Target::default());
+ }
+ let mut parts = input.split('+');
+
+ let Some(triple_string) = parts.next() else {
+ return Err(eyre!("no architecture"));
+ };
+
+ let triple = match Triple::from_str(triple_string) {
+ Ok(val) => val,
+ Err(e) => return Err(eyre!(e)),
+ };
+
+ let mut features = CpuFeature::set();
+ for flag in parts {
+ features.insert(CpuFeature::from_str(flag)?);
+ }
+
+ Ok(Target::new(triple, features))
+}
+
+/// Populates `TARGET_CACHE` inserting target specified by `description` under `name` key.
+/// Additionally, if `native` is set it sets `TARGET_NATIVE` to the specified target.
+pub fn target_cache_set(name: String, description: String, native: bool) -> Result<()> {
+ let target = target_from_string(description)?;
+
+ if native {
+ if !target.is_native() {
+ return Err(eyre!("arch not native"));
+ }
+ let flags_not_supported = Target::default()
+ .cpu_features()
+ .complement()
+ .intersection(*target.cpu_features());
+ if !flags_not_supported.is_empty() {
+ let mut err_message = String::new();
+ err_message.push_str("cpu flags not supported on local cpu for: ");
+ for item in flags_not_supported.iter() {
+ err_message.push('+');
+ err_message.push_str(&item.to_string());
+ }
+ return Err(eyre!(err_message));
+ }
+ *TARGET_NATIVE.write() = target.clone();
+ }
+
+ TARGET_CACHE.write().insert(name, target);
+
+ Ok(())
+}
+
+pub fn target_native() -> Target {
+ TARGET_NATIVE.read().clone()
+}
+
+pub fn target_cache_get(name: &str) -> Result {
+ if name.is_empty() {
+ return Ok(TARGET_NATIVE.read().clone());
+ }
+ TARGET_CACHE
+ .read()
+ .get(name)
+ .cloned()
+ .ok_or_eyre("arch not set")
+}
diff --git a/arbitrator/stylus/src/test/api.rs b/arbitrator/stylus/src/test/api.rs
index 92d7317918..7a5af6f89f 100644
--- a/arbitrator/stylus/src/test/api.rs
+++ b/arbitrator/stylus/src/test/api.rs
@@ -4,7 +4,7 @@
use crate::{native, run::RunProgram};
use arbutil::{
evm::{
- api::{EvmApi, VecReader},
+ api::{EvmApi, Gas, Ink, VecReader},
user::UserOutcomeKind,
EvmData,
},
@@ -14,6 +14,7 @@ use eyre::Result;
use parking_lot::Mutex;
use prover::programs::{memory::MemoryModel, prelude::*};
use std::{collections::HashMap, sync::Arc};
+use wasmer::Target;
use super::TestInstance;
@@ -53,7 +54,7 @@ impl TestEvmApi {
pub fn deploy(&mut self, address: Bytes20, config: StylusConfig, name: &str) -> Result<()> {
let file = format!("tests/{name}/target/wasm32-unknown-unknown/release/{name}.wasm");
let wasm = std::fs::read(file)?;
- let module = native::module(&wasm, self.compile.clone())?;
+ let module = native::module(&wasm, self.compile.clone(), Target::default())?;
self.contracts.lock().insert(address, module);
self.configs.lock().insert(address, config);
Ok(())
@@ -67,24 +68,24 @@ impl TestEvmApi {
}
impl EvmApi for TestEvmApi {
- fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) {
+ fn get_bytes32(&mut self, key: Bytes32, _evm_api_gas_to_use: Gas) -> (Bytes32, Gas) {
let storage = &mut self.storage.lock();
let storage = storage.get_mut(&self.program).unwrap();
let value = storage.get(&key).cloned().unwrap_or_default();
- (value, 2100) // pretend worst case
+ (value, Gas(2100)) // pretend worst case
}
- fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 {
+ fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Gas {
let storage = &mut self.storage.lock();
let storage = storage.get_mut(&self.program).unwrap();
storage.insert(key, value);
- 0
+ Gas(0)
}
- fn flush_storage_cache(&mut self, _clear: bool, _gas_left: u64) -> Result {
+ fn flush_storage_cache(&mut self, _clear: bool, _gas_left: Gas) -> Result {
let storage = &mut self.storage.lock();
let storage = storage.get_mut(&self.program).unwrap();
- Ok(22100 * storage.len() as u64) // pretend worst case
+ Ok(Gas(22100) * storage.len() as u64) // pretend worst case
}
fn get_transient_bytes32(&mut self, _key: Bytes32) -> Bytes32 {
@@ -101,10 +102,10 @@ impl EvmApi for TestEvmApi {
&mut self,
contract: Bytes20,
calldata: &[u8],
- _gas_left: u64,
- gas_req: u64,
+ _gas_left: Gas,
+ gas_req: Gas,
_value: Bytes32,
- ) -> (u32, u64, UserOutcomeKind) {
+ ) -> (u32, Gas, UserOutcomeKind) {
let compile = self.compile.clone();
let evm_data = self.evm_data;
let config = *self.configs.lock().get(&contract).unwrap();
@@ -121,7 +122,7 @@ impl EvmApi for TestEvmApi {
let (status, outs) = outcome.into_data();
let outs_len = outs.len() as u32;
- let ink_left: u64 = native.ink_left().into();
+ let ink_left: Ink = native.ink_left().into();
let gas_left = config.pricing.ink_to_gas(ink_left);
*self.write_result.lock() = outs;
(outs_len, gas - gas_left, status)
@@ -131,9 +132,9 @@ impl EvmApi for TestEvmApi {
&mut self,
_contract: Bytes20,
_calldata: &[u8],
- _gas_left: u64,
- _gas_req: u64,
- ) -> (u32, u64, UserOutcomeKind) {
+ _gas_left: Gas,
+ _gas_req: Gas,
+ ) -> (u32, Gas, UserOutcomeKind) {
todo!("delegate call not yet supported")
}
@@ -141,9 +142,9 @@ impl EvmApi for TestEvmApi {
&mut self,
contract: Bytes20,
calldata: &[u8],
- gas_left: u64,
- gas_req: u64,
- ) -> (u32, u64, UserOutcomeKind) {
+ gas_left: Gas,
+ gas_req: Gas,
+ ) -> (u32, Gas, UserOutcomeKind) {
println!("note: overriding static call with call");
self.contract_call(contract, calldata, gas_left, gas_req, Bytes32::default())
}
@@ -152,8 +153,8 @@ impl EvmApi for TestEvmApi {
&mut self,
_code: Vec,
_endowment: Bytes32,
- _gas: u64,
- ) -> (Result, u32, u64) {
+ _gas: Gas,
+ ) -> (Result, u32, Gas) {
unimplemented!("create1 not supported")
}
@@ -162,8 +163,8 @@ impl EvmApi for TestEvmApi {
_code: Vec,
_endowment: Bytes32,
_salt: Bytes32,
- _gas: u64,
- ) -> (Result, u32, u64) {
+ _gas: Gas,
+ ) -> (Result, u32, Gas) {
unimplemented!("create2 not supported")
}
@@ -175,19 +176,19 @@ impl EvmApi for TestEvmApi {
Ok(()) // pretend a log was emitted
}
- fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, u64) {
+ fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, Gas) {
unimplemented!()
}
- fn account_code(&mut self, _address: Bytes20, _gas_left: u64) -> (VecReader, u64) {
+ fn account_code(&mut self, _address: Bytes20, _gas_left: Gas) -> (VecReader, Gas) {
unimplemented!()
}
- fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, u64) {
+ fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, Gas) {
unimplemented!()
}
- fn add_pages(&mut self, new: u16) -> u64 {
+ fn add_pages(&mut self, new: u16) -> Gas {
let model = MemoryModel::new(2, 1000);
let (open, ever) = *self.pages.lock();
@@ -202,8 +203,8 @@ impl EvmApi for TestEvmApi {
_name: &str,
_args: &[u8],
_outs: &[u8],
- _start_ink: u64,
- _end_ink: u64,
+ _start_ink: Ink,
+ _end_ink: Ink,
) {
unimplemented!()
}
diff --git a/arbitrator/stylus/src/test/misc.rs b/arbitrator/stylus/src/test/misc.rs
index ae44a885f0..92c4394ae3 100644
--- a/arbitrator/stylus/src/test/misc.rs
+++ b/arbitrator/stylus/src/test/misc.rs
@@ -9,12 +9,12 @@ use crate::{
};
use eyre::Result;
use prover::programs::{prelude::*, start::StartMover};
-use wasmer::{imports, Function};
+use wasmer::{imports, Function, Target};
#[test]
fn test_bulk_memory() -> Result<()> {
let (compile, config, ink) = test_configs();
- let mut store = compile.store();
+ let mut store = compile.store(Target::default());
let filename = "../prover/test-cases/bulk-memory.wat";
let imports = imports! {
"env" => {
diff --git a/arbitrator/stylus/src/test/mod.rs b/arbitrator/stylus/src/test/mod.rs
index 236e5639e6..3fd0faede8 100644
--- a/arbitrator/stylus/src/test/mod.rs
+++ b/arbitrator/stylus/src/test/mod.rs
@@ -3,7 +3,10 @@
use crate::{env::WasmEnv, native::NativeInstance, run::RunProgram, test::api::TestEvmApi};
use arbutil::{
- evm::{api::VecReader, user::UserOutcome},
+ evm::{
+ api::{Ink, VecReader},
+ user::UserOutcome,
+ },
Bytes20, Bytes32, Color,
};
use eyre::{bail, Result};
@@ -16,7 +19,7 @@ use rand::prelude::*;
use std::{collections::HashMap, path::Path, sync::Arc};
use wasmer::{
imports, wasmparser::Operator, CompilerConfig, Function, FunctionEnv, Imports, Instance,
- Module, Store,
+ Module, Store, Target,
};
use wasmer_compiler_singlepass::Singlepass;
@@ -33,7 +36,7 @@ type TestInstance = NativeInstance;
impl TestInstance {
fn new_test(path: &str, compile: CompileConfig) -> Result {
- let mut store = compile.store();
+ let mut store = compile.store(Target::default());
let imports = imports! {
"test" => {
"noop" => Function::new_typed(&mut store, || {}),
@@ -41,7 +44,7 @@ impl TestInstance {
};
let mut native = Self::new_from_store(path, store, imports)?;
native.set_meter_data();
- native.set_ink(u64::MAX);
+ native.set_ink(Ink(u64::MAX));
native.set_stack(u32::MAX);
Ok(native)
}
@@ -86,7 +89,14 @@ impl TestInstance {
config: StylusConfig,
) -> Result<(Self, TestEvmApi)> {
let (mut evm, evm_data) = TestEvmApi::new(compile.clone());
- let native = Self::from_path(path, evm.clone(), evm_data, compile, config)?;
+ let native = Self::from_path(
+ path,
+ evm.clone(),
+ evm_data,
+ compile,
+ config,
+ Target::default(),
+ )?;
let footprint = native.memory().ty(&native.store).minimum.0 as u16;
evm.set_pages(footprint);
Ok((native, evm))
@@ -100,8 +110,8 @@ fn expensive_add(op: &Operator, _tys: &SigMap) -> u64 {
}
}
-pub fn random_ink(min: u64) -> u64 {
- rand::thread_rng().gen_range(min..=u64::MAX)
+pub fn random_ink(min: u64) -> Ink {
+ Ink(rand::thread_rng().gen_range(min..=u64::MAX))
}
pub fn random_bytes20() -> Bytes20 {
@@ -128,7 +138,7 @@ fn uniform_cost_config() -> StylusConfig {
stylus_config
}
-fn test_configs() -> (CompileConfig, StylusConfig, u64) {
+fn test_configs() -> (CompileConfig, StylusConfig, Ink) {
(
test_compile_config(),
uniform_cost_config(),
@@ -158,12 +168,12 @@ fn new_test_machine(path: &str, compile: &CompileConfig) -> Result {
Arc::new(|_, _, _| panic!("tried to read preimage")),
Some(stylus_data),
)?;
- mach.set_ink(u64::MAX);
+ mach.set_ink(Ink(u64::MAX));
mach.set_stack(u32::MAX);
Ok(mach)
}
-fn run_native(native: &mut TestInstance, args: &[u8], ink: u64) -> Result> {
+fn run_native(native: &mut TestInstance, args: &[u8], ink: Ink) -> Result> {
let config = native.env().config.expect("no config");
match native.run_main(args, config, ink)? {
UserOutcome::Success(output) => Ok(output),
@@ -175,7 +185,7 @@ fn run_machine(
machine: &mut Machine,
args: &[u8],
config: StylusConfig,
- ink: u64,
+ ink: Ink,
) -> Result> {
match machine.run_main(args, config, ink)? {
UserOutcome::Success(output) => Ok(output),
diff --git a/arbitrator/stylus/src/test/native.rs b/arbitrator/stylus/src/test/native.rs
index 503e5875fe..672bdd179c 100644
--- a/arbitrator/stylus/src/test/native.rs
+++ b/arbitrator/stylus/src/test/native.rs
@@ -16,7 +16,7 @@ use crate::{
use arbutil::{
crypto,
evm::{
- api::EvmApi,
+ api::{EvmApi, Gas, Ink},
user::{UserOutcome, UserOutcomeKind},
},
format, Bytes20, Bytes32, Color,
@@ -48,8 +48,8 @@ fn test_ink() -> Result<()> {
macro_rules! exhaust {
($ink:expr) => {
- native.set_ink($ink);
- assert_eq!(native.ink_left(), MachineMeter::Ready($ink));
+ native.set_ink(Ink($ink));
+ assert_eq!(native.ink_left(), MachineMeter::Ready(Ink($ink)));
assert!(add_one.call(&mut native.store, 32).is_err());
assert_eq!(native.ink_left(), MachineMeter::Exhausted);
};
@@ -59,12 +59,12 @@ fn test_ink() -> Result<()> {
exhaust!(50);
exhaust!(99);
- let mut ink_left = 500;
+ let mut ink_left = Ink(500);
native.set_ink(ink_left);
- while ink_left > 0 {
+ while ink_left > Ink(0) {
assert_eq!(native.ink_left(), MachineMeter::Ready(ink_left));
assert_eq!(add_one.call(&mut native.store, 64)?, 65);
- ink_left -= 100;
+ ink_left -= Ink(100);
}
assert!(add_one.call(&mut native.store, 32).is_err());
assert_eq!(native.ink_left(), MachineMeter::Exhausted);
@@ -198,7 +198,7 @@ fn test_import_export_safety() -> Result<()> {
let mut bin = bin?;
assert!(bin.clone().instrument(&compile, codehash).is_err());
compile.debug.debug_info = false;
- assert!(bin.instrument(&compile, &codehash).is_err());
+ assert!(bin.instrument(&compile, codehash).is_err());
if both {
assert!(TestInstance::new_test(file, compile).is_err());
@@ -268,7 +268,7 @@ fn test_heap() -> Result<()> {
assert_eq!(pages, 128);
let used = config.pricing.ink_to_gas(ink - native.ink_ready()?);
- ensure!((used as i64 - 32_000_000).abs() < 3_000, "wrong ink");
+ ensure!((used.0 as i64 - 32_000_000).abs() < 3_000, "wrong ink");
assert_eq!(native.memory_size(), Pages(128));
if step == extra {
@@ -283,7 +283,7 @@ fn test_heap() -> Result<()> {
// the cost should exceed a maximum u32, consuming more gas than can ever be bought
let (mut native, _) = TestInstance::new_with_evm("tests/memory2.wat", &compile, config)?;
- let outcome = native.run_main(&[], config, config.pricing.ink_to_gas(u32::MAX.into()))?;
+ let outcome = native.run_main(&[], config, config.pricing.gas_to_ink(Gas(u32::MAX.into())))?;
assert_eq!(outcome.kind(), UserOutcomeKind::OutOfInk);
// ensure we reject programs with excessive footprints
@@ -381,7 +381,7 @@ fn test_storage() -> Result<()> {
let (mut native, mut evm) = TestInstance::new_with_evm(filename, &compile, config)?;
run_native(&mut native, &store_args, ink)?;
- assert_eq!(evm.get_bytes32(key.into()).0, Bytes32(value));
+ assert_eq!(evm.get_bytes32(key.into(), Gas(0)).0, Bytes32(value));
assert_eq!(run_native(&mut native, &load_args, ink)?, value);
let mut machine = Machine::from_user_path(Path::new(filename), &compile)?;
@@ -465,7 +465,7 @@ fn test_calls() -> Result<()> {
run_native(&mut native, &args, ink)?;
for (key, value) in slots {
- assert_eq!(evm.get_bytes32(key).0, value);
+ assert_eq!(evm.get_bytes32(key, Gas(0)).0, value);
}
Ok(())
}
diff --git a/arbitrator/stylus/src/test/wavm.rs b/arbitrator/stylus/src/test/wavm.rs
index e707cf490a..729cfebf2f 100644
--- a/arbitrator/stylus/src/test/wavm.rs
+++ b/arbitrator/stylus/src/test/wavm.rs
@@ -2,6 +2,7 @@
// For license information, see https://github.com/nitro/blob/master/LICENSE
use crate::test::{new_test_machine, test_compile_config};
+use arbutil::evm::api::Ink;
use eyre::Result;
use prover::{programs::prelude::*, Machine};
@@ -15,8 +16,8 @@ fn test_ink() -> Result<()> {
macro_rules! exhaust {
($ink:expr) => {
- machine.set_ink($ink);
- assert_eq!(machine.ink_left(), MachineMeter::Ready($ink));
+ machine.set_ink(Ink($ink));
+ assert_eq!(machine.ink_left(), MachineMeter::Ready(Ink($ink)));
assert!(call(machine, 32).is_err());
assert_eq!(machine.ink_left(), MachineMeter::Exhausted);
};
@@ -26,12 +27,12 @@ fn test_ink() -> Result<()> {
exhaust!(50);
exhaust!(99);
- let mut ink_left = 500;
+ let mut ink_left = Ink(500);
machine.set_ink(ink_left);
- while ink_left > 0 {
+ while ink_left > Ink(0) {
assert_eq!(machine.ink_left(), MachineMeter::Ready(ink_left));
assert_eq!(call(machine, 64)?, vec![65_u32.into()]);
- ink_left -= 100;
+ ink_left -= Ink(100);
}
assert!(call(machine, 32).is_err());
assert_eq!(machine.ink_left(), MachineMeter::Exhausted);
diff --git a/arbitrator/stylus/tests/erc20/Cargo.lock b/arbitrator/stylus/tests/erc20/Cargo.lock
index c3e215978d..f5e1e0b15e 100644
--- a/arbitrator/stylus/tests/erc20/Cargo.lock
+++ b/arbitrator/stylus/tests/erc20/Cargo.lock
@@ -575,9 +575,9 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.37.23"
+version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags",
"errno",
diff --git a/arbitrator/stylus/tests/hostio-test/Cargo.lock b/arbitrator/stylus/tests/hostio-test/Cargo.lock
new file mode 100644
index 0000000000..1e726910b1
--- /dev/null
+++ b/arbitrator/stylus/tests/hostio-test/Cargo.lock
@@ -0,0 +1,636 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloy-primitives"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65"
+dependencies = [
+ "bytes",
+ "cfg-if 1.0.0",
+ "const-hex",
+ "derive_more",
+ "hex-literal",
+ "itoa",
+ "ruint",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "alloy-sol-macro"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9"
+dependencies = [
+ "dunce",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+ "syn-solidity",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "alloy-sol-types"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235"
+dependencies = [
+ "alloy-primitives",
+ "alloy-sol-macro",
+ "const-hex",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "const-hex"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "hex",
+ "proptest",
+ "serde",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "convert_case"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
+dependencies = [
+ "convert_case 0.4.0",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.77",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
+[[package]]
+name = "hostio-test"
+version = "0.1.0"
+dependencies = [
+ "mini-alloc",
+ "stylus-sdk",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "keccak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "keccak-const"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea"
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.159"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
+
+[[package]]
+name = "libm"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "memory_units"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
+
+[[package]]
+name = "mini-alloc"
+version = "0.4.2"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wee_alloc",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proptest"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d"
+dependencies = [
+ "bitflags",
+ "num-traits",
+ "rand",
+ "rand_chacha",
+ "rand_xorshift",
+ "unarray",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_xorshift"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
+
+[[package]]
+name = "ruint"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286"
+dependencies = [
+ "proptest",
+ "rand",
+ "ruint-macro",
+ "serde",
+ "valuable",
+ "zeroize",
+]
+
+[[package]]
+name = "ruint-macro"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+
+[[package]]
+name = "serde"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+dependencies = [
+ "digest",
+ "keccak",
+]
+
+[[package]]
+name = "stylus-proc"
+version = "0.4.2"
+dependencies = [
+ "alloy-primitives",
+ "alloy-sol-types",
+ "cfg-if 1.0.0",
+ "convert_case 0.6.0",
+ "lazy_static",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "sha3",
+ "syn 1.0.109",
+ "syn-solidity",
+]
+
+[[package]]
+name = "stylus-sdk"
+version = "0.4.2"
+dependencies = [
+ "alloy-primitives",
+ "alloy-sol-types",
+ "cfg-if 1.0.0",
+ "derivative",
+ "hex",
+ "keccak-const",
+ "lazy_static",
+ "stylus-proc",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn-solidity"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wee_alloc"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "memory_units",
+ "winapi",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.77",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/arbitrator/stylus/tests/hostio-test/Cargo.toml b/arbitrator/stylus/tests/hostio-test/Cargo.toml
new file mode 100644
index 0000000000..da7bbce7a3
--- /dev/null
+++ b/arbitrator/stylus/tests/hostio-test/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "hostio-test"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["debug", "hostio"] }
+mini-alloc.path = "../../../langs/rust/mini-alloc"
+
+[profile.release]
+codegen-units = 1
+strip = true
+lto = true
+panic = "abort"
+opt-level = "s"
+
+[workspace]
diff --git a/arbitrator/stylus/tests/hostio-test/src/main.rs b/arbitrator/stylus/tests/hostio-test/src/main.rs
new file mode 100644
index 0000000000..17a5d10266
--- /dev/null
+++ b/arbitrator/stylus/tests/hostio-test/src/main.rs
@@ -0,0 +1,207 @@
+// Copyright 2024, Offchain Labs, Inc.
+// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE
+
+#![no_main]
+
+use stylus_sdk::{
+ abi::Bytes,
+ alloy_primitives::{Address, B256, U256},
+ block, console, contract, evm, hostio, msg,
+ prelude::*,
+ stylus_proc::entrypoint,
+ tx,
+ types::AddressVM,
+};
+extern crate alloc;
+
+#[cfg(target_arch = "wasm32")]
+#[global_allocator]
+static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct HostioTest {
+ }
+}
+
+type Result = std::result::Result