diff --git a/.github/actions/label/build.js b/.github/actions/label/build.js index c4d456b194e..8866bcf4e09 100644 --- a/.github/actions/label/build.js +++ b/.github/actions/label/build.js @@ -30,29 +30,24 @@ const skip = async ({ core, github }) => { ref: REF, }); - core.info(`check runs: ${check_runs}`); - const runs = linux ? check_runs.filter( (run) => run.name === "build" || run.name === "build / linux" ) : check_runs.filter((run) => run.name === "build / macos-x86"); - // Skip this action by default. - let skipped = false; + let skipAction = false; for (run of runs) { - // Process this action only if the previous build has been skipped. + // If there is already a build, skip this action. if ( - (run.name === "build" && run.conclusion === "skipped") - ) - skipped = true; - - // If there is already a build, skip this action without more conditions. - if (run.name === "build / linux" || run.name === "build / macos-x86") - return true; + run.name === "build / linux" + || run.name === "build / macos-x86" + || (run.name === "build" && run.conclusion !== "skipped")) { + return [true]; + } } - return !skipped; + return [skipAction, JSON.stringify(check_runs, null, 2)]; }; /** @@ -147,8 +142,9 @@ const listJobs = async ({ github, core, run_id }) => { * The main function. **/ module.exports = async ({ github, core }) => { - if (await skip({ core, github })) { - core.info("Build has already been processed."); + const [skipAction, check_runs] = await skip({ core, github }); + if (skipAction) { + core.info("Build has already been processed, check runs: " + check_runs); return; } diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 01a50f5f55a..8ea62ea73ee 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -127,6 +127,9 @@ jobs: - name: "Build: Node" run: ./scripts/gear.sh build node --release --locked + - name: "Check: Stack height limit" + run: cargo run -p calc-stack-height --release --locked + - name: "Test: gsdk tests" run: ./scripts/gear.sh test gsdk --release diff --git a/.github/workflows/PR.yml b/.github/workflows/PR.yml index dd388ecdb1e..cc3db9588b5 100644 --- a/.github/workflows/PR.yml +++ b/.github/workflows/PR.yml @@ -43,7 +43,7 @@ jobs: echo "cache: ${CACHE}" echo "cache=${CACHE}" >> $GITHUB_OUTPUT - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 env: HEAD_SHA: ${{ github.event.pull_request.head.sha }} TITLE: ${{ github.event.pull_request.title }} diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 1337b6fc61c..c33fc1468ac 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -44,6 +44,9 @@ jobs: - name: "Build: Node" run: cargo build -p gear-cli + + - name: "Check: Stack height limit" + run: cargo run -p calc-stack-height --release --locked - name: "Test: Lazy pages" run: >- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 09ca0ceb9d8..0a08e75669c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,6 +74,9 @@ jobs: - name: "Check: Vara runtime imports" run: ./target/release/wasm-proc --check-runtime-imports target/release/wbuild/vara-runtime/vara_runtime.compact.wasm + - name: "Check: Stack height limit" + run: cargo run -p calc-stack-height --release --locked + - name: "Test: Gear workspace" run: ./scripts/gear.sh test gear --exclude gclient --exclude gcli --exclude gsdk --release --locked @@ -100,7 +103,7 @@ jobs: # check also lazy-pages benchmarks tests for native runtime ./target/release/gear benchmark pallet --chain=dev --pallet=pallet_gear --extrinsic="check_lazy_pages_all" --execution=native --heap-pages=4096 --extra - - name: "Test: Sys-calls Wasmi integrity" + - name: "Test: Syscalls Wasmi integrity" run: ./scripts/gear.sh test syscalls --release - name: "Test: `try-runtime` feature tests" @@ -110,7 +113,7 @@ jobs: - name: "Test: Try runtime migrations" run: | cargo build -p gear-cli --features try-runtime --release --locked - ./target/release/gear try-runtime --runtime ./target/release/wbuild/vara-runtime/vara_runtime.wasm on-runtime-upgrade --checks live --uri ws://52.32.138.151:9944 + ./target/release/gear try-runtime --runtime ./target/release/wbuild/vara-runtime/vara_runtime.wasm on-runtime-upgrade --checks live --uri ws://rpc-private.vara-network.io:9944 env: RUST_LOG: info @@ -175,8 +178,6 @@ jobs: with: path: artifact - - run: sccache --show-stats - win-cross: runs-on: [kuberunner, github-runner-03] env: @@ -228,6 +229,11 @@ jobs: env: CARGO_BUILD_TARGET: x86_64-pc-windows-msvc + - name: "Check: Stack height limit" + run: cargo xwin run -p calc-stack-height --release --locked + env: + CARGO_BUILD_TARGET: x86_64-pc-windows-msvc + # These tests randomly stops responding #- name: "Test: Client tests" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index edb648865d9..a3f7fdde5f1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -61,8 +61,6 @@ jobs: - name: "Check: Compiling gstd on stable" run: cargo +stable check -p gstd - - run: sccache --show-stats - fuzzer: runs-on: [kuberunner, github-runner-01] env: @@ -78,6 +76,13 @@ jobs: - name: "ACTIONS: Checkout" uses: actions/checkout@v4 + - name: "MOUNT: Logs path" + run: | + FUZZER_LOGS_PATH=/mnt/fuzzer_logs + sudo mkdir -p $FUZZER_LOGS_PATH + sudo ln -s $FUZZER_LOGS_PATH/artifacts $PWD/utils/runtime-fuzzer/fuzz/artifacts + sudo ln -s $FUZZER_LOGS_PATH/proptest-regressions $PWD/utils/runtime-fuzzer/proptest-regressions + - name: "Install: Set cargo path" run: echo "/tmp/cargo/bin" >> $GITHUB_PATH @@ -90,8 +95,8 @@ jobs: - name: Install cargo-fuzz run: cargo install cargo-fuzz - - name: "Check fuzzer with mutation test" - run: ./scripts/check-fuzzer.sh + - name: "Run runtime-fuzzer crate tests" + run: ./scripts/gear.sh test fuzzer-tests - - name: "Check fuzzer reproduction" - run: ./scripts/gear.sh test fuzz-repr + - name: "Check fuzzer competence with mutation test" + run: ./scripts/check-fuzzer.sh diff --git a/.github/workflows/crates-io.yml b/.github/workflows/crates-io.yml new file mode 100644 index 00000000000..8d03d121436 --- /dev/null +++ b/.github/workflows/crates-io.yml @@ -0,0 +1,25 @@ +name: Crates IO + +on: + workflow_dispatch: + +env: + CARGO_INCREMENTAL: 0 + CARGO_TERM_COLOR: always + RUST_BACKTRACE: short + TERM: xterm-256color + +jobs: + publish: + runs-on: ubuntu-latest + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + steps: + - name: "ACTIONS: Checkout" + uses: actions/checkout@v4 + + - name: "Install: Rust toolchain" + uses: dsherret/rust-toolchain-file@v1 + + - name: "Publish packages" + run: cargo run --release -p crates-io-manager diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index c27ae71e5a6..ae81d5e140f 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 env: HEAD_SHA: ${{ github.event.pull_request.head.sha }} LABEL: ${{ github.event.label.name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70a8233f4f4..36b562829bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,9 +10,9 @@ env: TERM: xterm-256color jobs: - generate-changelog: + changelog: name: Generate changelog - runs-on: ubuntu-latest + runs-on: [kuberunner] outputs: release_body: ${{ steps.git-cliff.outputs.content }} steps: @@ -20,9 +20,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Update config - shell: bash - run: sed -E -i "s/\s+\#\s(.*)\s\#\sreplace issue numbers/\\t\1/g" cliff.toml + - name: Generate a changelog uses: orhun/git-cliff-action@v2 id: git-cliff @@ -32,21 +30,8 @@ jobs: env: OUTPUT: CHANGES.md - prepare: - needs: generate-changelog - runs-on: ubuntu-latest - steps: - - id: version - run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT - - - name: Show version - run: echo ${{ steps.version.outputs.VERSION }} - - outputs: - version: ${{ steps.version.outputs.VERSION }} - build: - needs: prepare + needs: changelog runs-on: [kuberunner] steps: - name: "Actions: Checkout" @@ -82,7 +67,7 @@ jobs: run: ./wasm-proc --check-runtime-imports --check-runtime-is-dev false target/production/wbuild/vara-runtime/vara_runtime.compact.wasm - name: "Artifact: Production `vara-runtime`" - run: cp target/production/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm "artifact/vara_runtime_v$VARA_SPEC.wasm" + run: cp target/production/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm "artifact/production_vara_runtime_v$VARA_SPEC.wasm" - name: "Build: Production node client and development `vara-runtime`" run: cargo build -p gear-cli --profile production @@ -92,14 +77,14 @@ jobs: - name: "Artifact: Production node client and development `vara-runtime`" run: | - cp target/production/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm "artifact/vara_devruntime_v$VARA_SPEC.wasm" + cp target/production/wbuild/vara-runtime/vara_runtime.compact.compressed.wasm "artifact/testnet_vara_runtime_v$VARA_SPEC.wasm" cp target/production/gear artifact/gear strip artifact/gear || true - name: Publish uses: softprops/action-gh-release@v1 with: - body: "${{ needs.generate-changelog.outputs.release_body }}" + body: ${{ needs.changelog.outputs.release_body }} files: artifact/* tag_name: ${{ steps.version.outputs.VERSION }} draft: true diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 67bcb54f0f4..4732ebb456f 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -52,7 +52,7 @@ jobs: context: . file: ./docker/Dockerfile push: true - tags: ghcr.io/gear-tech/node:latest, ${{ needs.tag-image.outputs.image_tag }} + tags: ${{ needs.tag-image.outputs.image_tag }} - name: SSH into VM uses: appleboy/ssh-action@v1.0.0 diff --git a/.gitignore b/.gitignore index 5f4cba4f153..b19ce35a504 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules/ target/ target-no-lazy/ +target-xwin/ log/ .binpath .vscode diff --git a/Cargo.lock b/Cargo.lock index 6db9f433084..a11bb4b00a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "actor-system-error" -version = "0.1.0" +version = "1.0.3" dependencies = [ "derive_more", ] @@ -529,7 +529,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -564,7 +564,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1024,6 +1024,20 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "calc-stack-height" +version = "1.0.3" +dependencies = [ + "anyhow", + "env_logger", + "gear-core", + "log", + "vara-runtime", + "wabt", + "wasmer", + "wasmer-types", +] + [[package]] name = "camino" version = "1.1.6" @@ -1056,6 +1070,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cargo_toml" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" +dependencies = [ + "serde", + "toml 0.7.8", +] + [[package]] name = "cc" version = "1.0.83" @@ -1217,9 +1241,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" dependencies = [ "clap_builder", "clap_derive 4.4.7", @@ -1227,9 +1251,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" dependencies = [ "anstream", "anstyle", @@ -1259,7 +1283,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1616,6 +1640,32 @@ dependencies = [ "wasmtime-types", ] +[[package]] +name = "crates-io" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876aa69b4afca5f2eb5e23daa3445930faf829bcb67075a20ffa884f11f8c57c" +dependencies = [ + "anyhow", + "curl", + "percent-encoding", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "crates-io-manager" +version = "1.0.3" +dependencies = [ + "anyhow", + "cargo_metadata", + "cargo_toml", + "crates-io", + "curl", + "toml 0.7.8", +] + [[package]] name = "crc" version = "3.0.1" @@ -1772,6 +1822,36 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2 0.4.9", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.68+curl-8.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a0d18d88360e374b16b2273c832b5e57258ffc1d4aa4f96b108e0738d5752f" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "windows-sys 0.48.0", +] + [[package]] name = "curve25519-dalek" version = "2.1.3" @@ -1823,7 +1903,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1863,7 +1943,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1880,7 +1960,7 @@ checksum = "587663dd5fb3d10932c8aecfe7c844db1bcf0aee93eeab08fac13dc1212c2e7f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1928,7 +2008,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -1950,7 +2030,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2051,6 +2131,7 @@ name = "demo-calc-hash" version = "0.1.0" dependencies = [ "gear-wasm-builder", + "gstd", "parity-scale-codec", "sha2 0.10.8", ] @@ -2106,6 +2187,14 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "demo-delayed-reservation-sender" +version = "0.1.0" +dependencies = [ + "gear-wasm-builder", + "gstd", +] + [[package]] name = "demo-delayed-sender" version = "0.1.0" @@ -2207,6 +2296,7 @@ name = "demo-meta-io" version = "0.1.0" dependencies = [ "gmeta", + "gstd", "parity-scale-codec", "scale-info", ] @@ -2416,6 +2506,14 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "demo-signal-wait" +version = "0.1.0" +dependencies = [ + "gear-wasm-builder", + "gstd", +] + [[package]] name = "demo-stack-allocations" version = "0.1.0" @@ -2611,7 +2709,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2779,7 +2877,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -2800,6 +2898,15 @@ dependencies = [ "str-buf", ] +[[package]] +name = "document-features" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e493c573fce17f00dcab13b6ac057994f3ce17d1af4dc39bfd482b83c6eb6157" +dependencies = [ + "litrs", +] + [[package]] name = "downcast" version = "0.11.0" @@ -3069,7 +3176,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3110,7 +3217,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3131,14 +3238,14 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -3161,12 +3268,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3395,9 +3502,9 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -3441,7 +3548,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.4.7", + "clap 4.4.10", "comfy-table", "frame-benchmarking", "frame-support", @@ -3756,7 +3863,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47" dependencies = [ - "rustix 0.38.21", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -3846,7 +3953,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -3907,18 +4014,18 @@ dependencies = [ [[package]] name = "galloc" -version = "1.0.2" +version = "1.0.3" dependencies = [ "dlmalloc", ] [[package]] name = "gcli" -version = "1.0.2" +version = "1.0.3" dependencies = [ "anyhow", "base64 0.21.5", - "clap 4.4.7", + "clap 4.4.10", "color-eyre", "demo-messager", "demo-new-meta", @@ -3928,8 +4035,6 @@ dependencies = [ "etc", "gear-core", "gear-core-errors", - "gear-core-processor", - "gear-lazy-pages-interface", "gmeta", "gsdk", "hex", @@ -3957,7 +4062,7 @@ dependencies = [ [[package]] name = "gclient" -version = "1.0.2" +version = "1.0.3" dependencies = [ "anyhow", "async-trait", @@ -3999,7 +4104,7 @@ dependencies = [ [[package]] name = "gcore" -version = "1.0.2" +version = "1.0.3" dependencies = [ "galloc", "gear-core-errors", @@ -4012,7 +4117,7 @@ dependencies = [ [[package]] name = "gear-authorship" -version = "1.0.2" +version = "1.0.3" dependencies = [ "demo-mul-by-const", "env_logger", @@ -4057,7 +4162,7 @@ dependencies = [ name = "gear-bags-thresholds" version = "1.0.0" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "generate-bags", "vara-runtime", ] @@ -4079,9 +4184,9 @@ dependencies = [ [[package]] name = "gear-cli" -version = "1.0.2" +version = "1.0.3" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -4111,7 +4216,7 @@ dependencies = [ [[package]] name = "gear-common" -version = "1.0.2" +version = "1.0.3" dependencies = [ "derive_more", "enum-iterator 1.4.1", @@ -4136,15 +4241,15 @@ dependencies = [ [[package]] name = "gear-common-codegen" -version = "1.0.2" +version = "1.0.3" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "gear-core" -version = "1.0.2" +version = "1.0.3" dependencies = [ "blake2-rfc", "byteorder", @@ -4171,7 +4276,7 @@ dependencies = [ [[package]] name = "gear-core-backend" -version = "1.0.2" +version = "1.0.3" dependencies = [ "actor-system-error", "blake2-rfc", @@ -4189,7 +4294,7 @@ dependencies = [ [[package]] name = "gear-core-errors" -version = "1.0.2" +version = "1.0.3" dependencies = [ "derive_more", "enum-iterator 1.4.1", @@ -4198,7 +4303,7 @@ dependencies = [ [[package]] name = "gear-core-processor" -version = "1.0.2" +version = "1.0.3" dependencies = [ "actor-system-error", "derive_more", @@ -4217,7 +4322,7 @@ dependencies = [ [[package]] name = "gear-key-finder" -version = "1.0.2" +version = "1.0.3" dependencies = [ "directories 5.0.1", "hex", @@ -4225,7 +4330,7 @@ dependencies = [ [[package]] name = "gear-lazy-pages" -version = "1.0.2" +version = "1.0.3" dependencies = [ "cfg-if", "derive_more", @@ -4238,17 +4343,14 @@ dependencies = [ "log", "mach", "nix 0.26.4", - "once_cell", "region", - "sp-io", - "sp-std 5.0.0", "sp-wasm-interface", "winapi", ] [[package]] name = "gear-lazy-pages-common" -version = "1.0.2" +version = "1.0.3" dependencies = [ "gear-core", "num_enum", @@ -4257,24 +4359,23 @@ dependencies = [ [[package]] name = "gear-lazy-pages-interface" -version = "1.0.2" +version = "1.0.3" dependencies = [ "byteorder", "gear-common", "gear-core", "gear-lazy-pages-common", "gear-runtime-interface", - "gear-wasm-instrument", "log", "sp-std 5.0.0", ] [[package]] name = "gear-node-loader" -version = "1.0.2" +version = "1.0.3" dependencies = [ "anyhow", - "clap 4.4.7", + "clap 4.4.10", "futures", "futures-timer", "gclient", @@ -4285,7 +4386,6 @@ dependencies = [ "gear-wasm-gen", "gsdk", "names 0.14.0", - "once_cell", "parking_lot 0.12.1", "primitive-types", "rand 0.8.5", @@ -4295,12 +4395,12 @@ dependencies = [ "tokio", "tracing", "tracing-appender", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", ] [[package]] name = "gear-node-testing" -version = "1.0.2" +version = "1.0.3" dependencies = [ "frame-benchmarking", "frame-support", @@ -4341,7 +4441,7 @@ dependencies = [ name = "gear-replay-cli" version = "1.0.0" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "frame-remote-externalities", "frame-system", "gear-runtime-interface", @@ -4368,7 +4468,7 @@ dependencies = [ [[package]] name = "gear-runtime-common" -version = "1.0.2" +version = "1.0.3" dependencies = [ "frame-benchmarking", "frame-support", @@ -4393,7 +4493,7 @@ dependencies = [ [[package]] name = "gear-runtime-interface" -version = "1.0.2" +version = "1.0.3" dependencies = [ "byteorder", "gear-core", @@ -4402,6 +4502,7 @@ dependencies = [ "gear-sandbox-host", "log", "parity-scale-codec", + "sp-io", "sp-runtime-interface", "sp-std 5.0.0", "sp-wasm-interface", @@ -4411,7 +4512,7 @@ dependencies = [ [[package]] name = "gear-runtime-primitives" -version = "1.0.2" +version = "1.0.3" dependencies = [ "sp-core", "sp-runtime", @@ -4435,7 +4536,7 @@ dependencies = [ [[package]] name = "gear-sandbox-env" -version = "0.1.0" +version = "1.0.3" dependencies = [ "parity-scale-codec", "sp-core", @@ -4445,12 +4546,11 @@ dependencies = [ [[package]] name = "gear-sandbox-host" -version = "0.1.0" +version = "1.0.3" dependencies = [ "environmental", "gear-sandbox-env", "log", - "once_cell", "parity-scale-codec", "sp-allocator", "sp-wasm-interface", @@ -4464,7 +4564,7 @@ dependencies = [ [[package]] name = "gear-service" -version = "1.0.2" +version = "1.0.3" dependencies = [ "frame-benchmarking", "frame-benchmarking-cli", @@ -4534,14 +4634,14 @@ dependencies = [ [[package]] name = "gear-stack-buffer" -version = "1.0.2" +version = "1.0.3" dependencies = [ "cc", ] [[package]] name = "gear-utils" -version = "0.1.0" +version = "1.0.3" dependencies = [ "env_logger", "gear-core", @@ -4557,7 +4657,7 @@ dependencies = [ name = "gear-validator-checks" version = "0.1.0" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "env_logger", "futures", "gsdk", @@ -4571,9 +4671,9 @@ dependencies = [ [[package]] name = "gear-wasm" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f745ed9eb163f4509f01d5564e37db52ec43dd23569bafdba597a5f1e4c125c9" +checksum = "bbfbfa701dc65e683fcd2fb24f046bcef22634acbdf47ad14724637dc39ad05b" [[package]] name = "gear-wasm-builder" @@ -4627,11 +4727,10 @@ dependencies = [ [[package]] name = "gear-wasm-instrument" -version = "1.0.2" +version = "1.0.3" dependencies = [ "enum-iterator 1.4.1", "gear-core", - "gear-core-backend", "gwasm-instrument", "wasmparser-nostd 0.100.1", "wat", @@ -4641,7 +4740,7 @@ dependencies = [ name = "gear-weight-diff" version = "1.0.0" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "frame-support", "indexmap 2.1.0", "pallet-gear", @@ -4805,7 +4904,7 @@ dependencies = [ [[package]] name = "gmeta" -version = "1.0.2" +version = "1.0.3" dependencies = [ "blake2-rfc", "derive_more", @@ -4819,7 +4918,7 @@ dependencies = [ [[package]] name = "gmeta-codegen" -version = "1.0.2" +version = "1.0.3" dependencies = [ "gmeta", "gstd", @@ -4827,7 +4926,7 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -4854,7 +4953,7 @@ dependencies = [ [[package]] name = "gsdk" -version = "1.0.2" +version = "1.0.3" dependencies = [ "anyhow", "base64 0.21.5", @@ -4902,23 +5001,24 @@ dependencies = [ "sp-io", "subxt-codegen", "subxt-metadata", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "gsdk-codegen" -version = "1.0.2" +version = "1.0.3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "gstd" -version = "1.0.2" +version = "1.0.3" dependencies = [ "bs58 0.5.0", + "document-features", "futures", "galloc", "gcore", @@ -4939,13 +5039,13 @@ dependencies = [ "gstd", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "trybuild", ] [[package]] name = "gsys" -version = "1.0.2" +version = "1.0.3" [[package]] name = "gtest" @@ -4962,7 +5062,8 @@ dependencies = [ "gear-core", "gear-core-errors", "gear-core-processor", - "gear-lazy-pages-interface", + "gear-lazy-pages", + "gear-lazy-pages-common", "gear-utils", "gear-wasm-instrument", "gsys", @@ -4971,14 +5072,13 @@ dependencies = [ "parity-scale-codec", "path-clean", "rand 0.8.5", - "sp-io", ] [[package]] name = "gwasm-instrument" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcb127cb43d375de7cdacffd0e4e1c746e52381d11a0465909ae6fbecb99c6c3" +checksum = "69fe7c1db90c8183b2aecb6d3629b2e59b71dfb538d9138f9ae2fa76a2fc0c55" dependencies = [ "gear-wasm", ] @@ -5325,9 +5425,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -5532,7 +5632,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.3", - "rustix 0.38.21", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -5895,9 +5995,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libc-print" @@ -6612,9 +6712,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + +[[package]] +name = "litrs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" [[package]] name = "lock_api" @@ -7409,7 +7515,7 @@ checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -7418,6 +7524,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "numerated" +version = "1.0.3" +dependencies = [ + "derive_more", + "env_logger", + "log", + "num-traits", + "parity-scale-codec", + "proptest", + "scale-info", +] + [[package]] name = "object" version = "0.28.4" @@ -7493,6 +7612,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -7730,7 +7861,7 @@ dependencies = [ [[package]] name = "pallet-gear" -version = "1.0.2" +version = "1.0.3" dependencies = [ "blake2-rfc", "demo-async", @@ -7745,6 +7876,7 @@ dependencies = [ "demo-compose", "demo-constructor", "demo-custom", + "demo-delayed-reservation-sender", "demo-delayed-sender", "demo-distributor", "demo-futures-unordered", @@ -7768,6 +7900,7 @@ dependencies = [ "demo-rwlock", "demo-send-from-reservation", "demo-signal-entry", + "demo-signal-wait", "demo-state-rollback", "demo-sync-duplicate", "demo-wait", @@ -7828,7 +7961,7 @@ dependencies = [ [[package]] name = "pallet-gear-bank" -version = "1.0.2" +version = "1.0.3" dependencies = [ "frame-benchmarking", "frame-support", @@ -7846,7 +7979,7 @@ dependencies = [ [[package]] name = "pallet-gear-debug" -version = "1.0.2" +version = "1.0.3" dependencies = [ "demo-vec", "env_logger", @@ -7882,7 +8015,7 @@ dependencies = [ [[package]] name = "pallet-gear-gas" -version = "1.0.2" +version = "1.0.3" dependencies = [ "env_logger", "frame-benchmarking", @@ -7911,7 +8044,7 @@ dependencies = [ [[package]] name = "pallet-gear-messenger" -version = "1.0.2" +version = "1.0.3" dependencies = [ "env_logger", "frame-benchmarking", @@ -7935,7 +8068,7 @@ dependencies = [ [[package]] name = "pallet-gear-payment" -version = "1.0.2" +version = "1.0.3" dependencies = [ "env_logger", "frame-benchmarking", @@ -7969,16 +8102,16 @@ dependencies = [ [[package]] name = "pallet-gear-proc-macro" -version = "1.0.2" +version = "1.0.3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] name = "pallet-gear-program" -version = "1.0.2" +version = "1.0.3" dependencies = [ "frame-support", "frame-system", @@ -8003,7 +8136,7 @@ dependencies = [ [[package]] name = "pallet-gear-rpc" -version = "1.0.2" +version = "1.0.3" dependencies = [ "gear-common", "gear-core", @@ -8019,7 +8152,7 @@ dependencies = [ [[package]] name = "pallet-gear-rpc-runtime-api" -version = "1.0.2" +version = "1.0.3" dependencies = [ "pallet-gear", "sp-api", @@ -8030,7 +8163,7 @@ dependencies = [ [[package]] name = "pallet-gear-scheduler" -version = "1.0.2" +version = "1.0.3" dependencies = [ "env_logger", "frame-benchmarking", @@ -8061,7 +8194,7 @@ dependencies = [ [[package]] name = "pallet-gear-staking-rewards" -version = "1.0.2" +version = "1.0.3" dependencies = [ "env_logger", "frame-benchmarking", @@ -8116,7 +8249,7 @@ dependencies = [ [[package]] name = "pallet-gear-voucher" -version = "1.0.2" +version = "1.0.3" dependencies = [ "env_logger", "frame-benchmarking", @@ -8751,9 +8884,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -8786,7 +8919,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -8827,7 +8960,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -9109,9 +9242,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -9178,9 +9311,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", @@ -9190,7 +9323,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.7.5", + "regex-syntax 0.8.2", "rusty-fork", "tempfile", "unarray", @@ -9590,7 +9723,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -9682,7 +9815,7 @@ dependencies = [ name = "regression-analysis" version = "0.1.0" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "frame-support", "junit-common", "pallet-gear", @@ -9908,7 +10041,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arbitrary", - "clap 4.4.7", + "clap 4.4.10", "frame-support", "frame-system", "gear-call-gen", @@ -10016,14 +10149,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys 0.4.10", + "linux-raw-sys 0.4.11", "windows-sys 0.48.0", ] @@ -10211,7 +10344,7 @@ source = "git+https://github.com/gear-tech/substrate.git?branch=gear-polkadot-v0 dependencies = [ "array-bytes", "chrono", - "clap 4.4.7", + "clap 4.4.10", "fdlimit", "futures", "libp2p-identity", @@ -10999,7 +11132,7 @@ name = "sc-storage-monitor" version = "0.1.0" source = "git+https://github.com/gear-tech/substrate.git?branch=gear-polkadot-v0.9.43-canary-no-sandbox#26cb1995d90479894e0631a5ba37e05ce878bd8f" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "fs4", "futures", "log", @@ -11094,7 +11227,7 @@ dependencies = [ "sp-tracing", "thiserror", "tracing", - "tracing-log", + "tracing-log 0.1.3", "tracing-subscriber 0.2.25", ] @@ -11512,9 +11645,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.190" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -11530,13 +11663,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -11558,7 +11691,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -13012,7 +13145,7 @@ dependencies = [ "quote", "scale-info", "subxt-metadata", - "syn 2.0.38", + "syn 2.0.39", "thiserror", "tokio", ] @@ -13043,7 +13176,7 @@ dependencies = [ "darling 0.20.3", "proc-macro-error", "subxt-codegen", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -13072,9 +13205,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -13159,7 +13292,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.4.1", - "rustix 0.38.21", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -13230,7 +13363,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -13352,9 +13485,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -13371,13 +13504,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -13541,13 +13674,14 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", + "thiserror", "time", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", ] [[package]] @@ -13558,7 +13692,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] @@ -13578,7 +13712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ "tracing", - "tracing-subscriber 0.3.17", + "tracing-subscriber 0.3.18", ] [[package]] @@ -13602,6 +13736,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -13631,15 +13776,15 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.1.3", "tracing-serde", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers 0.1.0", "nu-ansi-term", @@ -13652,7 +13797,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-serde", ] @@ -13736,7 +13881,7 @@ version = "0.10.0-dev" source = "git+https://github.com/gear-tech/substrate.git?branch=gear-polkadot-v0.9.43-canary-no-sandbox#26cb1995d90479894e0631a5ba37e05ce878bd8f" dependencies = [ "async-trait", - "clap 4.4.7", + "clap 4.4.10", "frame-remote-externalities", "frame-try-runtime", "hex", @@ -13929,12 +14074,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.5.0", "percent-encoding", ] @@ -13961,7 +14106,7 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vara-runtime" -version = "1.0.2" +version = "1.0.3" dependencies = [ "const-str", "env_logger", @@ -14164,7 +14309,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-shared", ] @@ -14198,7 +14343,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -14219,9 +14364,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.36.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53ae0be20bf87918df4fa831bfbbd0b491d24aee407ed86360eae4c2c5608d38" +checksum = "7b09bc5df933a3dabbdb72ae4b6b71be8ae07f58774d5aa41bd20adcd41a235a" dependencies = [ "leb128", ] @@ -14230,7 +14375,7 @@ dependencies = [ name = "wasm-info" version = "0.1.0" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "hex", "parity-wasm 0.45.0", ] @@ -14287,7 +14432,7 @@ dependencies = [ name = "wasm-proc" version = "0.1.0" dependencies = [ - "clap 4.4.7", + "clap 4.4.10", "env_logger", "gear-wasm-builder", "log", @@ -14729,9 +14874,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.116.0" +version = "0.118.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53290b1276c5c2d47d694fb1a920538c01f51690e7e261acbe1d10c5fc306ea1" +checksum = "ebbb91574de0011ded32b14db12777e7dd5e9ea2f9d7317a1ab51a9495c75924" dependencies = [ "indexmap 2.1.0", "semver 1.0.20", @@ -14754,12 +14899,12 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.71" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f98260aa20f939518bcec1fac32c78898d5c68872e7363a4651f21f791b6c7e" +checksum = "61a7a046e6636d25c06a5df00bdc34e02f9e6e0e8a356d738299b961a6126114" dependencies = [ "anyhow", - "wasmparser 0.116.0", + "wasmparser 0.118.0", ] [[package]] @@ -14959,21 +15104,21 @@ dependencies = [ [[package]] name = "wast" -version = "67.0.0" +version = "69.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c2933efd77ff2398b83817a98984ffe4b67aefd9aa1d2c8e68e19b553f1c38" +checksum = "efa51b5ad1391943d1bfad537e50f28fe938199ee76b115be6bae83802cd5185" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.36.1", + "wasm-encoder 0.38.0", ] [[package]] name = "wat" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02905d13751dcb18f4e19f489d37a1bf139f519feaeef28d072a41a78e69a74" +checksum = "74a4c2488d058326466e086a43f5d4ea448241a8d0975e3eb0642c0828be1eb3" dependencies = [ "wast", ] @@ -15240,7 +15385,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.21", + "rustix 0.38.25", ] [[package]] @@ -15350,6 +15495,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -15380,6 +15534,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -15392,6 +15561,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.33.0" @@ -15410,6 +15585,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.33.0" @@ -15428,6 +15609,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.33.0" @@ -15446,6 +15633,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.33.0" @@ -15464,6 +15657,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -15476,6 +15675,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.33.0" @@ -15494,6 +15699,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.17" @@ -15689,7 +15900,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.39", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4a35351232f..cc63134cc6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.0.2" +version = "1.0.3" authors = ["Gear Technologies"] edition = "2021" license = "GPL-3.0" @@ -14,6 +14,7 @@ default-members = ["node/cli"] members = [ "common", "common/codegen", + "common/numerated", "core", "core-backend", "core-processor", @@ -26,6 +27,7 @@ members = [ "examples/autoreply", "examples/calc-hash", "examples/custom", + "examples/delayed-reservation-sender", "examples/compose", "examples/constructor", "examples/delayed-sender", @@ -57,9 +59,10 @@ members = [ "examples/rwlock", "examples/send-from-reservation", "examples/signal-entry", + "examples/signal-wait", "examples/state-rollback", "examples/sync-duplicate", - "examples/sys-calls", + "examples/syscalls", "examples/syscall-error", "examples/vec", "examples/wait", @@ -73,6 +76,7 @@ members = [ "gclient", "gcore", "gmeta", + "gmeta/codegen", "gsdk", "gsdk/codegen", "gsdk/api-gen", @@ -98,7 +102,9 @@ base64 = "0.21.5" byteorder = { version = "1.5.0", default-features = false } blake2-rfc = { version = "0.2.18", default-features = false } bs58 = { version = "0.5.0", default-features = false } -clap = { version = "4.4.7" } +# TODO: upgrade this package ( issue #2694 ) +cargo_metadata = "=0.15.3" +clap = { version = "4.4.10" } codec = { package = "parity-scale-codec", version = "3.6.4", default-features = false } color-eyre = "0.6.2" colored = "2.0.0" @@ -109,6 +115,7 @@ dlmalloc = { git = "https://github.com/gear-tech/dlmalloc-rust.git" } dyn-clonable = "0.9.0" enum-iterator = "1.4.0" env_logger = "0.10" +environmental = "1.1.3" futures = { version = "0.3", default-features = false } futures-timer = "3.0.2" futures-util = "0.3.29" @@ -127,7 +134,7 @@ parking_lot = "0.12.1" path-clean = "1.0.1" primitive-types = { version = "0.12.2", default-features = false } proc-macro2 = { version = "1", default-features = false } -proptest = "1.3.1" +proptest = "1.4.0" quick-xml = "0.28" quote = { version = "1.0.33", default-features = false } rand = { version = "0.8", default-features = false } @@ -157,31 +164,36 @@ static_assertions = "1" subxt = { version = "0.32.1" } subxt-metadata = { version = "0.32.1" } subxt-codegen = { version = "0.32.1" } -syn = "2.0.38" +syn = "2.0.39" thiserror = "1.0.50" -tokio = { version = "1.33.0" } -url = "2.4.1" -wat = "1.0.78" +tokio = { version = "1.34.0" } +url = "2.5.0" +wat = "1.0.81" wabt = "0.10.0" +wasmer = "2.2" +wasmer-cache = "2.2.1" +wasmer-types = "2.2" wasmi = { version = "0.14.0", default-features = false } wasmparser = { package = "wasmparser-nostd", version = "0.100.1", default-features = false } -which = "4.4.0" +which = "4.4.2" winapi = "0.3.9" paste = "1.0" +tempfile = "3.8.1" # Published deps # # fork of `parity-wasm` with sign-ext enabled by default. # -# https://github.com/gear-tech/parity-wasm/tree/v0.45.0-sign-ext -# gear-wasm = "0.45.0" +# https://github.com/gear-tech/parity-wasm/tree/v0.45.1-sign-ext +# gear-wasm = "0.45.1" # # fork of `wasm-instrument` # -# https://github.com/gear-tech/wasm-instrument/tree/v0.2.1-sign-ext -gwasm-instrument = { version = "0.2.1", default-features = false } +# https://github.com/gear-tech/wasm-instrument/tree/v0.2.3-sign-ext +gwasm-instrument = { version = "0.2.3", default-features = false } # Internal deps +numerated = { path = "common/numerated" } authorship = { package = "gear-authorship", path = "node/authorship" } common = { package = "gear-common", path = "common", default-features = false } core-processor = { package = "gear-core-processor", path = "core-processor", default-features = false } @@ -190,10 +202,11 @@ gcore = { path = "gcore" } gcli = { path = "gcli" } gclient = { path = "gclient" } gsdk = { path = "gsdk" } -gstd = { path = "gstd" } +gstd = { path = "gstd", features = [ "nightly" ] } gsys = { path = "gsys" } gtest = { path = "gtest" } gmeta = { path = "gmeta" } +gmeta-codegen = { path = "gmeta/codegen" } gear-authorship = { path = "node/authorship" } gear-core-backend = { path = "core-backend", default-features = false } gear-call-gen = { path = "utils/call-gen" } @@ -219,6 +232,7 @@ gear-wasm-gen = { path = "utils/wasm-gen" } gear-wasm-instrument = { path = "utils/wasm-instrument", default-features = false } junit-common = { path = "utils/junit-common" } actor-system-error = { path = "utils/actor-system-error" } +calc-stack-height = { path = "utils/calc-stack-height" } pallet-gear = { path = "pallets/gear", default-features = false } pallet-gear-debug = { path = "pallets/gear-debug", default-features = false } pallet-gear-gas = { path = "pallets/gas", default-features = false } @@ -240,6 +254,10 @@ testing = { package = "gear-node-testing", path = "node/testing" } vara-runtime = { path = "runtime/vara", default-features = false } wasm-smith = { version = "0.12.21", git = "https://github.com/gear-tech/wasm-tools.git", branch = "gear-stable" } +# Common executors between `sandbox-host` and `calc-stack-height` +sandbox-wasmer = { package = "wasmer", version = "2.2", features = ["singlepass"] } +sandbox-wasmer-types = { package = "wasmer-types", version = "2.2" } + # Substrate deps frame-benchmarking = { version = "4.0.0-dev", git = "https://github.com/gear-tech/substrate.git", branch = "gear-polkadot-v0.9.43-canary-no-sandbox", default-features = false } frame-benchmarking-cli = { version = "4.0.0-dev", git = "https://github.com/gear-tech/substrate.git", branch = "gear-polkadot-v0.9.43-canary-no-sandbox" } @@ -360,7 +378,7 @@ substrate-wasm-builder = { version = "5.0.0-dev", git = "https://github.com/gear try-runtime-cli = { version = "0.10.0-dev", git = "https://github.com/gear-tech/substrate.git", branch = "gear-polkadot-v0.9.43-canary-no-sandbox" } # Examples -test-syscalls = { path = "examples/sys-calls", default-features = false } +test-syscalls = { path = "examples/syscalls", default-features = false } demo-async = { path = "examples/async" } demo-async-custom-entry = { path = "examples/async-custom-entry" } demo-async-init = { path = "examples/async-init" } @@ -371,6 +389,7 @@ demo-calc-hash = { path = "examples/calc-hash" } demo-calc-hash-in-one-block = { path = "examples/calc-hash/in-one-block" } demo-calc-hash-over-blocks = { path = "examples/calc-hash/over-blocks" } demo-custom = { path = "examples/custom" } +demo-delayed-reservation-sender = { path = "examples/delayed-reservation-sender" } demo-compose = { path = "examples/compose" } demo-constructor = { path = "examples/constructor", default-features = false } demo-delayed-sender = { path = "examples/delayed-sender" } @@ -401,6 +420,7 @@ demo-reserve-gas = { path = "examples/reserve-gas", default-features = false } demo-rwlock = { path = "examples/rwlock" } demo-send-from-reservation = { path = "examples/send-from-reservation" } demo-signal-entry = { path = "examples/signal-entry" } +demo-signal-wait = { path = "examples/signal-wait" } demo-state-rollback = { path = "examples/state-rollback" } demo-sync-duplicate = { path = "examples/sync-duplicate" } demo-vec = { path = "examples/vec" } @@ -416,9 +436,8 @@ demo-wat = { path = "examples/wat" } # # TODO: remove these dependencies (from this file?) or add more docs. cfg-if = "1.0.0" # gear-lazy-pages -# TODO: upgrade this package ( issue #2694 ) -cargo_metadata = "=0.15.4" # utils/wasm-builder errno = "0.3" # gear-lazy-pages +nix = "0.26.4" # gear-lazy-pages impl-trait-for-tuples = "0.2.2" # pallets/staking-rewards indexmap = "2.1.0" # utils/weight-diff indicatif = "*" # utils/wasm-gen @@ -426,7 +445,6 @@ keyring = "1.2.1" # gcli libp2p = "=0.50.1" # gcli (same version as sc-consensus) mimalloc = { version = "0.1.39", default-features = false } # node/cli nacl = "0.5.3" # gcli -nix = "0.26.4" # gear-lazy-pages nonempty = "0.8.1" # utils/utils libfuzzer-sys = "0.4" # utils/runtime-fuzzer/fuzz pwasm-utils = "0.19.0" # utils/wasm-builder @@ -440,7 +458,7 @@ thousands = "0.2.0" # util toml = "0.7.8" # utils/wasm-builder tracing = "0.1.40" # utils/node-loder tracing-appender = "0.2" # utils/node-loder -tracing-subscriber = "0.3.16" # utils/node-loder +tracing-subscriber = "0.3.18" # utils/node-loder trybuild = "1" # gstd/codegen wasm-opt = "0.112" # utils/wasm-builder wasmprinter = "0.2" # utils/wasm-gen @@ -449,6 +467,9 @@ fail = "0.5" # gear scale-value = "^0.12" # gsdk heck = "0.4.1" # gsdk-api-gen etc = "0.1.16" # gcli +cargo_toml = "0.15.3" # crates-io +crates-io = "0.37.0" # crates-io +curl = "0.4.44" # crates-io scale-decode = "0.9.0" # gsdk directories = "5.0.1" # utils/key-finder num-traits = { version = "0.2", default-features = false } # gear-core diff --git a/README.md b/README.md index abbdd0a2f89..35428c73f98 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@

-

-Gear is a Substrate-based smart-contract platform allowing anyone to run dApp in a few minutes. -

+

+Gear Protocol is a Substrate-based solution for developers, enabling anyone to run a dApp in just a few minutes. +

# - +
[![CI][c1]][c2] @@ -35,19 +35,19 @@ Gear is a Substrate-based smart-contract platform allowing anyone to run dApp in [l2]: https://github.com/gear-tech/gear/blob/master/LICENSE
-

Hit the :star: button to keep up with our daily progress!

+

Hit the :star: button to keep up with daily protocol's development progress!

# Getting Started -1. :open_hands: The easiest way to get started with Gear is by using the demo environment at [https://idea.gear-tech.io](https://idea.gear-tech.io). +1. :open_hands: The easiest way to get started with Gear Protocol is by using the demo environment at [https://idea.gear-tech.io](https://idea.gear-tech.io). -2. :wrench: Follow the instructions from ["Getting started in 5 minutes"](https://wiki.gear-tech.io/docs/getting-started-in-5-minutes/) to compile the Rust test smart contract to Wasm. :running: Upload and run smart contract in Vara Network Testnet via [Gear Idea](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Ftestnet.vara-network.io), send a message to a program, check how it is going. +2. :wrench: Follow the instructions from ["Getting started in 5 minutes"](https://wiki.gear-tech.io/docs/getting-started-in-5-minutes/) to compile the Rust test program to Wasm. :running: Upload and run the program on the Vara Network Testnet via [Gear Idea](https://idea.gear-tech.io/programs?node=wss%3A%2F%2Ftestnet.vara-network.io), send a message to a program, check how it is going. -3. :scroll: Write your own smart contract or choose one from the available comprehensive [examples](https://github.com/gear-foundation/dapps) for a convenient and swift onboarding process. +3. :scroll: Write your own program or use one from the comprehensive [examples library](https://github.com/gear-foundation/dapps) as a basis for a convenient and swift onboarding process. 4. :computer: Download and run your Gear node locally or create your own multi-node local testnet. -5. :dolphin: Deep dive to the [Smart Contracts section](https://wiki.gear-tech.io/docs/developing-contracts/introduction) of the Gear Wiki for more details about how to implement and run your dApp in Gear. +5. :dolphin: Deep dive to the [Examples section](https://wiki.gear-tech.io/docs/developing-contracts/introduction) of the Gear Wiki for more details about how to implement and run your dApp with Gear. ## Run Gear Node @@ -66,19 +66,25 @@ Gear node can run in a single Dev Net mode or you can create a Multi-Node local - **Linux x64**: [gear-nightly-x86_64-unknown-linux-gnu.tar.xz](https://get.gear.rs/gear-nightly-x86_64-unknown-linux-gnu.tar.xz) - **Windows x64**: [gear-nightly-x86_64-pc-windows-msvc.zip](https://get.gear.rs/gear-nightly-x86_64-pc-windows-msvc.zip) -2. Run Gear node without special arguments to get a node connected to the testnet: +2. Run Gear node without special arguments to get a node connected to the test network: ```bash gear ``` -3. One may run a local node in development mode for testing purposes. This node will not be connected to any external network. Use `--dev` argument for running the node locally and storing the state in temporary storage: +3. Connect to the Vara network: + + ```bash + gear --chain=vara + ``` + +4. One may run a local node in development mode for testing purposes. This node will not be connected to any external network. Use `--dev` argument for running the node locally and storing the state in temporary storage: ```bash gear --dev ``` -4. Get more info about usage details, flags, available options and subcommands: +5. Get more info about usage details, flags, available options and subcommands: ```bash gear --help @@ -86,39 +92,41 @@ Gear node can run in a single Dev Net mode or you can create a Multi-Node local ## Implement and run your own blockchain application -1. Visit the [Gear Wiki](https://wiki.gear-tech.io/docs/examples/prerequisites) to explore dApp examples in action and gain a deeper understanding of their functionalities. Write your own smart contract or take one from the available templates. Adapt a template in accordance with your business needs. +1. Visit the [Gear Wiki](https://wiki.gear-tech.io/docs/examples/prerequisites) to explore dApp examples in action and gain a deeper understanding of their functionalities. Write your own program or take one from the available templates. Adapt a template in accordance with your business needs. -2. Test your smart contract off-chain, test it on-chain using a local node, then upload to Gear-powered network via [Gear Idea](https://idea.gear-tech.io/). +2. Test your program off-chain, test it on-chain using a local node, then upload to Gear-powered network via [Gear Idea](https://idea.gear-tech.io/). -3. Implement frontend applications that interact with your smart contracts using [JS API](https://github.com/gear-tech/gear-js/tree/main/api). React application examples are available [here](https://github.com/gear-foundation/dapps/tree/master/frontend). +3. Implement frontend application that interacts with your program using [JS API](https://github.com/gear-tech/gear-js/tree/main/api). React application examples are available [here](https://github.com/gear-foundation/dapps/tree/master/frontend). -# Gear components +# Gear Protocol components -* [core](https://github.com/gear-tech/gear/tree/master/core) - Gear engine for distributed computing core components. +* [core](https://github.com/gear-tech/gear/tree/master/core) - engine for distributed computing core components. -* [node](https://github.com/gear-tech/gear/tree/master/node) - Gear substrate-based node, ready for hacking :rocket:. +* [node](https://github.com/gear-tech/gear/tree/master/node) - substrate-based node, ready for hacking :rocket:. -* [gstd](https://github.com/gear-tech/gear/tree/master/gstd) - Standard library for Gear smart contracts. +* [gstd](https://github.com/gear-tech/gear/tree/master/gstd) - standard library for implementing programs with Gear Protocol. -* [gear-js](https://github.com/gear-tech/gear-js/tree/main/api) - JSON-RPC API of Gear backend. +* [gtest](https://github.com/gear-tech/gear/tree/master/gtest) - fast and lightweight tool for debugging program logic. + +* [gclient](https://github.com/gear-tech/gear/tree/master/gclient) - a tool for testing programs with a real blockchain network. -* [examples](https://github.com/gear-foundation/dapps) - Smart contract examples. +* [gear-js](https://github.com/gear-tech/gear-js/tree/main/api) - JSON-RPC API of Gear backend. -Go to https://docs.gear.rs to dive into the documentation on Gear crates. +Go to https://docs.gear.rs to dive into the documentation on Gear Protocol crates. -# What does Gear do? +# What does Gear Protocol do?

-
Gear provides the easiest and most cost-effective way
to run WebAssembly programs (smart-contracts) compiled from
many popular languages, such as Rust, C/C++ and more. +
Gear Protocol provides the easiest and most cost-effective way
to run Wasm programs compiled from
many popular languages, such as Rust, C/C++ and more.

-
Gear ensures very minimal, intuitive, and sufficient API
for running both newly written and existing programs
on multiple networks without the need to rewrite them. +
It ensures very minimal, intuitive, and sufficient API
for running both newly written and existing programs
on multiple Gear-powered networks without the need to rewrite them.

-
Smart Contracts are stored in the blockchain’s state
and are invoked preserving their state upon request. +
Programs are stored in the blockchain’s state
and are invoked preserving their state upon request.

-
Gear facilitates a seamless transition to Web3,
enabling the operation of dApps, microservices, middleware, and open APIs. +
Gear Protocol facilitates a seamless transition to Web3,
enabling the operation of dApps, microservices, middleware, and open APIs.

### :fire: Key features @@ -126,24 +134,24 @@ Go to https://docs.gear.rs to dive into the documentation on Gear crates. - Programs run in Wasm VM (near-native code execution speed) - **Unique** :crown: : Parallelizable architecture (even greater speed) - **Unique** :crown: : Actor model for message-passing communications - secure, effective, clear - - dApp in minutes using Gear libraries + - **Unique** :crown: : Continued messaging automation, payless transactions, and other features enable the implementation of truly on-chain user-friendly dApps. + - **Unique** :crown: : dApp in minutes using Gear Protocol's libraries - Based on Substrate ### Main capabilities -Gear enables anyone to create and run any custom-logic dApp and is a go-to solution for the following types of applications: - - **Run dApps** that support business logic of any project in the **decentralized Gear network** (such as the [Vara Network](https://vara-network.io/)). Upload programs to the network and interact with them. - - Being a **Polkadot parachain**, Gear establishes cross-chain communications between other blockchains, allowing anyone to run a dApp in the Polkadot network in a very **cost-less** manner. - - Join Substrate-supported blockchains in any other platform outside Polkadot. - - A standalone instance running microservices, middleware, open API and more + - Gear Protocol enables anyone to create and run a custom-logic **decentralized programs**. + - Programs can support business logic of any other projects running in the **Gear-powered network** (such as the [Vara Network](https://vara-network.io/)) and interact with them. + - Establish cross-chain communications between other Substrate-supported blockchains, allowing anyone to run a dApp in the Dotsama ecosystem in a very **cost-less** manner. + - A Gear node can run as a standalone instance running microservices, middleware, open API and more. - # Why? +# Why? The blockchain technology launched a rapid transition from centralized, server-based internet (Web2) to decentralized, distributed one (Web3). -Web3 introduces a new type of decentralized applications (dApps) that enable the existence of DeFi, DEX, Decentralized marketplaces, NFTs, Creators and Social Tokens. +Web3 introduces a new type of applications (dApps) that enable the existence of decentralized Gaming, DeFi, DEX, Decentralized marketplaces, NFTs, Creators and Social Tokens. -Smart Contract is an equivalent of a microservice which is stored on the blockchain network and is the essential building block of a decentralized application. +Programs running on the blockchain network can serve as the equivalent of microservices, which are the essential building blocks of decentralized applications. Modern blockchains solve many issues of the older blockchain networks, such as: - Lack of scalability, low transaction speed, high transaction costs @@ -151,23 +159,17 @@ Modern blockchains solve many issues of the older blockchain networks, such as: - Complex and inefficient native consensus protocols - Absence of intercommunication tools -But still have room for improvements due to: - - Fixated, rigid native consensus protocols +But the room for improvements remains related to: + - Optimisation of the user experience when using Web3 applications - Lack of interoperability with other networks -To resolve the interoperability issue, Parity technologies focused on creating a technology that connects every other blockchain: - - Polkadot - a blockchain of blockchains. Provides a “relay chain” (the primary blockchain) that enables “parachains” (functional blockchains) to be deployed on top of it. All parachains are interconnected, creating a massive network of multifunctional blockchain services. - - Substrate - a modular framework that allows to create custom-built blockchains with consensus mechanism, core functionality and security out of the box. - -Building a blockchain with Substrate allows it to be deployed on any compatible relay chain such as Polkadot and Kusama. Substrate serves as a layer of communication between the relay chain and the parachain. - # How does it work? -The internal flow of Gear: +The internal flow of Gear Protocol: Snow -Refer to the technical paper for some insights about how Gear works internally. +Refer to the technical paper for some insights about how it works internally. # Performance @@ -180,7 +182,7 @@ Here are some features in progress or planned: https://github.com/gear-tech/gear # License -Gear is licensed under [GPL v3.0 with a classpath linking exception](LICENSE). +Gear Protocol is licensed under [GPL v3.0 with a classpath linking exception](LICENSE). ## diff --git a/common/numerated/Cargo.toml b/common/numerated/Cargo.toml new file mode 100644 index 00000000000..994f9f7813f --- /dev/null +++ b/common/numerated/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "numerated" +description = "A library for working with intervals and sets of numerated values" +keywords = ["gear", "tree", "interval", "numerated", "no-std", "math"] +categories = ["mathematics", "no-std"] +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +num-traits.workspace = true +derive_more.workspace = true +scale-info = { workspace = true, features = ["derive"] } +parity-scale-codec = { workspace = true, features = ["derive"] } +log = { workspace = true, optional = true } + +[dev-dependencies] +env_logger.workspace = true +proptest.workspace = true +log.workspace = true + +[features] +mock = ["log"] diff --git a/common/numerated/src/interval.rs b/common/numerated/src/interval.rs new file mode 100644 index 00000000000..8a5d7a20dd7 --- /dev/null +++ b/common/numerated/src/interval.rs @@ -0,0 +1,505 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! [NonEmptyInterval], [Interval] implementations. + +use core::{ + fmt::{self, Debug, Display, Formatter}, + ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, +}; +use num_traits::{ + bounds::{LowerBounded, UpperBounded}, + CheckedAdd, One, Zero, +}; + +use crate::{ + numerated::{BoundValue, Numerated}, + Bound, +}; + +/// Describes not empty interval start..=end. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct NonEmptyInterval { + start: T, + end: T, +} + +/// Describes interval start..=end, where end can be None, +/// which means that interval is empty. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Interval { + start: T, + end: Option, +} + +impl From> for (T, T) { + fn from(interval: NonEmptyInterval) -> (T, T) { + (interval.start, interval.end) + } +} + +impl From> for RangeInclusive { + fn from(interval: NonEmptyInterval) -> Self { + interval.start..=interval.end + } +} + +impl NonEmptyInterval { + /// Creates new interval start..=end with checks only in debug mode. + /// # Safety + /// Unsafe, because allows to create invalid interval. + /// Safe, when start ≤ end. + #[track_caller] + pub unsafe fn new_unchecked(start: T, end: T) -> Self { + debug_assert!(start <= end); + Self { start, end } + } + + /// Creates new interval start..=end if start ≤ end, else returns None. + pub fn new(start: T, end: T) -> Option { + (start <= end).then_some(Self { start, end }) + } + + /// Interval start (the smallest value inside interval) + pub fn start(&self) -> T { + self.start + } + + /// Interval end (the biggest value inside interval) + pub fn end(&self) -> T { + self.end + } + + /// Converts to [Interval], which implements iterator. + pub fn iter(&self) -> Interval { + (*self).into() + } + + /// Into (start, end) + pub fn into_inner(self) -> (T, T) { + self.into() + } +} + +/// Error which occurs when trying to convert empty [Interval] into [NonEmptyInterval]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IntoNonEmptyIntervalError; + +impl TryFrom<&Interval> for NonEmptyInterval { + type Error = IntoNonEmptyIntervalError; + + fn try_from(interval: &Interval) -> Result { + interval + .end + .map(|end| unsafe { + // Guaranteed by `Self` that start ≤ end + NonEmptyInterval::new_unchecked(interval.start, end) + }) + .ok_or(IntoNonEmptyIntervalError) + } +} + +impl TryFrom> for NonEmptyInterval { + type Error = IntoNonEmptyIntervalError; + + fn try_from(interval: Interval) -> Result { + TryFrom::try_from(&interval) + } +} + +impl TryFrom> for RangeInclusive { + type Error = IntoNonEmptyIntervalError; + + fn try_from(interval: Interval) -> Result { + NonEmptyInterval::try_from(interval).map(Into::into) + } +} + +impl Interval { + /// Creates new interval start..end if start ≤ end, else returns None. + /// If start == end, then returns empty interval. + pub fn new, E: Into>(start: S, end: E) -> Option { + Self::try_from((start, end)).ok() + } + /// Returns interval start. + /// - if interval is empty, then returns any existed `T` point, + /// which user set when creates this interval. + /// - if interval is not empty, then returns the smallest point inside interval. + pub fn start(&self) -> T { + self.start + } + /// Returns whether interval is empty. + pub fn is_empty(&self) -> bool { + self.end.is_none() + } + /// Tries to convert into (start, end). + pub fn into_inner(self) -> Option<(T, T)> { + NonEmptyInterval::try_from(self).ok().map(Into::into) + } + /// Tries to convert into range inclusive. + pub fn into_range_inclusive(self) -> Option> { + RangeInclusive::try_from(self).ok() + } +} + +impl From> for Interval { + fn from(interval: NonEmptyInterval) -> Self { + Self { + start: interval.start, + end: Some(interval.end), + } + } +} + +impl Iterator for Interval { + type Item = T; + + fn next(&mut self) -> Option { + if let Some((start, end)) = self.into_inner() { + if start == end { + self.end = None; + Some(start) + } else { + // Guaranteed by `Self` + debug_assert!(start < end); + + let result = start; + let start = start.inc_if_lt(end).unwrap_or_else(|| { + unreachable!("`T: Numerated` impl error: for each s: T, e: T, e > s ⇔ s.inc_if_lt(e) == Some(_)") + }); + self.start = start; + Some(result) + } + } else { + None + } + } +} + +impl From for Interval { + fn from(point: T) -> Self { + unsafe { + // Safe cause `point` == `point` + NonEmptyInterval::new_unchecked(point, point).into() + } + } +} + +impl From<&T> for Interval { + fn from(point: &T) -> Self { + Self::from(*point) + } +} + +impl From> for Interval { + fn from(range: RangeToInclusive) -> Self { + NonEmptyInterval::new(T::min_value(), range.end) + .unwrap_or_else(|| { + unreachable!( + "`T: LowerBounded` impl error: for any x: T must be T::min_value() ≤ x" + ) + }) + .into() + } +} + +impl> From> for Interval { + fn from(range: RangeFrom) -> Self { + let start: T::B = range.start.into(); + match start.unbound() { + BoundValue::Value(start) => NonEmptyInterval::new(start, T::max_value()) + .unwrap_or_else(|| { + unreachable!( + "`T: UpperBounded` impl error: for any x: T must be x ≤ T::max_value()" + ) + }) + .into(), + BoundValue::Upper(start) => Self { start, end: None }, + } + } +} + +impl From for Interval { + fn from(_: RangeFull) -> Self { + NonEmptyInterval::new(T::min_value(), T::max_value()).unwrap_or_else(|| { + unreachable!("`T: UpperBounded + LowerBounded` impl error: must be T::min_value() ≤ T::max_value()") + }).into() + } +} + +impl> From> for Interval { + fn from(range: RangeTo) -> Self { + let end: T::B = range.end.into(); + let start = T::min_value(); + match end.unbound() { + BoundValue::Value(end) => { + debug_assert!(end >= T::min_value()); + let end = end.dec_if_gt(T::min_value()); + Self { start, end } + } + BoundValue::Upper(end) => Self { + start, + end: Some(end), + }, + } + } +} + +/// Error, which occurs, when trying to convert `start` > `end` into `Interval`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IntoIntervalError; + +impl, E: Into> TryFrom<(S, E)> for Interval { + type Error = IntoIntervalError; + + fn try_from((start, end): (S, E)) -> Result { + use BoundValue::*; + + let start: T::B = start.into(); + let end: T::B = end.into(); + + match (start.unbound(), end.unbound()) { + (Upper(start), Upper(_)) => Ok(Self { start, end: None }), + (Upper(_), Value(_)) => Err(IntoIntervalError), + (Value(start), Upper(end)) => Ok(Self { + start, + end: Some(end), + }), + (Value(start), Value(end)) => { + if let Some(end) = end.dec_if_gt(start) { + debug_assert!(start <= end); + Ok(Self { + start, + end: Some(end), + }) + } else if start == end { + Ok(Self { start, end: None }) + } else { + Err(IntoIntervalError) + } + } + } + } +} + +impl> TryFrom> for Interval { + type Error = IntoIntervalError; + + fn try_from(range: Range) -> Result { + Self::try_from((range.start, range.end)) + } +} + +impl TryFrom> for Interval { + type Error = IntoIntervalError; + + fn try_from(range: RangeInclusive) -> Result { + let (start, end) = range.into_inner(); + NonEmptyInterval::new(start, end) + .ok_or(IntoIntervalError) + .map(Into::into) + } +} + +impl NonEmptyInterval { + /// Returns amount of elements in interval in `T::N` if it's possible. + /// None means that interval size is bigger, than `T::N::max_value()`. + pub fn raw_size(&self) -> Option { + let (start, end) = self.into_inner(); + + // guarantied by `Self` + debug_assert!(start <= end); + + let distance = end.distance(start).unwrap_or_else(|| { + unreachable!( + "`T: Numerated` impl error: for any s: T, e: T, e ≥ s ⇔ e.distance(s) == Some(_)" + ) + }); + + distance.checked_add(&T::N::one()) + } +} + +impl NonEmptyInterval { + /// Returns size of interval in `T` if it's possible. + /// - If interval size is bigger than `T` possible elements amount, then returns `None`. + /// - If interval size is equal to some `T::N`, then returns `T` of corresponding numeration: + /// ````ignore + /// { T::N::zero() -> T::min_value(), T::N::one() -> T::min_value() + 1, ... } + /// ```` + pub fn size(&self) -> Option { + let raw_size = self.raw_size()?; + // It's ok if `add_if_enclosed_by` returns `None`, + // because `raw_size` can be bigger than `T` possible elements amount, + // in that case `size` must return `None`. + T::min_value().add_if_enclosed_by(raw_size, T::max_value()) + } +} + +impl Interval { + /// Returns amount of elements in interval in `T::N` if it's possible. + /// None means that interval size is bigger, than `T::N::max_value()`. + /// If interval is empty, then returns `Some(T::min_value())`, + /// which is actually equal to `Some(T::zero())`. + pub fn raw_size(&self) -> Option { + let Ok(interval) = NonEmptyInterval::try_from(self) else { + return Some(T::N::min_value()); + }; + + interval.raw_size() + } +} + +impl Interval { + /// Returns size of interval in `T` if it's possible. + /// - if interval is empty, then returns `Some(T::min_value())`. + /// - if interval size is bigger than `T` possible elements amount, then returns `None`. + /// - if interval size is equal to some `T::N`, then returns `T` of corresponding numeration. + pub fn size(&self) -> Option { + let Ok(interval) = NonEmptyInterval::try_from(self) else { + return Some(T::min_value()); + }; + + interval.size() + } +} + +impl Interval { + /// Returns interval [`start`..`start` + `count`) if it's possible. + /// Size of result interval is equal to `count`. + /// - if `count` is None, then it is supposed, that interval size must be `T::N::max_value()`. + /// - if `start` + `count` - 1 is out of `T`, then returns `None`. + /// - if `count` is zero, then returns empty interval. + pub fn count_from, C: Into>>(start: S, count: C) -> Option { + use BoundValue::*; + let start: T::B = start.into(); + let count: Option = count.into(); + match (start.unbound(), count) { + (Value(start), Some(c)) | (Upper(start), Some(c)) if c == T::N::zero() => { + Some(Self { start, end: None }) + } + (Upper(_), _) => None, + (Value(s), c) => { + // subtraction is safe, because c != 0 + let c = c.map(|c| c - T::N::one()).unwrap_or(T::N::max_value()); + s.add_if_enclosed_by(c, T::max_value()) + .map(|e| NonEmptyInterval::new(s, e).unwrap_or_else(|| { + unreachable!("`T: Numerated` impl error: for each s: T, c: T::N ⇔ s.add_if_between(c, _) ≥ s") + }).into()) + } + } + } +} + +impl Debug for NonEmptyInterval { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "({:?}..={:?})", self.start, self.end) + } +} + +impl Display for NonEmptyInterval { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "({}..={})", self.start, self.end) + } +} + +impl Debug for Interval { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "({:?}..={:?})", self.start, self.end) + } +} + +impl Display for Interval { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(end) = self.end.as_ref() { + write!(f, "({}..={})", self.start, end) + } else { + write!(f, "∅({})", self.start) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn size() { + assert_eq!(Interval::::try_from(11..111).unwrap().size(), Some(100),); + assert_eq!(Interval::::try_from(..1).unwrap().size(), Some(1),); + assert_eq!(Interval::::from(..=1).size(), Some(2)); + assert_eq!(Interval::::from(1..).size(), Some(255)); + assert_eq!(Interval::::from(0..).size(), None); + assert_eq!(Interval::::from(..).size(), None); + assert_eq!(Interval::::try_from(1..1).unwrap().size(), Some(0)); + + assert_eq!( + Interval::::try_from(11..111).unwrap().raw_size(), + Some(100), + ); + assert_eq!(Interval::::try_from(..1).unwrap().raw_size(), Some(1),); + assert_eq!(Interval::::from(..=1).raw_size(), Some(2)); + assert_eq!(Interval::::from(1..).raw_size(), Some(255)); + assert_eq!(Interval::::from(0..).raw_size(), None); + assert_eq!(Interval::::from(..).raw_size(), None); + assert_eq!(Interval::::try_from(1..1).unwrap().raw_size(), Some(0)); + + assert_eq!(Interval::::try_from(-1..99).unwrap().size(), Some(-28)); // corresponds to 100 numeration + assert_eq!(Interval::::try_from(..1).unwrap().size(), Some(1)); // corresponds to 129 numeration + assert_eq!(Interval::::from(..=1).size(), Some(2)); // corresponds to 130 numeration + assert_eq!(Interval::::from(1..).size(), Some(-1)); // corresponds to 127 numeration + assert_eq!(Interval::::from(0..).size(), Some(0)); // corresponds to 128 numeration + assert_eq!(Interval::::from(..).size(), None); // corresponds to 256 numeration + assert_eq!(Interval::::try_from(1..1).unwrap().size(), Some(-128)); // corresponds to 0 numeration + + assert_eq!( + Interval::::try_from(-1..99).unwrap().raw_size(), + Some(100) + ); + assert_eq!(Interval::::try_from(..1).unwrap().raw_size(), Some(129)); + assert_eq!(Interval::::from(..=1).raw_size(), Some(130)); + assert_eq!(Interval::::from(1..).raw_size(), Some(127)); + assert_eq!(Interval::::from(0..).raw_size(), Some(128)); + assert_eq!(Interval::::from(..).raw_size(), None); + assert_eq!(Interval::::try_from(1..1).unwrap().raw_size(), Some(0)); + } + + #[test] + fn count_from() { + assert_eq!( + Interval::::count_from(0, 100).and_then(Interval::into_range_inclusive), + Some(0..=99) + ); + assert_eq!( + Interval::::count_from(0, 255).and_then(Interval::into_range_inclusive), + Some(0..=254) + ); + assert_eq!( + Interval::::count_from(0, None).and_then(Interval::into_range_inclusive), + Some(0..=255) + ); + assert_eq!( + Interval::::count_from(1, 255).and_then(Interval::into_range_inclusive), + Some(1..=255) + ); + + assert!(Interval::::count_from(1, 0).unwrap().is_empty()); + assert_eq!(Interval::::count_from(1, None), None); + assert_eq!(Interval::::count_from(2, 255), None); + } +} diff --git a/common/numerated/src/lib.rs b/common/numerated/src/lib.rs new file mode 100644 index 00000000000..8b1e5633498 --- /dev/null +++ b/common/numerated/src/lib.rs @@ -0,0 +1,46 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Crate for working with [Numerated] types and their sets: [Interval], [NonEmptyInterval] and [IntervalsTree]. + +#![no_std] +#![deny(missing_docs)] + +extern crate alloc; + +mod interval; +mod numerated; +mod tree; + +pub use crate::{ + interval::{Interval, IntoIntervalError, NonEmptyInterval}, + numerated::{Bound, BoundValue, Numerated}, + tree::{IntervalsTree, VoidsIterator}, +}; + +pub use num_traits::{ + self, + bounds::{LowerBounded, UpperBounded}, + CheckedAdd, One, Zero, +}; + +#[cfg(any(feature = "mock", test))] +pub mod mock; + +#[cfg(test)] +mod tests; diff --git a/common/numerated/src/mock.rs b/common/numerated/src/mock.rs new file mode 100644 index 00000000000..f134d73826f --- /dev/null +++ b/common/numerated/src/mock.rs @@ -0,0 +1,147 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Mock for crate property testing and also can be used in other crates for their numerated types impls. + +use crate::{Bound, BoundValue, Interval, IntervalsTree, NonEmptyInterval, Numerated, One, Zero}; +use alloc::{collections::BTreeSet, fmt::Debug, vec::Vec}; + +/// Mock function for any [Numerated] implementation testing. +pub fn test_numerated(x: T, y: T) +where + T: Numerated + Debug, + T::N: Debug, +{ + assert_eq!(x.add_if_enclosed_by(T::N::one(), y), x.inc_if_lt(y)); + assert_eq!(x.sub_if_enclosed_by(T::N::one(), y), x.dec_if_gt(y)); + assert_eq!(y.add_if_enclosed_by(T::N::one(), x), y.inc_if_lt(x)); + assert_eq!(y.sub_if_enclosed_by(T::N::one(), x), y.dec_if_gt(x)); + + assert_eq!(x.add_if_enclosed_by(T::N::zero(), y), Some(x)); + assert_eq!(x.sub_if_enclosed_by(T::N::zero(), y), Some(x)); + assert_eq!(y.add_if_enclosed_by(T::N::zero(), x), Some(y)); + assert_eq!(y.sub_if_enclosed_by(T::N::zero(), x), Some(y)); + + let (x, y) = (x.min(y), x.max(y)); + if x == y { + assert_eq!(x.inc_if_lt(y), None); + assert_eq!(x.dec_if_gt(y), None); + assert_eq!(x.distance(y), Some(T::N::zero())); + } else { + assert!(x.inc_if_lt(y).is_some()); + assert!(x.dec_if_gt(y).is_none()); + assert!(y.inc_if_lt(y).is_none()); + assert!(y.dec_if_gt(x).is_some()); + assert!(x.distance(y).is_none()); + let d = y.distance(x).unwrap(); + assert_eq!(x.add_if_enclosed_by(d, y), Some(y)); + assert_eq!(y.sub_if_enclosed_by(d, x), Some(x)); + } +} + +/// [Interval] testing action. +#[derive(Debug)] +pub enum IntervalAction { + /// Try to create interval from correct start..end. + Correct(T::B, T::B), + /// Try to create interval from incorrect start..end. + Incorrect(T::B, T::B), +} + +/// Mock function for [Interval] testing for any [Numerated] implementation. +pub fn test_interval(action: IntervalAction) +where + T: Numerated + Debug, + T::B: Debug, +{ + log::debug!("{:?}", action); + match action { + IntervalAction::Incorrect(start, end) => { + assert!(Interval::::new(start, end).is_none()); + assert!(Interval::::try_from(start..end).is_err()); + assert!(Interval::::try_from((start, end)).is_err()); + } + IntervalAction::Correct(start, end) => { + let i = Interval::::new(start, end).unwrap(); + if start.get() == end.get() { + assert!(i.is_empty()); + assert_eq!(i.into_range_inclusive(), None); + assert_eq!(i.into_inner(), None); + assert_eq!(NonEmptyInterval::try_from(i).ok(), None); + } else { + assert_eq!(i.start(), start.get().unwrap()); + assert!(!i.is_empty()); + let i = NonEmptyInterval::try_from(i).unwrap(); + match end.unbound() { + BoundValue::Value(e) => assert_eq!(i.end(), e.dec_if_gt(i.start()).unwrap()), + BoundValue::Upper(e) => assert_eq!(i.end(), e), + } + } + } + } +} + +/// [IntervalsTree] testing action. +#[derive(Debug)] +pub enum TreeAction { + /// Inserts interval into tree action. + Insert(Interval), + /// Removes interval from tree action. + Remove(Interval), + /// Check voids iterator. + Voids(Interval), + /// Check and not iterator. + AndNotIterator(BTreeSet), +} + +fn btree_set_voids(set: &BTreeSet, interval: Interval) -> BTreeSet { + interval.filter(|p| !set.contains(p)).collect() +} + +/// Mock function for [IntervalsTree] testing for any [Numerated] implementation. +pub fn test_tree(initial: BTreeSet, actions: Vec>) { + let mut tree: IntervalsTree = initial.iter().collect(); + let mut expected: BTreeSet = tree.points_iter().collect(); + assert_eq!(expected, initial); + + for action in actions { + log::debug!("{:?}", action); + match action { + TreeAction::Insert(interval) => { + tree.remove(interval); + interval.for_each(|i| { + expected.remove(&i); + }); + } + TreeAction::Remove(interval) => { + tree.insert(interval); + expected.extend(interval); + } + TreeAction::Voids(interval) => { + let voids: BTreeSet = tree.voids(interval).flat_map(|i| i.iter()).collect(); + assert_eq!(voids, btree_set_voids(&expected, interval)); + } + TreeAction::AndNotIterator(x) => { + let y = x.iter().collect(); + let z: BTreeSet = tree.and_not_iter(&y).flat_map(|i| i.iter()).collect(); + assert_eq!(z, expected.difference(&x).copied().collect()); + } + } + assert_eq!(expected, tree.points_iter().collect()); + } +} diff --git a/common/numerated/src/numerated.rs b/common/numerated/src/numerated.rs new file mode 100644 index 00000000000..df139ee6016 --- /dev/null +++ b/common/numerated/src/numerated.rs @@ -0,0 +1,197 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! [Numerated], [Bound] traits definition and implementation for integer types. + +use num_traits::{bounds::UpperBounded, One, PrimInt, Unsigned}; + +/// Represents a value or upper bound. Can be in two states: +/// - Value: contains value. +/// - Upper: contains max value for `T`. +/// +/// See also trait [Bound]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BoundValue { + /// The bound is a value. Contains `T` value. + Value(T), + /// The bound is an upper bound. Contains `T` max value. + Upper(T), +} + +/// For any type `T`, `Bound` is a type, which has set of values bigger than `T` by one element. +/// - Each value from `T` has unambiguous mapping to `Bound`. +/// - Each value from `Bound`, except one called __upper__, has unambiguous mapping to `T`. +/// - __upper__ value has no mapping to `T`, but can be used to get `T` max value. +/// +/// # Examples +/// 1) For any `T`, which max value can be get by calling some static live time function, +/// `Option`` can be used as `Bound`. `None` is __upper__. Mapping: Some(t) -> t, t -> Some(t). +/// +/// 2) When `inner` field max value is always smaller than `inner` type max value, then we can use this variant: +/// ``` +/// use numerated::{Bound, BoundValue}; +/// +/// /// `inner` is a value from 0 to 99. +/// struct Number { inner: u32 } +/// +/// /// `inner` is a value from 0 to 100. +/// #[derive(Clone, Copy)] +/// struct BoundForNumber { inner: u32 } +/// +/// impl From for BoundForNumber { +/// fn from(t: Number) -> Self { +/// Self { inner: t.inner } +/// } +/// } +/// +/// impl Bound for BoundForNumber { +/// fn unbound(self) -> BoundValue { +/// if self.inner == 100 { +/// BoundValue::Upper(Number { inner: 99 }) +/// } else { +/// BoundValue::Value(Number { inner: self.inner }) +/// } +/// } +/// } +/// ``` +pub trait Bound: From + Copy { + /// Unbound means mapping bound back to value if possible. + /// - In case bound is __upper__, then returns Upper(max), where `max` is `T` max value. + /// - Otherwise returns Value(value). + fn unbound(self) -> BoundValue; + /// Returns `T` if `self` is value, otherwise (self is __upper__) returns `None`. + fn get(self) -> Option { + match self.unbound() { + BoundValue::Value(v) => Some(v), + BoundValue::Upper(_) => None, + } + } +} + +/// Numerated type is a type, which has type for distances between any two values of `Self`, +/// and provide an interface to add/subtract distance to/from value. +/// +/// Default implementation is provided for all integer types: +/// [i8] [u8] [i16] [u16] [i32] [u32] [i64] [u64] [i128] [u128] [isize] [usize]. +pub trait Numerated: Copy + Sized + Ord + Eq { + /// Numerate type: type that describes the distances between two values of `Self`. + type N: PrimInt + Unsigned; + /// Bound type: type for which any value can be mapped to `Self`, + /// and also has __upper__ value, which is bigger than any value of `Self`. + type B: Bound; + /// Adds `num` to `self`, if `self + num` is enclosed by `self` and `other`. + /// + /// # Guaranties + /// - iff `self + num` is enclosed by `self` and `other`, then returns `Some(_)`. + /// - iff `self.add_if_enclosed_by(num, other) == Some(a)`, + /// then `a.sub_if_enclosed_by(num, self) == Some(self)`. + fn add_if_enclosed_by(self, num: Self::N, other: Self) -> Option; + /// Subtracts `num` from `self`, if `self - num` is enclosed by `self` and `other`. + /// + /// # Guaranties + /// - iff `self - num` is enclosed by `self` and `other`, then returns `Some(_)`. + /// - iff `self.sub_if_enclosed_by(num, other) == Some(a)`, + /// then `a.add_if_enclosed_by(num, self) == Some(self)`. + fn sub_if_enclosed_by(self, num: Self::N, other: Self) -> Option; + /// Returns `self - other`, if `self ≥ other`. + /// + /// # Guaranties + /// - iff `self ≥ other`, then returns `Some(_)`. + /// - iff `self == other`, then returns `Some(0)`. + /// - iff `self.distance(other) == Some(a)`, then + /// - `self.sub_if_enclosed_by(a, other) == Some(other)` + /// - `other.add_if_enclosed_by(a, self) == Some(self)` + fn distance(self, other: Self) -> Option; + /// Increments `self`, if `self < other`. + fn inc_if_lt(self, other: Self) -> Option { + self.add_if_enclosed_by(Self::N::one(), other) + } + /// Decrements `self`, if `self` > `other`. + fn dec_if_gt(self, other: Self) -> Option { + self.sub_if_enclosed_by(Self::N::one(), other) + } + /// Returns `true`, if `self` is enclosed by `a` and `b`. + fn enclosed_by(self, a: &Self, b: &Self) -> bool { + self <= *a.max(b) && self >= *a.min(b) + } +} + +impl From for BoundValue { + fn from(value: T) -> Self { + Self::Value(value) + } +} + +impl From> for BoundValue { + fn from(value: Option) -> Self { + match value { + Some(value) => Self::Value(value), + None => Self::Upper(T::max_value()), + } + } +} + +impl Bound for BoundValue { + fn unbound(self) -> BoundValue { + self + } +} + +macro_rules! impl_for_unsigned { + ($($t:ty)*) => ($( + impl Numerated for $t { + type N = $t; + type B = BoundValue<$t>; + fn add_if_enclosed_by(self, num: Self::N, other: Self) -> Option { + self.checked_add(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res)) + } + fn sub_if_enclosed_by(self, num: Self::N, other: Self) -> Option { + self.checked_sub(num).and_then(|res| res.enclosed_by(&self, &other).then_some(res)) + } + fn distance(self, other: Self) -> Option<$t> { + self.checked_sub(other) + } + } + )*) +} + +impl_for_unsigned!(u8 u16 u32 u64 u128 usize); + +macro_rules! impl_for_signed { + ($($s:ty => $u:ty),*) => { + $( + impl Numerated for $s { + type N = $u; + type B = BoundValue<$s>; + fn add_if_enclosed_by(self, num: $u, other: Self) -> Option { + let res = (self as $u).wrapping_add(num) as $s; + res.enclosed_by(&self, &other).then_some(res) + } + fn sub_if_enclosed_by(self, num: Self::N, other: Self) -> Option { + let res = (self as $u).wrapping_sub(num) as $s; + res.enclosed_by(&self, &other).then_some(res) + } + fn distance(self, other: Self) -> Option<$u> { + (self >= other).then_some(self.abs_diff(other)) + } + } + )* + }; +} + +impl_for_signed!(i8 => u8, i16 => u16, i32 => u32, i64 => u64, i128 => u128, isize => usize); diff --git a/common/numerated/src/tests.rs b/common/numerated/src/tests.rs new file mode 100644 index 00000000000..123701885ec --- /dev/null +++ b/common/numerated/src/tests.rs @@ -0,0 +1,90 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Property testing for Numerated, Interval and IntervalsTree. + +use crate::{ + mock::{self, test_interval, test_numerated, IntervalAction, TreeAction}, + BoundValue, Interval, +}; +use alloc::{collections::BTreeSet, vec::Vec}; +use proptest::{ + arbitrary::any, prop_oneof, proptest, strategy::Strategy, test_runner::Config as ProptestConfig, +}; + +fn rand_interval() -> impl Strategy> { + any::() + .prop_flat_map(|start| (start..).prop_map(move |end| (start..=end).try_into().unwrap())) +} + +fn rand_set() -> impl Strategy> { + proptest::collection::btree_set(any::(), 0..1000) +} + +fn tree_actions() -> impl Strategy>> { + let action = prop_oneof![ + rand_interval().prop_map(TreeAction::Insert), + rand_interval().prop_map(TreeAction::Remove), + rand_interval().prop_map(TreeAction::Voids), + rand_set().prop_map(TreeAction::AndNotIterator), + ]; + proptest::collection::vec(action, 10..20) +} + +fn interval_action() -> impl Strategy> { + let start = any::>(); + let end = any::>(); + (start, end).prop_map(|(start, end)| { + let start: BoundValue = start.into(); + let end: BoundValue = end.into(); + match (start, end) { + (_, BoundValue::Upper(_)) => IntervalAction::Correct(start, end), + (BoundValue::Value(s), BoundValue::Value(e)) => { + if s > e { + IntervalAction::Incorrect(start, end) + } else { + IntervalAction::Correct(start, end) + } + } + (BoundValue::Upper(_), BoundValue::Value(_)) => IntervalAction::Incorrect(start, end), + } + }) +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(10_000))] + + #[test] + fn proptest_numerated(x in any::(), y in any::()) { + test_numerated(x, y); + } + + #[test] + fn proptest_interval(action in interval_action()) { + test_interval(action); + } +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(128))] + + #[test] + fn proptest_tree(actions in tree_actions(), initial in rand_set()) { + mock::test_tree(initial, actions); + } +} diff --git a/common/numerated/src/tree.rs b/common/numerated/src/tree.rs new file mode 100644 index 00000000000..b5557141fef --- /dev/null +++ b/common/numerated/src/tree.rs @@ -0,0 +1,866 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! [IntervalsTree] implementation. + +use crate::{Interval, NonEmptyInterval, Numerated}; +use alloc::{collections::BTreeMap, fmt, fmt::Debug, vec::Vec}; +use core::{fmt::Formatter, ops::RangeInclusive}; +use num_traits::{CheckedAdd, Zero}; +use scale_info::{ + scale::{Decode, Encode}, + TypeInfo, +}; + +/// # Non overlapping intervals tree +/// Can be considered as set of points, but with possibility to work with +/// continuous sets of this points (the same as interval) as fast as with points. +/// Insert and remove operations has complexity between `[O(log(n)), O(n)]`, +/// where `n` is amount of intervals in tree. +/// So, even if you insert for example points from [`0u64`] to [`u64::MAX`], +/// then removing all of them or any part of them is as fast as removing one point. +/// +/// # Examples +/// ``` +/// use numerated::IntervalsTree; +/// use std::collections::BTreeSet; +/// use std::ops::RangeInclusive; +/// +/// let mut tree = IntervalsTree::new(); +/// let mut set = BTreeSet::new(); +/// +/// tree.insert(1i32); +/// // now `tree` contains only one interval: [1..=1] +/// set.insert(1i32); +/// // `points_iter()` - is iterator over all points in `tree`. +/// assert_eq!(set, tree.points_iter().collect()); +/// +/// // We can add points from 3 to 100_000 only by one insert operation. +/// // `try` is only for range check, that it has start ≤ end. +/// tree.try_insert(3..=100_000).unwrap(); +/// // now `tree` contains two intervals: [1..=1] and [3..=100_000] +/// set.extend(3..=100_000); +/// // extend complexity is O(n), where n is amount of elements in range. +/// assert_eq!(set, tree.points_iter().collect()); +/// +/// // We can remove points from 1 to 99_000 not inclusive only by one remove operation. +/// // `try` is only for range check, that it has start ≤ end. +/// tree.try_remove(1..99_000).unwrap(); +/// // now `tree` contains two intervals: [99_000..=100_000] +/// (1..99_000).for_each(|i| { set.remove(&i); }); +/// // remove complexity for set is O(n*log(m)), +/// // where `n` is amount of elements in range and `m` size of `tree`. +/// assert_eq!(set, tree.points_iter().collect()); +/// +/// // Can insert or remove all possible points just by one operation: +/// tree.insert(..); +/// tree.remove(..); +/// +/// // Iterate over voids (intervals between intervals in tree): +/// tree.try_insert(1..=3).unwrap(); +/// tree.try_insert(5..=7).unwrap(); +/// let voids = tree.voids(..).map(RangeInclusive::from).collect::>(); +/// assert_eq!(voids, vec![i32::MIN..=0, 4..=4, 8..=i32::MAX]); +/// +/// // AndNot iterator: iterate over intervals from `tree` which are not in `other_tree`. +/// let other_tree: IntervalsTree = [3, 4, 5, 7, 8, 9].iter().collect(); +/// let and_not: Vec<_> = tree.and_not_iter(&other_tree).map(RangeInclusive::from).collect(); +/// assert_eq!(and_not, vec![1..=2, 6..=6]); +/// ``` +/// +/// # Possible panic cases +/// Using `IntervalsTree` for type `T: Numerated` cannot cause panics, +/// if implementation [Numerated], [Copy], [Ord], [Eq] are correct for `T`. +/// In other cases `IntervalsTree` does not guarantees execution without panics. +#[derive(Clone, PartialEq, Eq, TypeInfo, Encode, Decode)] +pub struct IntervalsTree { + inner: BTreeMap, +} + +impl IntervalsTree { + /// Creates new empty intervals tree. + pub const fn new() -> Self { + Self { + inner: BTreeMap::new(), + } + } + /// Returns amount of not empty intervals in tree. + /// + /// Complexity: O(1). + pub fn intervals_amount(&self) -> usize { + self.inner.len() + } +} + +impl IntervalsTree { + /// Returns the biggest point in tree. + pub fn end(&self) -> Option { + self.inner.last_key_value().map(|(_, &e)| e) + } + /// Returns the smallest point in tree. + pub fn start(&self) -> Option { + self.inner.first_key_value().map(|(&s, _)| s) + } +} + +impl Default for IntervalsTree { + fn default() -> Self { + Self::new() + } +} + +impl Debug for IntervalsTree { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{:?}", + self.inner.iter().map(|(s, e)| s..=e).collect::>() + ) + } +} + +impl IntervalsTree { + fn into_start_end>>(interval: I) -> Option<(T, T)> { + Into::>::into(interval).into_inner() + } + + /// Returns iterator over all intervals in tree. + pub fn iter(&self) -> impl Iterator> + '_ { + self.inner.iter().map(|(&start, &end)| unsafe { + // Safe, because `Self` guaranties, that inner contains only `start` ≤ `end`. + NonEmptyInterval::::new_unchecked(start, end) + }) + } + + /// Returns true if for each `p` ∈ `interval` ⇒ `p` ∈ `self`, otherwise returns false. + pub fn contains>>(&self, interval: I) -> bool { + let Some((start, end)) = Self::into_start_end(interval) else { + // Empty interval is always contained. + return true; + }; + if let Some((&s, &e)) = self.inner.range(..=end).next_back() { + if s <= start { + return e >= end; + } + } + false + } + + /// The same as [`Self::contains`], but returns [`I::Error`] if `try_into` [Interval] fails. + pub fn try_contains>>(&self, interval: I) -> Result { + let interval: Interval = interval.try_into()?; + Ok(self.contains(interval)) + } + + /// Insert interval into tree. + /// - if `interval` is empty, then nothing will be inserted. + /// - if `interval` is not empty, then after inserting: for each `p` ∈ `interval` ⇒ `p` ∈ `self`. + /// + /// Complexity: `O(log(n) + m)`, where + /// - `n` is amount of intervals in `self` + /// - `m` is amount of intervals in `self` ⋂ `interval` + pub fn insert>>(&mut self, interval: I) { + let Some((start, end)) = Self::into_start_end(interval) else { + // Empty interval - nothing to insert. + return; + }; + + let Some(last) = self.end() else { + // No other intervals, so can just insert as is. + self.inner.insert(start, end); + return; + }; + + let mut iter = if let Some(point_after_end) = end.inc_if_lt(last) { + // If `end` < `last`, then we must take in account next point after `end`, + // because there can be neighbor interval which must be merged with `interval`. + self.inner.range(..=point_after_end) + } else { + self.inner.range(..=end) + } + .map(|(&s, &e)| (s, e)); + + let Some((right_start, right_end)) = iter.next_back() else { + // No neighbor or intersected intervals, so can just insert as is. + self.inner.insert(start, end); + return; + }; + + if let Some(right_end) = right_end.inc_if_lt(start) { + if right_end == start { + self.inner.insert(right_start, end); + } else { + self.inner.insert(start, end); + } + return; + } else if right_start <= start { + if right_end < end { + self.inner.insert(right_start, end); + } else { + // nothing to do: our interval is completely inside "right interval". + } + return; + } + + let mut left_interval = None; + let mut intervals_to_remove = Vec::new(); + while let Some((s, e)) = iter.next_back() { + if s <= start { + left_interval = Some((s, e)); + break; + } + intervals_to_remove.push(s); + } + for start in intervals_to_remove { + self.inner.remove(&start); + } + + // In this point `start` < `right_start` ≤ `end`, so in any cases it will be removed. + self.inner.remove(&right_start); + + let end = right_end.max(end); + + let Some((left_start, left_end)) = left_interval else { + self.inner.insert(start, end); + return; + }; + + debug_assert!(left_end < right_start); + debug_assert!(left_start <= start); + let Some(left_end) = left_end.inc_if_lt(right_start) else { + unreachable!( + "`T: Numerated` impl error: for each x: T, y: T, x < y ↔ x.inc_if_lt(y) == Some(_)" + ); + }; + + if left_end >= start { + self.inner.insert(left_start, end); + } else { + self.inner.insert(start, end); + } + } + + /// The same as [`Self::insert`], but returns [`I::Error`] if `try_into` [Interval] fails. + pub fn try_insert>>(&mut self, interval: I) -> Result<(), I::Error> { + let interval: Interval = interval.try_into()?; + self.insert(interval); + Ok(()) + } + + /// Remove `interval` from tree. + /// - if `interval` is empty, then nothing will be removed. + /// - if `interval` is not empty, then after removing for each `p` ∈ `interval` ⇒ `p` ∉ `self`. + /// + /// Complexity: `O(log(n) + m)`, where + /// - `n` is amount of intervals in `self` + /// - `m` is amount of intervals in `self` ⋂ `interval` + pub fn remove>>(&mut self, interval: I) { + let Some((start, end)) = Self::into_start_end(interval) else { + // Empty interval - nothing to remove. + return; + }; + + let mut iter = self.inner.range(..=end); + + let Some((&right_start, &right_end)) = iter.next_back() else { + return; + }; + + if right_end < start { + return; + } + + let mut left_interval = None; + let mut intervals_to_remove = Vec::new(); + while let Some((&s, &e)) = iter.next_back() { + if s < start { + if e >= start { + left_interval = Some(s); + } + break; + } + + intervals_to_remove.push(s) + } + + for start in intervals_to_remove { + self.inner.remove(&start); + } + + if let Some(start) = start.dec_if_gt(right_start) { + debug_assert!(start >= right_start); + self.inner.insert(right_start, start); + } else { + debug_assert!(right_start <= end); + self.inner.remove(&right_start); + } + + if let Some(end) = end.inc_if_lt(right_end) { + debug_assert!(end <= right_end); + self.inner.insert(end, right_end); + } else { + debug_assert!(start <= right_end); + } + + if let Some(left_start) = left_interval { + // `left_start` < `start` cause of method it was found. + debug_assert!(left_start < start); + if let Some(start) = start.dec_if_gt(left_start) { + self.inner.insert(left_start, start); + } else { + unreachable!("`T: Numerated` impl error: for each x: T, y: T, x > y ⇔ x.dec_if_gt(y) == Some(_)"); + } + } + } + + /// The same as [`Self::remove`], but returns [`I::Error`] if `try_into` [Interval] fails. + pub fn try_remove>>(&mut self, interval: I) -> Result<(), I::Error> { + let interval: Interval = interval.try_into()?; + self.remove(interval); + Ok(()) + } + + /// Returns iterator over non empty intervals, that consist of points `p: T` + /// where each `p` ∉ `self` and `p` ∈ `interval`. + /// Intervals in iterator are sorted in ascending order. + /// + /// Iterating complexity: `O(log(n) + m)`, where + /// - `n` is amount of intervals in `self` + /// - `m` is amount of intervals in `self` ⋂ `interval` + pub fn voids>>( + &self, + interval: I, + ) -> VoidsIterator> + '_> { + let Some((mut start, end)) = Self::into_start_end(interval) else { + // Empty interval. + return VoidsIterator { inner: None }; + }; + + if let Some((_, &e)) = self.inner.range(..=start).next_back() { + if let Some(e) = e.inc_if_lt(end) { + if e > start { + start = e; + } + } else { + // `interval` is inside of one of `self` interval - no voids. + return VoidsIterator { inner: None }; + } + } + + let iter = self.inner.range(start..=end).map(|(&start, &end)| { + // Safe, because `Self` guaranties, that inner contains only `start` ≤ `end`. + unsafe { NonEmptyInterval::new_unchecked(start, end) } + }); + + // Safe, because we have already checked, that `start` ≤ `end`. + let interval = unsafe { NonEmptyInterval::new_unchecked(start, end) }; + + VoidsIterator { + inner: Some((iter, interval)), + } + } + + /// The same as [`Self::voids`], but returns [`I::Error`] if `try_into` [Interval] fails. + pub fn try_voids>>( + &self, + interval: I, + ) -> Result> + '_>, I::Error> { + let interval: Interval = interval.try_into()?; + Ok(self.voids(interval)) + } + + /// Returns iterator over intervals which consist of points `p: T`, + /// where each `p` ∈ `self` and `p` ∉ `other`. + /// + /// Iterating complexity: `O(n + m)`, where + /// - `n` is amount of intervals in `self` + /// - `m` is amount of intervals in `other` + pub fn and_not_iter<'a: 'b, 'b: 'a>( + &'a self, + other: &'b Self, + ) -> impl Iterator> + '_ { + AndNotIterator { + iter1: self.iter(), + iter2: other.iter(), + interval1: None, + interval2: None, + } + } + + /// Number of points in tree set. + /// + /// Complexity: `O(n)`, where `n` is amount of intervals in `self`. + pub fn points_amount(&self) -> Option { + let mut res = T::N::zero(); + for interval in self.iter() { + res = res.checked_add(&interval.raw_size()?)?; + } + Some(res) + } + + /// Iterator over all points in tree set. + pub fn points_iter(&self) -> impl Iterator + '_ { + self.inner.iter().flat_map(|(&s, &e)| unsafe { + // Safe, because `Self` guaranties, that it contains only `start` ≤ `end` + NonEmptyInterval::new_unchecked(s, e).iter() + }) + } + + /// Convert tree to vector of inclusive ranges. + pub fn to_vec(&self) -> Vec> { + self.iter().map(Into::into).collect() + } +} + +/// Helper struct to iterate over voids in tree. +/// +/// See also [`IntervalsTree::voids`]. +pub struct VoidsIterator>> { + inner: Option<(I, NonEmptyInterval)>, +} + +impl>> Iterator for VoidsIterator { + type Item = NonEmptyInterval; + + fn next(&mut self) -> Option { + let (iter, interval) = self.inner.as_mut()?; + if let Some(next) = iter.next() { + let (start, end) = next.into_inner(); + + // Guaranties by tree: between two intervals always exists void. + debug_assert!(interval.start() < start); + + let void_end = start.dec_if_gt(interval.start()).unwrap_or_else(|| { + unreachable!("`T: Numerated` impl error: for each x: T, y: T, x > y ↔ x.dec_if_gt(y) == Some(_)"); + }); + + let res = NonEmptyInterval::new(interval.start(), void_end); + if res.is_none() { + unreachable!( + "`T: Numerated` impl error: for each x: T, y: T, x > y ↔ x.dec_if_gt(y) ≥ y" + ); + } + + if let Some(new_start) = end.inc_if_lt(interval.end()) { + *interval = NonEmptyInterval::new(new_start, interval.end()).unwrap_or_else(|| { + unreachable!("`T: Numerated` impl error: for each x: T, y: T, x < y ↔ x.inc_if_lt(y) ≤ y"); + }); + } else { + self.inner = None; + } + + res + } else { + let res = Some(*interval); + self.inner = None; + res + } + } +} + +/// Helper struct to iterate over intervals from `tree`, which are not in `other_tree`. +/// +/// See also [`IntervalsTree::and_not_iter`]. +pub struct AndNotIterator< + T: Numerated, + I1: Iterator>, + I2: Iterator>, +> { + iter1: I1, + iter2: I2, + interval1: Option>, + interval2: Option>, +} + +impl< + T: Numerated, + I1: Iterator>, + I2: Iterator>, + > Iterator for AndNotIterator +{ + type Item = NonEmptyInterval; + + fn next(&mut self) -> Option { + loop { + let interval1 = if let Some(interval1) = self.interval1 { + interval1 + } else { + let interval1 = self.iter1.next()?; + self.interval1 = Some(interval1); + interval1 + }; + + let interval2 = if let Some(interval2) = self.interval2 { + interval2 + } else if let Some(interval2) = self.iter2.next() { + interval2 + } else { + self.interval1 = None; + return Some(interval1); + }; + + if interval2.end() < interval1.start() { + self.interval2 = None; + continue; + } + + self.interval2 = Some(interval2); + + if interval1.end() < interval2.start() { + self.interval1 = None; + return Some(interval1); + } else { + if let Some(new_start) = interval2.end().inc_if_lt(interval1.end()) { + self.interval1 = NonEmptyInterval::new(new_start, interval1.end()); + if self.interval1.is_none() { + unreachable!("`T: Numerated` impl error: for each x: T, y: T, x < y ⇔ x.inc_if_lt(y) ≤ y"); + } + } else if interval1.end() == interval2.end() { + self.interval1 = None; + self.interval2 = None; + } else { + self.interval1 = None; + } + + if let Some(new_end) = interval2.start().dec_if_gt(interval1.start()) { + let res = NonEmptyInterval::new(interval1.start(), new_end); + if res.is_none() { + unreachable!("`T: Numerated` impl error: for each x: T, y: T, x > y ⇔ x.dec_if_gt(y) ≥ y"); + } + return res; + } else { + continue; + } + } + } + } +} + +impl>> FromIterator for IntervalsTree { + fn from_iter>(iter: I) -> Self { + let mut tree = Self::new(); + for interval in iter { + tree.insert(interval); + } + tree + } +} + +#[allow(clippy::reversed_empty_ranges)] +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec; + + #[test] + fn insert() { + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=2).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=2]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + assert_eq!(tree.to_vec(), vec![-1..=2, 4..=5]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=2).unwrap(); + tree.try_insert(3..=4).unwrap(); + assert_eq!(tree.to_vec(), vec![-1..=4]); + + let mut tree = IntervalsTree::new(); + tree.insert(1); + tree.insert(2); + assert_eq!(tree.to_vec(), vec![1..=2]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=3).unwrap(); + tree.try_insert(5..=7).unwrap(); + tree.try_insert(2..=6).unwrap(); + tree.try_insert(7..=7).unwrap(); + tree.try_insert(19..=25).unwrap(); + assert_eq!(tree.to_vec(), vec![-1..=7, 19..=25]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=3).unwrap(); + tree.try_insert(10..=14).unwrap(); + tree.try_insert(4..=9).unwrap(); + assert_eq!(tree.to_vec(), vec![-1..=14]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-111..=3).unwrap(); + tree.try_insert(10..=14).unwrap(); + tree.try_insert(3..=10).unwrap(); + assert_eq!(tree.to_vec(), vec![-111..=14]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(i32::MIN..=10).unwrap(); + tree.try_insert(3..=4).unwrap(); + assert_eq!(tree.to_vec(), vec![i32::MIN..=10]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=10).unwrap(); + tree.try_insert(3..=4).unwrap(); + tree.try_insert(5..=6).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=10]); + } + + #[test] + fn remove() { + let mut tree = IntervalsTree::new(); + tree.insert(1); + tree.remove(1); + assert_eq!(tree.to_vec(), vec![]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=2).unwrap(); + tree.try_remove(1..=2).unwrap(); + assert_eq!(tree.to_vec(), vec![]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(-1..=2).unwrap(); + assert_eq!(tree.to_vec(), vec![4..=5]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(4..=5).unwrap(); + assert_eq!(tree.to_vec(), vec![-1..=2]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(2..=4).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=1, 5..=5]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(3..=4).unwrap(); + assert_eq!(tree.to_vec(), vec![-1..=2, 5..=5]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(-1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(-1..=5).unwrap(); + assert_eq!(tree.to_vec(), vec![]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(2..=5).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=1]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(1..=4).unwrap(); + assert_eq!(tree.to_vec(), vec![5..=5]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=2).unwrap(); + tree.try_insert(4..=5).unwrap(); + tree.try_remove(1..=3).unwrap(); + assert_eq!(tree.to_vec(), vec![4..=5]); + + let mut tree = IntervalsTree::new(); + tree.try_insert(1..=10).unwrap(); + assert_eq!(tree.clone().to_vec(), vec![1..=10]); + tree.remove(10..); + assert_eq!(tree.clone().to_vec(), vec![1..=9]); + tree.remove(2..); + assert_eq!(tree.clone().to_vec(), vec![1..=1]); + tree.try_insert(3..6).unwrap(); + assert_eq!(tree.clone().to_vec(), vec![1..=1, 3..=5]); + tree.remove(..=3); + assert_eq!(tree.clone().to_vec(), vec![4..=5]); + tree.try_insert(1..=2).unwrap(); + assert_eq!(tree.clone().to_vec(), vec![1..=2, 4..=5]); + tree.remove(..); + assert_eq!(tree.clone().to_vec(), vec![]); + tree.insert(..); + assert_eq!(tree.clone().to_vec(), vec![i32::MIN..=i32::MAX]); + tree.remove(..=9); + assert_eq!(tree.clone().to_vec(), vec![10..=i32::MAX]); + tree.remove(21..); + assert_eq!(tree.clone().to_vec(), vec![10..=20]); + } + + #[test] + fn try_voids() { + let mut tree = IntervalsTree::new(); + tree.try_insert(1u32..=7).unwrap(); + tree.try_insert(19..=25).unwrap(); + assert_eq!(tree.clone().to_vec(), vec![1..=7, 19..=25]); + assert_eq!( + tree.try_voids(0..100) + .unwrap() + .map(RangeInclusive::from) + .collect::>(), + vec![0..=0, 8..=18, 26..=99] + ); + assert_eq!( + tree.try_voids((0, None)) + .unwrap() + .map(RangeInclusive::from) + .collect::>(), + vec![0..=0, 8..=18, 26..=u32::MAX] + ); + assert_eq!( + tree.try_voids((None, None)) + .unwrap() + .map(RangeInclusive::from) + .collect::>(), + vec![] + ); + assert_eq!( + tree.try_voids(1..1) + .unwrap() + .map(RangeInclusive::from) + .collect::>(), + vec![] + ); + assert_eq!( + tree.try_voids(0..=0) + .unwrap() + .map(RangeInclusive::from) + .collect::>(), + vec![0..=0] + ); + + assert!(tree.try_voids(1..0).is_err()); + } + + #[test] + fn try_insert() { + let mut tree = IntervalsTree::new(); + tree.try_insert(1u32..=2).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=2]); + tree.try_insert(4..=5).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=2, 4..=5]); + tree.try_insert(4..4).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=2, 4..=5]); + assert!(tree.try_insert(4..3).is_err()); + tree.try_insert(None..None).unwrap(); + assert_eq!(tree.to_vec(), vec![1..=2, 4..=5]); + tree.try_insert((0, None)).unwrap(); + assert_eq!(tree.to_vec(), vec![0..=u32::MAX]); + } + + #[test] + fn try_remove() { + let mut tree = [1u32, 2, 5, 6, 7, 9, 10, 11] + .into_iter() + .collect::>(); + assert_eq!(tree.to_vec(), vec![1..=2, 5..=7, 9..=11]); + assert!(tree.try_remove(0..0).is_ok()); + assert_eq!(tree.to_vec(), vec![1..=2, 5..=7, 9..=11]); + assert!(tree.try_remove(1..1).is_ok()); + assert_eq!(tree.to_vec(), vec![1..=2, 5..=7, 9..=11]); + assert!(tree.try_remove(1..2).is_ok()); + assert_eq!(tree.to_vec(), vec![2..=2, 5..=7, 9..=11]); + assert!(tree.try_remove(..7).is_ok()); + assert_eq!(tree.to_vec(), vec![7..=7, 9..=11]); + assert!(tree.try_remove(None..None).is_ok()); + assert_eq!(tree.to_vec(), vec![7..=7, 9..=11]); + assert!(tree.try_remove(1..0).is_err()); + assert_eq!(tree.to_vec(), vec![7..=7, 9..=11]); + assert!(tree.try_remove((1, None)).is_ok()); + assert_eq!(tree.to_vec(), vec![]); + } + + #[test] + fn contains() { + let tree: IntervalsTree = [0, 100, 101, 102, 45678, 45679, 1, 2, 3] + .into_iter() + .collect(); + assert_eq!(tree.to_vec(), vec![0..=3, 100..=102, 45678..=45679]); + assert!(tree.contains(0)); + assert!(tree.contains(1)); + assert!(tree.contains(2)); + assert!(tree.contains(3)); + assert!(!tree.contains(4)); + assert!(!tree.contains(99)); + assert!(tree.contains(100)); + assert!(tree.contains(101)); + assert!(tree.contains(102)); + assert!(!tree.contains(103)); + assert!(!tree.contains(45677)); + assert!(tree.contains(45678)); + assert!(tree.contains(45679)); + assert!(!tree.contains(45680)); + assert!(!tree.contains(141241)); + assert!(tree.try_contains(0..=3).unwrap()); + assert!(tree.try_contains(0..4).unwrap()); + assert!(!tree.try_contains(0..5).unwrap()); + assert!(tree.try_contains(1..1).unwrap()); + assert!(!tree.contains(..)); + assert!(tree.contains(..1)); + } + + #[test] + fn amount() { + let tree: IntervalsTree = [-100, -99, 100, 101, 102, 1000].into_iter().collect(); + assert_eq!(tree.intervals_amount(), 3); + assert_eq!(tree.points_amount(), Some(6)); + + let tree: IntervalsTree = [..].into_iter().collect(); + assert_eq!(tree.intervals_amount(), 1); + assert_eq!(tree.points_amount(), None); + + let tree: IntervalsTree = Default::default(); + assert_eq!(tree.intervals_amount(), 0); + assert_eq!(tree.points_amount(), Some(0)); + } + + #[test] + fn start_end() { + let tree: IntervalsTree = [0u64, 100, 101, 102, 45678, 45679, 1, 2, 3] + .into_iter() + .collect(); + assert_eq!(tree.to_vec(), vec![0..=3, 100..=102, 45678..=45679]); + assert_eq!(tree.start(), Some(0)); + assert_eq!(tree.end(), Some(45679)); + } + + #[test] + fn and_not_iter() { + let tree: IntervalsTree = [0, 1, 2, 3, 4, 8, 9, 100, 101, 102].into_iter().collect(); + let tree1: IntervalsTree = [3, 4, 7, 8, 9, 10, 45, 46, 100, 102].into_iter().collect(); + let v: Vec> = tree.and_not_iter(&tree1).map(Into::into).collect(); + assert_eq!(v, vec![0..=2, 101..=101]); + + let tree1: IntervalsTree = [..].into_iter().collect(); + let v: Vec> = tree.and_not_iter(&tree1).map(Into::into).collect(); + assert_eq!(v, vec![]); + + let tree1: IntervalsTree = [..=100].into_iter().collect(); + let v: Vec> = tree.and_not_iter(&tree1).map(Into::into).collect(); + assert_eq!(v, vec![101..=102]); + + let tree1: IntervalsTree = [101..].into_iter().collect(); + let v: Vec> = tree.and_not_iter(&tree1).map(Into::into).collect(); + assert_eq!(v, vec![0..=4, 8..=9, 100..=100]); + + let tree1: IntervalsTree = [6, 10, 110].into_iter().collect(); + let v: Vec> = tree.and_not_iter(&tree1).map(Into::into).collect(); + assert_eq!(v, vec![0..=4, 8..=9, 100..=102]); + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 0f700e56d72..e8fdff55497 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -17,6 +17,8 @@ // along with this program. If not, see . #![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] #[macro_use] extern crate gear_common_codegen; @@ -64,7 +66,6 @@ use gear_core::{ }; use primitive_types::H256; use sp_arithmetic::traits::{BaseArithmetic, One, Saturating, UniqueSaturatedInto, Unsigned}; -use sp_core::crypto::UncheckedFrom; use sp_std::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet}, prelude::*, @@ -106,7 +107,7 @@ impl Origin for sp_runtime::AccountId32 { } fn from_origin(v: H256) -> Self { - sp_runtime::AccountId32::unchecked_from(v) + Self::new(v.0) } } diff --git a/core-backend/Cargo.toml b/core-backend/Cargo.toml index 7343023cbb6..dd27726e2ea 100644 --- a/core-backend/Cargo.toml +++ b/core-backend/Cargo.toml @@ -21,7 +21,7 @@ gear-sandbox-env.workspace = true actor-system-error.workspace = true blake2-rfc.workspace = true -# Use max_level_debug feature to remove tracing in sys-calls by default. +# Use max_level_debug feature to remove tracing in syscalls by default. log.workspace = true derive_more.workspace = true codec.workspace = true diff --git a/core-backend/src/env.rs b/core-backend/src/env.rs index fddf96961e0..f9d973d2b95 100644 --- a/core-backend/src/env.rs +++ b/core-backend/src/env.rs @@ -54,7 +54,7 @@ use gear_sandbox::{ SandboxMemory, SandboxStore, Value, }; use gear_wasm_instrument::{ - syscalls::SysCallName::{self, *}, + syscalls::SyscallName::{self, *}, GLOBAL_NAME_GAS, STACK_END_EXPORT_NAME, }; @@ -135,7 +135,7 @@ where // It makes adding functions to `EnvironmentDefinitionBuilder` shorter. struct EnvBuilder { env_def_builder: EnvironmentDefinitionBuilder>, - forbidden_funcs: BTreeSet, + forbidden_funcs: BTreeSet, funcs_count: usize, } @@ -148,7 +148,7 @@ where { fn add_func( &mut self, - name: SysCallName, + name: SyscallName, f: HostFuncType>, ) { if self.forbidden_funcs.contains(&name) { @@ -317,13 +317,13 @@ where Self::bind_funcs(&mut builder); - // Check that we have implementations for all the sys-calls. + // Check that we have implementations for all the syscalls. // This is intended to panic during any testing, when the // condition is not met. assert_eq!( builder.funcs_count, - SysCallName::count(), - "Not all existing sys-calls were added to the module's env." + SyscallName::count(), + "Not all existing syscalls were added to the module's env." ); let env_builder: EnvironmentDefinitionBuilder<_> = builder.into(); diff --git a/core-backend/src/error.rs b/core-backend/src/error.rs index 694f350de1d..7ee462221cd 100644 --- a/core-backend/src/error.rs +++ b/core-backend/src/error.rs @@ -104,7 +104,7 @@ impl From for ActorTerminationReason { #[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)] pub struct SystemTerminationReason; -/// Execution error in infallible sys-call. +/// Execution error in infallible syscall. #[derive( Decode, Encode, @@ -119,7 +119,7 @@ pub struct SystemTerminationReason; )] #[codec(crate = codec)] pub enum UnrecoverableExecutionError { - #[display(fmt = "Invalid debug string passed in `gr_debug` sys-call")] + #[display(fmt = "Invalid debug string passed in `gr_debug` syscall")] InvalidDebugString, #[display(fmt = "Not enough gas for operation")] NotEnoughGas, @@ -131,7 +131,7 @@ pub enum UnrecoverableExecutionError { UnsupportedEnvVarsVersion, } -/// Memory error in infallible sys-call. +/// Memory error in infallible syscall. #[derive( Decode, Encode, @@ -154,7 +154,7 @@ pub enum UnrecoverableMemoryError { RuntimeAllocOutOfBounds, } -/// Wait error in infallible sys-call. +/// Wait error in infallible syscall. #[derive( Decode, Encode, @@ -216,14 +216,14 @@ pub enum TrapExplanation { /// An error occurs in attempt to charge more gas than available during execution. #[display(fmt = "Not enough gas to continue execution")] GasLimitExceeded, - /// An error occurs in attempt to call forbidden sys-call. + /// An error occurs in attempt to call forbidden syscall. #[display(fmt = "Unable to call a forbidden function")] ForbiddenFunction, /// The error occurs when a program tries to allocate more memory than /// allowed. #[display(fmt = "Trying to allocate more wasm program memory than allowed")] ProgramAllocOutOfBounds, - #[display(fmt = "Sys-call unrecoverable error: {_0}")] + #[display(fmt = "Syscall unrecoverable error: {_0}")] UnrecoverableExt(UnrecoverableExtError), #[display(fmt = "{_0}")] Panic(LimitedStr<'static>), @@ -231,7 +231,7 @@ pub enum TrapExplanation { Unknown, } -/// Error returned by fallible sys-call. +/// Error returned by fallible syscall. #[derive(Debug, Clone)] pub enum RunFallibleError { UndefinedTerminationReason(UndefinedTerminationReason), diff --git a/core-backend/src/funcs.rs b/core-backend/src/funcs.rs index e6d67b90235..265a1aae110 100644 --- a/core-backend/src/funcs.rs +++ b/core-backend/src/funcs.rs @@ -56,30 +56,30 @@ const PTR_SPECIAL: u32 = u32::MAX; /// Actually just wrapper around [`Value`] to implement conversions. #[derive(Clone, Copy)] -struct SysCallValue(Value); +struct SyscallValue(Value); -impl From for SysCallValue { +impl From for SyscallValue { fn from(value: i32) -> Self { - SysCallValue(Value::I32(value)) + SyscallValue(Value::I32(value)) } } -impl From for SysCallValue { +impl From for SyscallValue { fn from(value: u32) -> Self { - SysCallValue(Value::I32(value as i32)) + SyscallValue(Value::I32(value as i32)) } } -impl From for SysCallValue { +impl From for SyscallValue { fn from(value: i64) -> Self { - SysCallValue(Value::I64(value)) + SyscallValue(Value::I64(value)) } } -impl TryFrom for u32 { +impl TryFrom for u32 { type Error = HostError; - fn try_from(val: SysCallValue) -> Result { + fn try_from(val: SyscallValue) -> Result { if let Value::I32(val) = val.0 { Ok(val as u32) } else { @@ -88,10 +88,10 @@ impl TryFrom for u32 { } } -impl TryFrom for u64 { +impl TryFrom for u64 { type Error = HostError; - fn try_from(val: SysCallValue) -> Result { + fn try_from(val: SyscallValue) -> Result { if let Value::I64(val) = val.0 { Ok(val as u64) } else { @@ -101,44 +101,44 @@ impl TryFrom for u64 { } /// Actually just wrapper around [`ReturnValue`] to implement conversions. -pub struct SysCallReturnValue(ReturnValue); +pub struct SyscallReturnValue(ReturnValue); -impl From for ReturnValue { - fn from(value: SysCallReturnValue) -> Self { +impl From for ReturnValue { + fn from(value: SyscallReturnValue) -> Self { value.0 } } -impl From<()> for SysCallReturnValue { +impl From<()> for SyscallReturnValue { fn from((): ()) -> Self { Self(ReturnValue::Unit) } } -impl From for SysCallReturnValue { +impl From for SyscallReturnValue { fn from(value: i32) -> Self { Self(ReturnValue::Value(Value::I32(value))) } } -impl From for SysCallReturnValue { +impl From for SyscallReturnValue { fn from(value: u32) -> Self { Self(ReturnValue::Value(Value::I32(value as i32))) } } -pub(crate) trait SysCallContext: Sized { +pub(crate) trait SyscallContext: Sized { fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError>; } -impl SysCallContext for () { +impl SyscallContext for () { fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError> { Ok(((), args)) } } -pub(crate) trait SysCall { - type Context: SysCallContext; +pub(crate) trait Syscall { + type Context: SyscallContext; fn execute( self, @@ -151,15 +151,15 @@ pub(crate) trait SysCall { /// /// # Generics /// `Args` is to make specialization based on function arguments -/// `Ext` and `Res` are for sys-call itself (`SysCall`) -pub(crate) trait SysCallBuilder { - fn build(self, args: &[Value]) -> Result; +/// `Ext` and `Res` are for syscall itself (`Syscall`) +pub(crate) trait SyscallBuilder { + fn build(self, args: &[Value]) -> Result; } -impl SysCallBuilder for Builder +impl SyscallBuilder for Builder where Builder: FnOnce() -> Call, - Call: SysCall, + Call: Syscall, { fn build(self, args: &[Value]) -> Result { let _: [Value; 0] = args.try_into().map_err(|_| HostError)?; @@ -167,33 +167,33 @@ where } } -impl SysCallBuilder for Builder +impl SyscallBuilder for Builder where Builder: for<'a> FnOnce(&'a [Value]) -> Call, - Call: SysCall, + Call: Syscall, { fn build(self, args: &[Value]) -> Result { Ok((self)(args)) } } -// implement [`SysCallBuilder`] for functions with different amount of arguments +// implement [`SyscallBuilder`] for functions with different amount of arguments macro_rules! impl_syscall_builder { ($($generic:ident),+) => { #[allow(non_snake_case)] - impl SysCallBuilder + impl SyscallBuilder for Builder where Builder: FnOnce($($generic),+) -> Call, - Call: SysCall, - $( $generic: TryFrom,)+ + Call: Syscall, + $( $generic: TryFrom,)+ { fn build(self, args: &[Value]) -> Result { const ARGS_AMOUNT: usize = impl_syscall_builder!(@count $($generic),+); let [$($generic),+]: [Value; ARGS_AMOUNT] = args.try_into().map_err(|_| HostError)?; $( - let $generic = SysCallValue($generic).try_into()?; + let $generic = SyscallValue($generic).try_into()?; )+ Ok((self)($($generic),+)) } @@ -211,16 +211,16 @@ impl_syscall_builder!(A, B, C, D, E); impl_syscall_builder!(A, B, C, D, E, F); impl_syscall_builder!(A, B, C, D, E, F, G); -/// "raw" sys-call without any argument parsing or without calling [`CallerWrap`] helper methods -struct RawSysCall(F); +/// "raw" syscall without any argument parsing or without calling [`CallerWrap`] helper methods +struct RawSyscall(F); -impl RawSysCall { +impl RawSyscall { fn new(f: F) -> Self { Self(f) } } -impl SysCall for RawSysCall +impl Syscall for RawSyscall where F: FnOnce(&mut CallerWrap) -> Result<(Gas, T), HostError>, Ext: BackendExternalities + 'static, @@ -236,32 +236,32 @@ where } } -/// Fallible sys-call context that parses `gas` and `err_ptr` arguments. -struct FallibleSysCallContext { +/// Fallible syscall context that parses `gas` and `err_ptr` arguments. +struct FallibleSyscallContext { gas: Gas, res_ptr: u32, } -impl SysCallContext for FallibleSysCallContext { +impl SyscallContext for FallibleSyscallContext { fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError> { let (gas, args) = args.split_first().ok_or(HostError)?; - let gas: Gas = SysCallValue(*gas).try_into()?; + let gas: Gas = SyscallValue(*gas).try_into()?; let (res_ptr, args) = args.split_last().ok_or(HostError)?; - let res_ptr: u32 = SysCallValue(*res_ptr).try_into()?; - Ok((FallibleSysCallContext { gas, res_ptr }, args)) + let res_ptr: u32 = SyscallValue(*res_ptr).try_into()?; + Ok((FallibleSyscallContext { gas, res_ptr }, args)) } } -/// Fallible sys-call that calls [`CallerWrap::run_fallible`] underneath. -struct FallibleSysCall { +/// Fallible syscall that calls [`CallerWrap::run_fallible`] underneath. +struct FallibleSyscall { costs: RuntimeCosts, error: PhantomData, f: F, } -impl FallibleSysCall<(), F> { - fn new(costs: RuntimeCosts, f: F) -> FallibleSysCall { - FallibleSysCall { +impl FallibleSyscall<(), F> { + fn new(costs: RuntimeCosts, f: F) -> FallibleSyscall { + FallibleSyscall { costs, error: PhantomData, f, @@ -269,13 +269,13 @@ impl FallibleSysCall<(), F> { } } -impl SysCall for FallibleSysCall +impl Syscall for FallibleSyscall where F: FnOnce(&mut CallerWrap) -> Result, E: From>, Ext: BackendExternalities + 'static, { - type Context = FallibleSysCallContext; + type Context = FallibleSyscallContext; fn execute( self, @@ -287,42 +287,42 @@ where error: _error, f, } = self; - let FallibleSysCallContext { gas, res_ptr } = context; + let FallibleSyscallContext { gas, res_ptr } = context; caller.run_fallible::(gas, res_ptr, costs, f) } } -/// Infallible sys-call context that parses `gas` argument. -pub struct InfallibleSysCallContext { +/// Infallible syscall context that parses `gas` argument. +pub struct InfallibleSyscallContext { gas: Gas, } -impl SysCallContext for InfallibleSysCallContext { +impl SyscallContext for InfallibleSyscallContext { fn from_args(args: &[Value]) -> Result<(Self, &[Value]), HostError> { let (gas, args) = args.split_first().ok_or(HostError)?; - let gas: Gas = SysCallValue(*gas).try_into()?; + let gas: Gas = SyscallValue(*gas).try_into()?; Ok((Self { gas }, args)) } } -/// Infallible sys-call that calls [`CallerWrap::run_any`] underneath -struct InfallibleSysCall { +/// Infallible syscall that calls [`CallerWrap::run_any`] underneath +struct InfallibleSyscall { costs: RuntimeCosts, f: F, } -impl InfallibleSysCall { +impl InfallibleSyscall { fn new(costs: RuntimeCosts, f: F) -> Self { Self { costs, f } } } -impl SysCall for InfallibleSysCall +impl Syscall for InfallibleSyscall where F: Fn(&mut CallerWrap) -> Result, Ext: BackendExternalities + 'static, { - type Context = InfallibleSysCallContext; + type Context = InfallibleSyscallContext; fn execute( self, @@ -330,7 +330,7 @@ where ctx: Self::Context, ) -> Result<(Gas, T), HostError> { let Self { costs, f } = self; - let InfallibleSysCallContext { gas } = ctx; + let InfallibleSyscallContext { gas } = ctx; caller.run_any::(gas, costs, f) } } @@ -352,18 +352,18 @@ where builder: Builder, ) -> Result where - Builder: SysCallBuilder, + Builder: SyscallBuilder, Args: ?Sized, - Call: SysCall, - Res: Into, + Call: Syscall, + Res: Into, { crate::log::trace_syscall::(args); let mut caller = CallerWrap::prepare(caller); let (ctx, args) = Call::Context::from_args(args)?; - let sys_call = builder.build(args)?; - let (gas, value) = sys_call.execute(&mut caller, ctx)?; + let syscall = builder.build(args)?; + let (gas, value) = syscall.execute(&mut caller, ctx)?; let value = value.into(); Ok(WasmReturnValue { @@ -420,8 +420,8 @@ where .map_err(Into::into) } - pub fn send(pid_value_ptr: u32, payload_ptr: u32, len: u32, delay: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn send(pid_value_ptr: u32, payload_ptr: u32, len: u32, delay: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::Send(len), move |ctx: &mut CallerWrap| { Self::send_inner(ctx, pid_value_ptr, payload_ptr, len, None, delay) @@ -435,8 +435,8 @@ where len: u32, gas_limit: u64, delay: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendWGas(len), move |ctx: &mut CallerWrap| { Self::send_inner(ctx, pid_value_ptr, payload_ptr, len, Some(gas_limit), delay) @@ -471,8 +471,8 @@ where .map_err(Into::into) } - pub fn send_commit(handle: u32, pid_value_ptr: u32, delay: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn send_commit(handle: u32, pid_value_ptr: u32, delay: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendCommit, move |ctx: &mut CallerWrap| { Self::send_commit_inner(ctx, handle, pid_value_ptr, None, delay) @@ -485,8 +485,8 @@ where pid_value_ptr: u32, gas_limit: u64, delay: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendCommitWGas, move |ctx: &mut CallerWrap| { Self::send_commit_inner(ctx, handle, pid_value_ptr, Some(gas_limit), delay) @@ -494,15 +494,15 @@ where ) } - pub fn send_init() -> impl SysCall { - FallibleSysCall::new::( + pub fn send_init() -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendInit, move |ctx: &mut CallerWrap| ctx.ext_mut().send_init().map_err(Into::into), ) } - pub fn send_push(handle: u32, payload_ptr: u32, len: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn send_push(handle: u32, payload_ptr: u32, len: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendPush(len), move |ctx: &mut CallerWrap| { let read_payload = ctx.manager.register_read(payload_ptr, len); @@ -520,8 +520,8 @@ where payload_ptr: u32, len: u32, delay: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReservationSend(len), move |ctx: &mut CallerWrap| { let read_rid_pid_value = ctx.manager.register_read_as(rid_pid_value_ptr); @@ -548,8 +548,8 @@ where handle: u32, rid_pid_value_ptr: u32, delay: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReservationSendCommit, move |ctx: &mut CallerWrap| { let read_rid_pid_value = ctx.manager.register_read_as(rid_pid_value_ptr); @@ -571,8 +571,8 @@ where ) } - pub fn read(at: u32, len: u32, buffer_ptr: u32) -> impl SysCall { - FallibleSysCall::new::(RuntimeCosts::Read, move |ctx: &mut CallerWrap| { + pub fn read(at: u32, len: u32, buffer_ptr: u32) -> impl Syscall { + FallibleSyscall::new::(RuntimeCosts::Read, move |ctx: &mut CallerWrap| { let payload_lock = ctx.ext_mut().lock_payload(at, len)?; payload_lock .drop_with::(|payload_access| { @@ -587,8 +587,8 @@ where }) } - pub fn size(size_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Size, move |ctx: &mut CallerWrap| { + pub fn size(size_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Size, move |ctx: &mut CallerWrap| { let size = ctx.ext_mut().size()? as u32; let write_size = ctx.manager.register_write_as(size_ptr); @@ -597,16 +597,16 @@ where }) } - pub fn exit(inheritor_id_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Exit, move |ctx: &mut CallerWrap| { + pub fn exit(inheritor_id_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Exit, move |ctx: &mut CallerWrap| { let read_inheritor_id = ctx.manager.register_read_decoded(inheritor_id_ptr); let inheritor_id = ctx.read_decoded(read_inheritor_id)?; Err(ActorTerminationReason::Exit(inheritor_id).into()) }) } - pub fn reply_code() -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_code() -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyCode, move |ctx: &mut CallerWrap| { ctx.ext_mut() @@ -617,8 +617,8 @@ where ) } - pub fn signal_code() -> impl SysCall { - FallibleSysCall::new::( + pub fn signal_code() -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SignalCode, move |ctx: &mut CallerWrap| { ctx.ext_mut() @@ -629,8 +629,8 @@ where ) } - pub fn alloc(pages: u32) -> impl SysCall { - InfallibleSysCall::new( + pub fn alloc(pages: u32) -> impl Syscall { + InfallibleSyscall::new( RuntimeCosts::Alloc(pages), move |ctx: &mut CallerWrap| { let res = ctx.alloc(pages); @@ -651,8 +651,8 @@ where ) } - pub fn free(page_no: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Free, move |ctx: &mut CallerWrap| { + pub fn free(page_no: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Free, move |ctx: &mut CallerWrap| { let page = WasmPage::new(page_no).map_err(|_| { UndefinedTerminationReason::Actor(ActorTerminationReason::Trap( TrapExplanation::Unknown, @@ -675,8 +675,8 @@ where }) } - pub fn env_vars(vars_ver: u32, vars_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::EnvVars, move |ctx: &mut CallerWrap| { + pub fn env_vars(vars_ver: u32, vars_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::EnvVars, move |ctx: &mut CallerWrap| { let vars = ctx.ext_mut().env_vars(vars_ver)?; let vars_bytes = vars.to_bytes(); let vars_write = ctx @@ -686,8 +686,8 @@ where }) } - pub fn block_height(height_ptr: u32) -> impl SysCall { - InfallibleSysCall::new( + pub fn block_height(height_ptr: u32) -> impl Syscall { + InfallibleSyscall::new( RuntimeCosts::BlockHeight, move |ctx: &mut CallerWrap| { let height = ctx.ext_mut().block_height()?; @@ -699,8 +699,8 @@ where ) } - pub fn block_timestamp(timestamp_ptr: u32) -> impl SysCall { - InfallibleSysCall::new( + pub fn block_timestamp(timestamp_ptr: u32) -> impl Syscall { + InfallibleSyscall::new( RuntimeCosts::BlockTimestamp, move |ctx: &mut CallerWrap| { let timestamp = ctx.ext_mut().block_timestamp()?; @@ -712,8 +712,8 @@ where ) } - pub fn random(subject_ptr: u32, bn_random_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Random, move |ctx: &mut CallerWrap| { + pub fn random(subject_ptr: u32, bn_random_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Random, move |ctx: &mut CallerWrap| { let read_subject = ctx.manager.register_read_decoded(subject_ptr); let write_bn_random = ctx.manager.register_write_as(bn_random_ptr); @@ -746,8 +746,8 @@ where .map_err(Into::into) } - pub fn reply(payload_ptr: u32, len: u32, value_ptr: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reply(payload_ptr: u32, len: u32, value_ptr: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::Reply(len), move |ctx: &mut CallerWrap| { Self::reply_inner(ctx, payload_ptr, len, None, value_ptr) @@ -760,8 +760,8 @@ where len: u32, gas_limit: u64, value_ptr: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyWGas(len), move |ctx: &mut CallerWrap| { Self::reply_inner(ctx, payload_ptr, len, Some(gas_limit), value_ptr) @@ -785,15 +785,15 @@ where .map_err(Into::into) } - pub fn reply_commit(value_ptr: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_commit(value_ptr: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyCommit, move |ctx: &mut CallerWrap| Self::reply_commit_inner(ctx, None, value_ptr), ) } - pub fn reply_commit_wgas(gas_limit: u64, value_ptr: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_commit_wgas(gas_limit: u64, value_ptr: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyCommitWGas, move |ctx: &mut CallerWrap| { Self::reply_commit_inner(ctx, Some(gas_limit), value_ptr) @@ -801,8 +801,8 @@ where ) } - pub fn reservation_reply(rid_value_ptr: u32, payload_ptr: u32, len: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reservation_reply(rid_value_ptr: u32, payload_ptr: u32, len: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReservationReply(len), move |ctx: &mut CallerWrap| { let read_rid_value = ctx.manager.register_read_as(rid_value_ptr); @@ -820,8 +820,8 @@ where ) } - pub fn reservation_reply_commit(rid_value_ptr: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reservation_reply_commit(rid_value_ptr: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReservationReplyCommit, move |ctx: &mut CallerWrap| { let read_rid_value = ctx.manager.register_read_as(rid_value_ptr); @@ -840,22 +840,22 @@ where ) } - pub fn reply_to() -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_to() -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyTo, move |ctx: &mut CallerWrap| ctx.ext_mut().reply_to().map_err(Into::into), ) } - pub fn signal_from() -> impl SysCall { - FallibleSysCall::new::( + pub fn signal_from() -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SignalFrom, move |ctx: &mut CallerWrap| ctx.ext_mut().signal_from().map_err(Into::into), ) } - pub fn reply_push(payload_ptr: u32, len: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_push(payload_ptr: u32, len: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyPush(len), move |ctx: &mut CallerWrap| { let read_payload = ctx.manager.register_read(payload_ptr, len); @@ -887,8 +887,8 @@ where .map_err(Into::into) } - pub fn reply_input(offset: u32, len: u32, value_ptr: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_input(offset: u32, len: u32, value_ptr: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyInput, move |ctx: &mut CallerWrap| { Self::reply_input_inner(ctx, offset, len, None, value_ptr) @@ -901,8 +901,8 @@ where len: u32, gas_limit: u64, value_ptr: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyInputWGas, move |ctx: &mut CallerWrap| { Self::reply_input_inner(ctx, offset, len, Some(gas_limit), value_ptr) @@ -910,8 +910,8 @@ where ) } - pub fn reply_push_input(offset: u32, len: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_push_input(offset: u32, len: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyPushInput, move |ctx: &mut CallerWrap| { ctx.ext_mut() @@ -953,8 +953,8 @@ where .map_err(Into::into) } - pub fn send_input(pid_value_ptr: u32, offset: u32, len: u32, delay: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn send_input(pid_value_ptr: u32, offset: u32, len: u32, delay: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendInput, move |ctx: &mut CallerWrap| { Self::send_input_inner(ctx, pid_value_ptr, offset, len, None, delay) @@ -968,8 +968,8 @@ where len: u32, gas_limit: u64, delay: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendInputWGas, move |ctx: &mut CallerWrap| { Self::send_input_inner(ctx, pid_value_ptr, offset, len, Some(gas_limit), delay) @@ -977,8 +977,8 @@ where ) } - pub fn send_push_input(handle: u32, offset: u32, len: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn send_push_input(handle: u32, offset: u32, len: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SendPushInput, move |ctx: &mut CallerWrap| { ctx.ext_mut() @@ -988,8 +988,8 @@ where ) } - pub fn debug(data_ptr: u32, data_len: u32) -> impl SysCall { - InfallibleSysCall::new( + pub fn debug(data_ptr: u32, data_len: u32) -> impl Syscall { + InfallibleSyscall::new( RuntimeCosts::Debug(data_len), move |ctx: &mut CallerWrap| { let read_data = ctx.manager.register_read(data_ptr, data_len); @@ -1011,8 +1011,8 @@ where ) } - pub fn panic(data_ptr: u32, data_len: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Null, move |ctx: &mut CallerWrap| { + pub fn panic(data_ptr: u32, data_len: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Null, move |ctx: &mut CallerWrap| { let read_data = ctx.manager.register_read(data_ptr, data_len); let data = ctx.read(read_data).unwrap_or_default(); @@ -1022,14 +1022,14 @@ where }) } - pub fn oom_panic() -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Null, |_ctx: &mut CallerWrap| { + pub fn oom_panic() -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Null, |_ctx: &mut CallerWrap| { Err(ActorTerminationReason::Trap(TrapExplanation::ProgramAllocOutOfBounds).into()) }) } - pub fn reserve_gas(gas_value: u64, duration: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn reserve_gas(gas_value: u64, duration: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReserveGas, move |ctx: &mut CallerWrap| { ctx.ext_mut() @@ -1039,8 +1039,8 @@ where ) } - pub fn reply_deposit(message_id_ptr: u32, gas_value: u64) -> impl SysCall { - FallibleSysCall::new::( + pub fn reply_deposit(message_id_ptr: u32, gas_value: u64) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::ReplyDeposit, move |ctx: &mut CallerWrap| { let read_message_id = ctx.manager.register_read_decoded(message_id_ptr); @@ -1053,8 +1053,8 @@ where ) } - pub fn unreserve_gas(reservation_id_ptr: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn unreserve_gas(reservation_id_ptr: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::UnreserveGas, move |ctx: &mut CallerWrap| { let read_reservation_id = ctx.manager.register_read_decoded(reservation_id_ptr); @@ -1067,8 +1067,8 @@ where ) } - pub fn system_reserve_gas(gas_value: u64) -> impl SysCall { - FallibleSysCall::new::( + pub fn system_reserve_gas(gas_value: u64) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::SystemReserveGas, move |ctx: &mut CallerWrap| { ctx.ext_mut() @@ -1078,8 +1078,8 @@ where ) } - pub fn gas_available(gas_ptr: u32) -> impl SysCall { - InfallibleSysCall::new( + pub fn gas_available(gas_ptr: u32) -> impl Syscall { + InfallibleSyscall::new( RuntimeCosts::GasAvailable, move |ctx: &mut CallerWrap| { let gas_available = ctx.ext_mut().gas_available()?; @@ -1091,8 +1091,8 @@ where ) } - pub fn message_id(message_id_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::MsgId, move |ctx: &mut CallerWrap| { + pub fn message_id(message_id_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::MsgId, move |ctx: &mut CallerWrap| { let message_id = ctx.ext_mut().message_id()?; let write_message_id = ctx.manager.register_write_as(message_id_ptr); @@ -1101,8 +1101,8 @@ where }) } - pub fn program_id(program_id_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::ProgramId, move |ctx: &mut CallerWrap| { + pub fn program_id(program_id_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::ProgramId, move |ctx: &mut CallerWrap| { let program_id = ctx.ext_mut().program_id()?; let write_program_id = ctx.manager.register_write_as(program_id_ptr); @@ -1111,8 +1111,8 @@ where }) } - pub fn pay_program_rent(rent_pid_ptr: u32) -> impl SysCall { - FallibleSysCall::new::( + pub fn pay_program_rent(rent_pid_ptr: u32) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::PayProgramRent, move |ctx: &mut CallerWrap| { let read_rent_pid = ctx.manager.register_read_as(rent_pid_ptr); @@ -1129,8 +1129,8 @@ where ) } - pub fn source(source_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Source, move |ctx: &mut CallerWrap| { + pub fn source(source_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Source, move |ctx: &mut CallerWrap| { let source = ctx.ext_mut().source()?; let write_source = ctx.manager.register_write_as(source_ptr); @@ -1139,8 +1139,8 @@ where }) } - pub fn value(value_ptr: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Value, move |ctx: &mut CallerWrap| { + pub fn value(value_ptr: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Value, move |ctx: &mut CallerWrap| { let value = ctx.ext_mut().value()?; let write_value = ctx.manager.register_write_as(value_ptr); @@ -1149,8 +1149,8 @@ where }) } - pub fn value_available(value_ptr: u32) -> impl SysCall { - InfallibleSysCall::new( + pub fn value_available(value_ptr: u32) -> impl Syscall { + InfallibleSyscall::new( RuntimeCosts::ValueAvailable, move |ctx: &mut CallerWrap| { let value_available = ctx.ext_mut().value_available()?; @@ -1162,28 +1162,28 @@ where ) } - pub fn leave() -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Leave, move |_ctx: &mut CallerWrap| { + pub fn leave() -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Leave, move |_ctx: &mut CallerWrap| { Err(ActorTerminationReason::Leave.into()) }) } - pub fn wait() -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Wait, move |ctx: &mut CallerWrap| { + pub fn wait() -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Wait, move |ctx: &mut CallerWrap| { ctx.ext_mut().wait()?; Err(ActorTerminationReason::Wait(None, MessageWaitedType::Wait).into()) }) } - pub fn wait_for(duration: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::WaitFor, move |ctx: &mut CallerWrap| { + pub fn wait_for(duration: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::WaitFor, move |ctx: &mut CallerWrap| { ctx.ext_mut().wait_for(duration)?; Err(ActorTerminationReason::Wait(Some(duration), MessageWaitedType::WaitFor).into()) }) } - pub fn wait_up_to(duration: u32) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::WaitUpTo, move |ctx: &mut CallerWrap| { + pub fn wait_up_to(duration: u32) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::WaitUpTo, move |ctx: &mut CallerWrap| { let waited_type = if ctx.ext_mut().wait_up_to(duration)? { MessageWaitedType::WaitUpToFull } else { @@ -1193,8 +1193,8 @@ where }) } - pub fn wake(message_id_ptr: u32, delay: u32) -> impl SysCall { - FallibleSysCall::new::(RuntimeCosts::Wake, move |ctx: &mut CallerWrap| { + pub fn wake(message_id_ptr: u32, delay: u32) -> impl Syscall { + FallibleSyscall::new::(RuntimeCosts::Wake, move |ctx: &mut CallerWrap| { let read_message_id = ctx.manager.register_read_decoded(message_id_ptr); let message_id = ctx.read_decoded(read_message_id)?; @@ -1247,8 +1247,8 @@ where payload_ptr: u32, payload_len: u32, delay: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::CreateProgram(payload_len, salt_len), move |ctx: &mut CallerWrap| -> Result<_, RunFallibleError> { Self::create_program_inner( @@ -1273,8 +1273,8 @@ where payload_len: u32, gas_limit: u64, delay: u32, - ) -> impl SysCall { - FallibleSysCall::new::( + ) -> impl Syscall { + FallibleSyscall::new::( RuntimeCosts::CreateProgramWGas(payload_len, salt_len), move |ctx: &mut CallerWrap| { Self::create_program_inner( @@ -1291,14 +1291,14 @@ where ) } - pub fn forbidden(_args: &[Value]) -> impl SysCall { - InfallibleSysCall::new(RuntimeCosts::Null, |_: &mut CallerWrap| { + pub fn forbidden(_args: &[Value]) -> impl Syscall { + InfallibleSyscall::new(RuntimeCosts::Null, |_: &mut CallerWrap| { Err(ActorTerminationReason::Trap(TrapExplanation::ForbiddenFunction).into()) }) } - pub fn out_of_gas(_gas: Gas) -> impl SysCall { - RawSysCall::new(|ctx: &mut CallerWrap| { + pub fn out_of_gas(_gas: Gas) -> impl Syscall { + RawSyscall::new(|ctx: &mut CallerWrap| { let ext = ctx.ext_mut(); let current_counter = ext.current_counter_type(); log::trace!(target: "syscalls", "[out_of_gas] Current counter in global represents {current_counter:?}"); diff --git a/core-backend/src/mock.rs b/core-backend/src/mock.rs index dca712108b6..84f55e70774 100644 --- a/core-backend/src/mock.rs +++ b/core-backend/src/mock.rs @@ -37,7 +37,7 @@ use gear_core::{ }; use gear_core_errors::{ReplyCode, SignalCode}; use gear_lazy_pages_common::ProcessAccessError; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; /// Mock error #[derive(Debug, Clone, Encode, Decode)] @@ -70,7 +70,7 @@ impl BackendAllocSyscallError for Error { /// Mock ext #[derive(Debug, PartialEq, Eq, Clone, Default)] -pub struct MockExt(BTreeSet); +pub struct MockExt(BTreeSet); impl CountersOwner for MockExt { fn charge_gas_runtime(&mut self, _cost: RuntimeCosts) -> Result<(), ChargeError> { @@ -236,7 +236,7 @@ impl Externalities for MockExt { ) -> Result<(), Self::UnrecoverableError> { Ok(()) } - fn forbidden_funcs(&self) -> &BTreeSet { + fn forbidden_funcs(&self) -> &BTreeSet { &self.0 } fn reserve_gas( diff --git a/core-backend/src/tests.rs b/core-backend/src/tests.rs index f2ca2f96de9..32f7cc26468 100644 --- a/core-backend/src/tests.rs +++ b/core-backend/src/tests.rs @@ -595,3 +595,62 @@ fn test_register_write_as_with_zero_size() { core::mem::size_of::() as u32 ); } + +/// Check that all syscalls are supported by backend. +#[test] +fn test_syscalls_table() { + use crate::{ + env::{BackendReport, Environment}, + error::ActorTerminationReason, + mock::MockExt, + }; + use gear_core::message::DispatchKind; + use gear_wasm_instrument::{ + gas_metering::ConstantCostRules, + inject, + parity_wasm::{self, builder}, + SyscallName, + }; + + // Make module with one empty function. + let mut module = builder::module() + .function() + .signature() + .build() + .build() + .build(); + + // Insert syscalls imports. + for name in SyscallName::instrumentable() { + let sign = name.signature(); + let types = module.type_section_mut().unwrap().types_mut(); + let type_no = types.len() as u32; + types.push(parity_wasm::elements::Type::Function(sign.func_type())); + + module = builder::from_module(module) + .import() + .module("env") + .external() + .func(type_no) + .field(name.to_str()) + .build() + .build(); + } + + let module = inject(module, &ConstantCostRules::default(), "env").unwrap(); + let code = module.into_bytes().unwrap(); + + // Execute wasm and check success. + let ext = MockExt::default(); + let env = + Environment::new(ext, &code, DispatchKind::Init, Default::default(), 0.into()).unwrap(); + let report = env + .execute(|_, _, _| -> Result<(), u32> { Ok(()) }) + .unwrap(); + + let BackendReport { + termination_reason, .. + } = report; + + assert_eq!(termination_reason, ActorTerminationReason::Success.into()); +} diff --git a/core-errors/src/lib.rs b/core-errors/src/lib.rs index 8af87b9054c..55be7cf0198 100644 --- a/core-errors/src/lib.rs +++ b/core-errors/src/lib.rs @@ -20,6 +20,8 @@ #![no_std] #![warn(missing_docs)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] extern crate alloc; diff --git a/core-processor/src/configs.rs b/core-processor/src/configs.rs index 4aea4207f44..11c7f9724fc 100644 --- a/core-processor/src/configs.rs +++ b/core-processor/src/configs.rs @@ -24,7 +24,7 @@ use gear_core::{ pages::{GearPage, WasmPage}, }; use gear_lazy_pages_common::LazyPagesWeights; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; use scale_info::scale::{self, Decode, Encode}; /// Number of max pages number to use it in tests. @@ -155,7 +155,7 @@ pub struct ExecutionSettings { /// Weights of host functions. pub host_fn_weights: HostFnWeights, /// Functions forbidden to be called. - pub forbidden_funcs: BTreeSet, + pub forbidden_funcs: BTreeSet, /// Threshold for inserting into mailbox pub mailbox_threshold: u64, /// Cost for single block waitlist holding. @@ -193,7 +193,7 @@ pub struct BlockConfig { /// Host function weights. pub host_fn_weights: HostFnWeights, /// Forbidden functions. - pub forbidden_funcs: BTreeSet, + pub forbidden_funcs: BTreeSet, /// Mailbox threshold. pub mailbox_threshold: u64, /// Cost for single block waitlist holding. diff --git a/core-processor/src/ext.rs b/core-processor/src/ext.rs index fed8880e141..9c56457f97b 100644 --- a/core-processor/src/ext.rs +++ b/core-processor/src/ext.rs @@ -57,7 +57,7 @@ use gear_core_errors::{ ProgramRentError, ReplyCode, ReservationError, SignalCode, }; use gear_lazy_pages_common::{GlobalsAccessConfig, LazyPagesWeights, ProcessAccessError, Status}; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; /// Processor context. pub struct ProcessorContext { @@ -95,7 +95,7 @@ pub struct ProcessorContext { /// Weights of host functions. pub host_fn_weights: HostFnWeights, /// Functions forbidden to be called. - pub forbidden_funcs: BTreeSet, + pub forbidden_funcs: BTreeSet, /// Mailbox threshold. pub mailbox_threshold: u64, /// Cost for single block waitlist holding. @@ -244,7 +244,7 @@ impl BackendSyscallError for UnrecoverableExtError { pub enum FallibleExtError { /// Basic error Core(FallibleExtErrorCore), - /// An error occurs in attempt to call forbidden sys-call. + /// An error occurs in attempt to call forbidden syscall. ForbiddenFunction, /// Charge error Charge(ChargeError), @@ -505,6 +505,35 @@ impl Ext { } } + /// Checking that reservation could be charged for + /// dispatch stash with given delay. + fn check_reservation_gas_limit_for_delayed_sending( + &mut self, + reservation_id: &ReservationId, + delay: u32, + ) -> Result<(), FallibleExtError> { + if delay != 0 { + let limit = self + .context + .gas_reserver + .limit_of(reservation_id) + .ok_or(ReservationError::InvalidReservationId)?; + + // Take delay and get cost of block. + // reserve = wait_cost * (delay + reserve_for). + let cost_per_block = self.context.dispatch_hold_cost; + let waiting_reserve = (self.context.reserve_for as u64) + .saturating_add(delay as u64) + .saturating_mul(cost_per_block); + + if limit < waiting_reserve { + return Err(MessageError::InsufficientGasForDelayedSending.into()); + } + } + + Ok(()) + } + fn reduce_gas(&mut self, gas_limit: GasLimit) -> Result<(), FallibleExtError> { if self.context.gas_counter.reduce(gas_limit) != ChargeResult::Enough { Err(FallibleExecutionError::NotEnoughGas.into()) @@ -522,20 +551,61 @@ impl Ext { } // It's temporary fn, used to solve `core-audit/issue#22`. - fn safe_gasfull_sends(&mut self, packet: &T) -> Result<(), FallibleExtError> { - let outgoing_gasless = self.outgoing_gasless; + fn safe_gasfull_sends( + &mut self, + packet: &T, + delay: u32, + ) -> Result<(), FallibleExtError> { + // In case of delayed sending from origin message we keep some gas + // for it while processing outgoing sending notes so gas for + // previously gasless sends should appear to prevent their + // invasion for gas for storing delayed message. + match (packet.gas_limit(), delay != 0) { + // Zero gasfull instant. + // + // In this case there is nothing to do. + (Some(0), false) => {} + + // Any non-zero gasfull or zero gasfull with delay. + // + // In case of zero gasfull with delay it's pretty similar to + // gasless with delay case. + // + // In case of any non-zero gasfull we prevent stealing for any + // previous gasless-es's thresholds from gas supposed to be + // sent with this `packet`. + (Some(_), _) => { + let prev_gasless_fee = self + .outgoing_gasless + .saturating_mul(self.context.mailbox_threshold); + + self.reduce_gas(prev_gasless_fee)?; - match packet.gas_limit() { - Some(x) if x != 0 => { self.outgoing_gasless = 0; + } - let prev_gasless_fee = - outgoing_gasless.saturating_mul(self.context.mailbox_threshold); + // Gasless with delay. + // + // In this case we must give threshold for each uncovered gasless-es + // sent, otherwise they will steal gas from this `packet` that was + // supposed to pay for delay. + // + // It doesn't guarantee threshold for itself. + (None, true) => { + let prev_gasless_fee = self + .outgoing_gasless + .saturating_mul(self.context.mailbox_threshold); self.reduce_gas(prev_gasless_fee)?; + + self.outgoing_gasless = 1; } - None => self.outgoing_gasless = outgoing_gasless.saturating_add(1), - _ => {} + + // Gasless instant. + // + // In this case there is no need to give any thresholds for previous + // gasless-es: only counter should be increased. + (None, false) => self.outgoing_gasless = self.outgoing_gasless.saturating_add(1), }; Ok(()) @@ -547,7 +617,7 @@ impl Ext { check_gas_limit: bool, ) -> Result<(), FallibleExtError> { self.check_message_value(packet.value())?; - // Charge for using expiring resources. Charge for calling sys-call was done earlier. + // Charge for using expiring resources. Charge for calling syscall was done earlier. let gas_limit = if check_gas_limit { self.check_gas_limit(packet.gas_limit())? } else { @@ -593,6 +663,7 @@ impl Ext { return Err(MessageError::InsufficientGasForDelayedSending.into()); } } + Ok(()) } @@ -781,10 +852,9 @@ impl Externalities for Ext { delay: u32, ) -> Result { self.check_forbidden_destination(msg.destination())?; - self.safe_gasfull_sends(&msg)?; + self.safe_gasfull_sends(&msg, delay)?; self.charge_expiring_resources(&msg, true)?; self.charge_sending_fee(delay)?; - self.charge_for_dispatch_stash_hold(delay)?; let msg_id = self @@ -804,13 +874,13 @@ impl Externalities for Ext { ) -> Result { self.check_forbidden_destination(msg.destination())?; self.check_message_value(msg.value())?; - self.check_gas_limit(msg.gas_limit())?; + // TODO: unify logic around different source of gas (may be origin msg, + // or reservation) in order to implement #1828. + self.check_reservation_gas_limit_for_delayed_sending(&id, delay)?; // TODO: gasful sending (#1828) self.charge_message_value(msg.value())?; self.charge_sending_fee(delay)?; - self.charge_for_dispatch_stash_hold(delay)?; - self.context.gas_reserver.mark_used(id)?; let msg_id = self @@ -828,7 +898,7 @@ impl Externalities for Ext { // TODO: Consider per byte charge (issue #2255). fn reply_commit(&mut self, msg: ReplyPacket) -> Result { self.check_forbidden_destination(self.context.message_context.reply_destination())?; - self.safe_gasfull_sends(&msg)?; + self.safe_gasfull_sends(&msg, 0)?; self.charge_expiring_resources(&msg, false)?; self.charge_sending_fee(0)?; @@ -1120,11 +1190,9 @@ impl Externalities for Ext { delay: u32, ) -> Result<(MessageId, ProgramId), Self::FallibleError> { // We don't check for forbidden destination here, since dest is always unique and almost impossible to match SYSTEM_ID - - self.safe_gasfull_sends(&packet)?; + self.safe_gasfull_sends(&packet, delay)?; self.charge_expiring_resources(&packet, true)?; self.charge_sending_fee(delay)?; - self.charge_for_dispatch_stash_hold(delay)?; let code_hash = packet.code_id(); @@ -1166,7 +1234,7 @@ impl Externalities for Ext { Ok((&self.context.random_data.0, self.context.random_data.1)) } - fn forbidden_funcs(&self) -> &BTreeSet { + fn forbidden_funcs(&self) -> &BTreeSet { &self.context.forbidden_funcs } } diff --git a/core-processor/src/lib.rs b/core-processor/src/lib.rs index 3e89e3e2ae0..9b63cd17dab 100644 --- a/core-processor/src/lib.rs +++ b/core-processor/src/lib.rs @@ -22,6 +22,7 @@ #![warn(missing_docs)] #![cfg_attr(feature = "strict", deny(warnings))] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] extern crate alloc; diff --git a/core/src/code.rs b/core/src/code.rs index 86bdc3df438..1561fb8c321 100644 --- a/core/src/code.rs +++ b/core/src/code.rs @@ -27,6 +27,7 @@ use alloc::{collections::BTreeSet, vec, vec::Vec}; use gear_wasm_instrument::{ parity_wasm::{ self, + builder::ModuleBuilder, elements::{ExportEntry, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, Module}, }, wasm_instrument::{ @@ -295,6 +296,22 @@ fn check_start_section(module: &Module) -> Result<(), CodeError> { } } +fn export_stack_height(module: Module) -> Module { + let globals = module + .global_section() + .expect("Global section must be create by `inject_stack_limiter` before") + .entries() + .len(); + ModuleBuilder::new() + .with_module(module) + .export() + .field("__gear_stack_height") + .internal() + .global(globals as u32 - 1) + .build() + .build() +} + /// Configuration for `Code::try_new_mock_`. /// By default all checks enabled. pub struct TryNewCodeConfig { @@ -302,6 +319,8 @@ pub struct TryNewCodeConfig { pub version: u32, /// Stack height limit pub stack_height: Option, + /// Export `__gear_stack_height` global + pub export_stack_height: bool, /// Check exports (wasm contains init or handle exports) pub check_exports: bool, /// Check and canonize stack end @@ -319,6 +338,7 @@ impl Default for TryNewCodeConfig { Self { version: 1, stack_height: None, + export_stack_height: false, check_exports: true, check_and_canonize_stack_end: true, check_mut_global_exports: true, @@ -398,10 +418,20 @@ impl Code { } if let Some(stack_limit) = config.stack_height { + let globals = config.export_stack_height.then(|| module.globals_space()); + module = wasm_instrument::inject_stack_limiter(module, stack_limit).map_err(|err| { log::trace!("Failed to inject stack height limits: {err}"); CodeError::StackLimitInjection })?; + + if let Some(globals_before) = globals { + // ensure stack limiter injector has created global + let globals_after = module.globals_space(); + assert_eq!(globals_after, globals_before + 1); + + module = export_stack_height(module); + } } if let Some(mut get_gas_rules) = get_gas_rules { diff --git a/core/src/env.rs b/core/src/env.rs index faf502a0dc6..7f89688cd7e 100644 --- a/core/src/env.rs +++ b/core/src/env.rs @@ -28,7 +28,7 @@ use crate::{ use alloc::collections::BTreeSet; use core::{fmt::Display, mem}; use gear_core_errors::{ReplyCode, SignalCode}; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; /// Lock for the payload of the incoming/currently executing message. /// @@ -174,10 +174,10 @@ impl From<(&mut MessageContext, &mut PayloadSliceLock)> for UnlockPayloadBound { /// use by an executing program to trigger state transition /// in runtime. pub trait Externalities { - /// An error issued in infallible sys-call. + /// An error issued in infallible syscall. type UnrecoverableError; - /// An error issued in fallible sys-call. + /// An error issued in fallible syscall. type FallibleError; /// An error issued during allocation. @@ -324,11 +324,11 @@ pub trait Externalities { /// like lock. /// /// Due to details of implementation of the runtime which executes gear - /// sys-calls inside wasm execution environment (either wasmi or sp_sandbox), - /// to prevent additional memory allocation on payload read op, we give - /// ownership over payload to the caller. Giving ownership over payload actually - /// means, that the payload value in the currently executed message will become - /// empty. To prevent from the risk of payload being not "returned" back to the + /// syscalls inside wasm execution environment, to prevent additional memory + /// allocation on payload read op, we give ownership over payload to the caller. + /// Giving ownership over payload actually means, that the payload value in the + /// currently executed message will become empty. + /// To prevent from the risk of payload being not "returned" back to the /// message a [`Externalities::unlock_payload`] is introduced. For more info, /// read docs to [`PayloadSliceLock`], [`DropPayloadLockBound`], /// [`UnlockPayloadBound`], [`PayloadSliceAccess`] types and their methods. @@ -396,5 +396,5 @@ pub trait Externalities { ) -> Result<(), Self::FallibleError>; /// Return the set of functions that are forbidden to be called. - fn forbidden_funcs(&self) -> &BTreeSet; + fn forbidden_funcs(&self) -> &BTreeSet; } diff --git a/core/src/ids.rs b/core/src/ids.rs index 7de67885dcf..8e24c1174f3 100644 --- a/core/src/ids.rs +++ b/core/src/ids.rs @@ -105,7 +105,7 @@ macro_rules! declare_id { } impl core::fmt::Display for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let len = self.0.len(); let median = (len + 1) / 2; @@ -132,7 +132,7 @@ macro_rules! declare_id { } impl core::fmt::Debug for $name { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Display::fmt(self, f) } } diff --git a/core/src/lib.rs b/core/src/lib.rs index 2458f0a4df7..cd9b6c45654 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -24,6 +24,7 @@ #![warn(missing_docs)] #![cfg_attr(feature = "strict", deny(warnings))] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] extern crate alloc; diff --git a/core/src/memory.rs b/core/src/memory.rs index 2cce25add66..10bb65bd74f 100644 --- a/core/src/memory.rs +++ b/core/src/memory.rs @@ -130,7 +130,7 @@ impl Decode for PageBuf { impl EncodeLike for PageBuf {} impl Debug for PageBuf { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "PageBuf({:?}..{:?})", diff --git a/core/src/message/mod.rs b/core/src/message/mod.rs index 186e1bd3b03..53d0dbd3405 100644 --- a/core/src/message/mod.rs +++ b/core/src/message/mod.rs @@ -48,7 +48,7 @@ pub use user::{UserMessage, UserStoredMessage}; use super::buffer::LimitedVec; use core::fmt::Display; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; /// Max payload size which one message can have (8 MiB). pub const MAX_PAYLOAD_SIZE: usize = 8 * 1024 * 1024; @@ -186,20 +186,20 @@ impl DispatchKind { matches!(self, Self::Signal) } - /// Sys-calls that are not allowed to be called for the dispatch kind. - pub fn forbidden_funcs(&self) -> BTreeSet { + /// Syscalls that are not allowed to be called for the dispatch kind. + pub fn forbidden_funcs(&self) -> BTreeSet { match self { DispatchKind::Signal => [ - SysCallName::Source, - SysCallName::Reply, - SysCallName::ReplyPush, - SysCallName::ReplyCommit, - SysCallName::ReplyCommitWGas, - SysCallName::ReplyInput, - SysCallName::ReplyInputWGas, - SysCallName::ReservationReply, - SysCallName::ReservationReplyCommit, - SysCallName::SystemReserveGas, + SyscallName::Source, + SyscallName::Reply, + SyscallName::ReplyPush, + SyscallName::ReplyCommit, + SyscallName::ReplyCommitWGas, + SyscallName::ReplyInput, + SyscallName::ReplyInputWGas, + SyscallName::ReservationReply, + SyscallName::ReservationReplyCommit, + SyscallName::SystemReserveGas, ] .into(), _ => Default::default(), diff --git a/core/src/reservation.rs b/core/src/reservation.rs index 31718e8bab0..4e7f2f7a49f 100644 --- a/core/src/reservation.rs +++ b/core/src/reservation.rs @@ -151,6 +151,15 @@ impl GasReserver { } } + /// Returns amount of gas in reservation, if exists. + pub fn limit_of(&self, reservation_id: &ReservationId) -> Option { + self.states.get(reservation_id).and_then(|v| match v { + GasReservationState::Exists { amount, .. } + | GasReservationState::Created { amount, .. } => Some(*amount), + _ => None, + }) + } + /// Reserves gas. /// /// Creates a new reservation and returns its id. diff --git a/examples/calc-hash/Cargo.toml b/examples/calc-hash/Cargo.toml index 09e0074e4c4..3a68890fbf5 100644 --- a/examples/calc-hash/Cargo.toml +++ b/examples/calc-hash/Cargo.toml @@ -8,6 +8,7 @@ homepage.workspace = true repository.workspace = true [dependencies] +gstd.workspace = true parity-scale-codec.workspace = true sha2 = { version = "0.10.8", default-features = false } diff --git a/examples/constructor/src/builder.rs b/examples/constructor/src/builder.rs index 344eae01c72..bf871191605 100644 --- a/examples/constructor/src/builder.rs +++ b/examples/constructor/src/builder.rs @@ -325,4 +325,8 @@ impl Calls { pub fn infinite_loop(self) -> Self { self.add_call(Call::Loop) } + + pub fn system_reserve_gas(self, gas: impl Into>) -> Self { + self.add_call(Call::SystemReserveGas(gas.into())) + } } diff --git a/examples/constructor/src/call.rs b/examples/constructor/src/call.rs index 06c0fde0edb..2f14a20030c 100644 --- a/examples/constructor/src/call.rs +++ b/examples/constructor/src/call.rs @@ -42,6 +42,7 @@ pub enum Call { Wake(Arg<[u8; 32]>), MessageId, Loop, + SystemReserveGas(Arg), } #[cfg(not(feature = "wasm-wrapper"))] @@ -317,6 +318,17 @@ mod wasm { Some(msg::id().encode()) } + fn system_reserve_gas(self) -> Option> { + let Self::SystemReserveGas(gas) = self else { + unreachable!() + }; + + let gas = gas.value(); + exec::system_reserve_gas(gas).expect("Failed to reserve gas"); + + None + } + pub(crate) fn process(self, previous: Option) -> CallResult { debug!("\t[CONSTRUCTOR] >> Processing {self:?}"); let call = self.clone(); @@ -347,6 +359,7 @@ mod wasm { Call::MessageId => self.message_id(), #[allow(clippy::empty_loop)] Call::Loop => loop {}, + Call::SystemReserveGas(..) => self.system_reserve_gas(), }; (call, value) diff --git a/examples/constructor/src/scheme/demo_exit_handle.rs b/examples/constructor/src/scheme/demo_exit_handle.rs index a7b6a508057..4a6ba5a3fb2 100644 --- a/examples/constructor/src/scheme/demo_exit_handle.rs +++ b/examples/constructor/src/scheme/demo_exit_handle.rs @@ -22,6 +22,10 @@ pub fn handle_reply() -> Calls { Calls::builder().noop() } +pub fn handle_signal() -> Calls { + Calls::builder().noop() +} + pub fn scheme() -> Scheme { - Scheme::predefined(init(), handle(), handle_reply()) + Scheme::predefined(init(), handle(), handle_reply(), handle_signal()) } diff --git a/examples/constructor/src/scheme/demo_exit_init.rs b/examples/constructor/src/scheme/demo_exit_init.rs index 74a6af4f6bf..9e191c164d4 100644 --- a/examples/constructor/src/scheme/demo_exit_init.rs +++ b/examples/constructor/src/scheme/demo_exit_init.rs @@ -29,6 +29,15 @@ pub fn handle_reply() -> Calls { Calls::builder().noop() } +pub fn handle_signal() -> Calls { + Calls::builder().noop() +} + pub fn scheme(send_before_exit: bool) -> Scheme { - Scheme::predefined(init(send_before_exit), handle(), handle_reply()) + Scheme::predefined( + init(send_before_exit), + handle(), + handle_reply(), + handle_signal(), + ) } diff --git a/examples/constructor/src/scheme/demo_ping.rs b/examples/constructor/src/scheme/demo_ping.rs index c0601efbff5..d05bee2fa01 100644 --- a/examples/constructor/src/scheme/demo_ping.rs +++ b/examples/constructor/src/scheme/demo_ping.rs @@ -29,6 +29,10 @@ pub fn handle_reply() -> Calls { Calls::builder().noop() } +pub fn handle_signal() -> Calls { + Calls::builder().noop() +} + pub fn scheme() -> Scheme { - Scheme::predefined(init(), handle(), handle_reply()) + Scheme::predefined(init(), handle(), handle_reply(), handle_signal()) } diff --git a/examples/constructor/src/scheme/demo_proxy_with_gas.rs b/examples/constructor/src/scheme/demo_proxy_with_gas.rs index 70b7e2a1ba5..7b893716d5e 100644 --- a/examples/constructor/src/scheme/demo_proxy_with_gas.rs +++ b/examples/constructor/src/scheme/demo_proxy_with_gas.rs @@ -65,6 +65,15 @@ pub fn handle_reply() -> Calls { ) } +pub fn handle_signal() -> Calls { + Calls::builder().noop() +} + pub fn scheme(destination: [u8; 32], delay: u32) -> Scheme { - Scheme::predefined(init(destination, delay), handle(), handle_reply()) + Scheme::predefined( + init(destination, delay), + handle(), + handle_reply(), + handle_signal(), + ) } diff --git a/examples/constructor/src/scheme/demo_reply_deposit.rs b/examples/constructor/src/scheme/demo_reply_deposit.rs index a7313e6b23a..5de12a39092 100644 --- a/examples/constructor/src/scheme/demo_reply_deposit.rs +++ b/examples/constructor/src/scheme/demo_reply_deposit.rs @@ -31,10 +31,15 @@ pub fn handle_reply(checker: [u8; 32]) -> Calls { .send_wgas(checker, Arg::bytes(SUCCESS_MESSAGE), 10_000) } +pub fn handle_signal() -> Calls { + Calls::builder().noop() +} + pub fn scheme(checker: [u8; 32], destination: [u8; 32], gas_to_send: u64) -> Scheme { Scheme::predefined( init(), handle(destination, gas_to_send), handle_reply(checker), + handle_signal(), ) } diff --git a/examples/constructor/src/scheme/demo_wait_init_exit_reply.rs b/examples/constructor/src/scheme/demo_wait_init_exit_reply.rs index bea2b68c1b6..e44eb574f62 100644 --- a/examples/constructor/src/scheme/demo_wait_init_exit_reply.rs +++ b/examples/constructor/src/scheme/demo_wait_init_exit_reply.rs @@ -42,6 +42,10 @@ pub fn handle_reply() -> Calls { .exit(source_var) } +pub fn handle_signal() -> Calls { + Calls::builder().noop() +} + pub fn scheme() -> Scheme { - Scheme::predefined(init(), handle(), handle_reply()) + Scheme::predefined(init(), handle(), handle_reply(), handle_signal()) } diff --git a/examples/constructor/src/scheme/mod.rs b/examples/constructor/src/scheme/mod.rs index a13a3379d1a..280c5682875 100644 --- a/examples/constructor/src/scheme/mod.rs +++ b/examples/constructor/src/scheme/mod.rs @@ -26,7 +26,7 @@ pub enum Scheme { /// /// Better to use this scheme if you need common-purpose program that /// executes the same commands across different incoming payloads. - Predefined(Vec, Vec, Vec), + Predefined(Vec, Vec, Vec, Vec), } impl Scheme { @@ -38,8 +38,18 @@ impl Scheme { Self::Direct(init.calls()) } - pub fn predefined(init: Calls, handle: Calls, handle_reply: Calls) -> Self { - Self::Predefined(init.calls(), handle.calls(), handle_reply.calls()) + pub fn predefined( + init: Calls, + handle: Calls, + handle_reply: Calls, + handle_signal: Calls, + ) -> Self { + Self::Predefined( + init.calls(), + handle.calls(), + handle_reply.calls(), + handle_signal.calls(), + ) } pub fn init(&self) -> &Vec { @@ -51,14 +61,21 @@ impl Scheme { pub fn handle(&self) -> Option<&Vec> { match self { - Self::Predefined(_, handle, _) => Some(handle), + Self::Predefined(_, handle, _, _) => Some(handle), _ => None, } } pub fn handle_reply(&self) -> Option<&Vec> { match self { - Self::Predefined(_, _, handle_reply) => Some(handle_reply), + Self::Predefined(_, _, handle_reply, _) => Some(handle_reply), + _ => None, + } + } + + pub fn handle_signal(&self) -> Option<&Vec> { + match self { + Self::Predefined(_, _, _, handle_signal) => Some(handle_signal), _ => None, } } diff --git a/examples/constructor/src/wasm.rs b/examples/constructor/src/wasm.rs index 4461952ba8e..3848d0516ef 100644 --- a/examples/constructor/src/wasm.rs +++ b/examples/constructor/src/wasm.rs @@ -34,3 +34,8 @@ extern "C" fn handle() { extern "C" fn handle_reply() { process_fn(Scheme::handle_reply); } + +#[no_mangle] +extern "C" fn handle_signal() { + process_fn(Scheme::handle_signal); +} diff --git a/examples/delayed-reservation-sender/Cargo.toml b/examples/delayed-reservation-sender/Cargo.toml new file mode 100644 index 00000000000..e313b5080da --- /dev/null +++ b/examples/delayed-reservation-sender/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "demo-delayed-reservation-sender" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gstd.workspace = true + +[build-dependencies] +gear-wasm-builder.workspace = true + +[features] +debug = ["gstd/debug"] +default = ["std"] +std = [] diff --git a/examples/delayed-reservation-sender/build.rs b/examples/delayed-reservation-sender/build.rs new file mode 100644 index 00000000000..4c502a3ddee --- /dev/null +++ b/examples/delayed-reservation-sender/build.rs @@ -0,0 +1,21 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +fn main() { + gear_wasm_builder::build(); +} diff --git a/examples/delayed-reservation-sender/src/lib.rs b/examples/delayed-reservation-sender/src/lib.rs new file mode 100644 index 00000000000..0e63cbb1ca8 --- /dev/null +++ b/examples/delayed-reservation-sender/src/lib.rs @@ -0,0 +1,50 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![no_std] + +use gstd::codec::{Decode, Encode}; + +#[cfg(feature = "std")] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} + +#[cfg(feature = "std")] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +pub const SENDING_EXPECT: &str = "Failed to send delayed message from reservation"; + +#[derive(Encode, Decode, Debug, Clone, Copy)] +#[codec(crate = gstd::codec)] +pub enum ReservationSendingShowcase { + ToSourceInPlace { + reservation_amount: u64, + reservation_delay: u32, + sending_delay: u32, + }, + ToSourceAfterWait { + reservation_amount: u64, + reservation_delay: u32, + wait_for: u32, + sending_delay: u32, + }, +} + +#[cfg(not(feature = "std"))] +mod wasm; diff --git a/examples/delayed-reservation-sender/src/wasm.rs b/examples/delayed-reservation-sender/src/wasm.rs new file mode 100644 index 00000000000..8887f1203ec --- /dev/null +++ b/examples/delayed-reservation-sender/src/wasm.rs @@ -0,0 +1,75 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{ReservationSendingShowcase, SENDING_EXPECT}; +use gstd::{exec, msg, prelude::*, ReservationId}; + +static mut CALLED_BEFORE: bool = false; +static mut RESERVATION_ID: Option = None; + +#[no_mangle] +extern "C" fn handle() { + let showcase = msg::load().expect("Failed to load request"); + + match showcase { + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount, + reservation_delay, + sending_delay, + } => { + let reservation_id = ReservationId::reserve(reservation_amount, reservation_delay) + .expect("Failed to reserve gas"); + + msg::send_bytes_delayed_from_reservation( + reservation_id, + msg::source(), + [], + 0, + sending_delay, + ) + .expect(SENDING_EXPECT); + } + ReservationSendingShowcase::ToSourceAfterWait { + reservation_amount, + reservation_delay, + wait_for, + sending_delay, + } => { + if unsafe { !CALLED_BEFORE } { + let reservation_id = ReservationId::reserve(reservation_amount, reservation_delay) + .expect("Failed to reserve gas"); + + unsafe { + CALLED_BEFORE = true; + RESERVATION_ID = Some(reservation_id); + } + + exec::wait_for(wait_for); + } + + msg::send_bytes_delayed_from_reservation( + unsafe { RESERVATION_ID.expect("Unset") }, + msg::source(), + [], + 0, + sending_delay, + ) + .expect(SENDING_EXPECT); + } + } +} diff --git a/examples/delayed-sender/src/lib.rs b/examples/delayed-sender/src/lib.rs index a2bb360b9a4..7bd89b91abb 100644 --- a/examples/delayed-sender/src/lib.rs +++ b/examples/delayed-sender/src/lib.rs @@ -26,5 +26,7 @@ mod code { #[cfg(feature = "wasm-wrapper")] pub use code::WASM_BINARY_OPT as WASM_BINARY; +pub const DELAY: u32 = 100; + #[cfg(not(feature = "wasm-wrapper"))] mod wasm; diff --git a/examples/delayed-sender/src/wasm.rs b/examples/delayed-sender/src/wasm.rs index 395f2e7e03e..16e8a6a1b66 100644 --- a/examples/delayed-sender/src/wasm.rs +++ b/examples/delayed-sender/src/wasm.rs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use crate::DELAY; use gstd::{exec, msg, MessageId}; static mut MID: Option = None; @@ -30,6 +31,19 @@ extern "C" fn init() { #[no_mangle] extern "C" fn handle() { + let size = msg::size(); + + if size == 0 { + // Another case of delayed sending, representing possible panic case of + // sending delayed gasless messages. + msg::send_bytes_delayed(msg::source(), [], 0, DELAY).expect("Failed to send msg"); + + msg::send_bytes_delayed(msg::source(), [], 0, DELAY).expect("Failed to send msg"); + + return; + } + + // Common delayed sender case. if let Some(message_id) = unsafe { MID.take() } { let delay: u32 = msg::load().unwrap(); diff --git a/examples/new-meta/io/Cargo.toml b/examples/new-meta/io/Cargo.toml index 6bd4fedeaed..4caadf43edd 100644 --- a/examples/new-meta/io/Cargo.toml +++ b/examples/new-meta/io/Cargo.toml @@ -8,6 +8,7 @@ homepage.workspace = true repository.workspace = true [dependencies] +gstd.workspace = true gmeta.workspace = true scale-info.workspace = true parity-scale-codec.workspace = true diff --git a/examples/program-factory/src/lib.rs b/examples/program-factory/src/lib.rs index ce33f8aca40..97bc4437196 100644 --- a/examples/program-factory/src/lib.rs +++ b/examples/program-factory/src/lib.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! An example of `create_program_with_gas` sys-call. +//! An example of `create_program_with_gas` syscall. //! -//! The program is mainly used for testing the sys-call logic in pallet `gear` tests. +//! The program is mainly used for testing the syscall logic in pallet `gear` tests. //! It works as a program factory: depending on input type it sends program creation //! request (message). diff --git a/examples/program-factory/src/wasm.rs b/examples/program-factory/src/wasm.rs index f0cd48f1db5..fa413847929 100644 --- a/examples/program-factory/src/wasm.rs +++ b/examples/program-factory/src/wasm.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! An example of `create_program_with_gas` sys-call. +//! An example of `create_program_with_gas` syscall. //! -//! The program is mainly used for testing the sys-call logic in pallet `gear` tests. +//! The program is mainly used for testing the syscall logic in pallet `gear` tests. //! It works as a program factory: depending on input type it sends program creation //! request (message). diff --git a/examples/signal-wait/Cargo.toml b/examples/signal-wait/Cargo.toml new file mode 100644 index 00000000000..a69c63c4d01 --- /dev/null +++ b/examples/signal-wait/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "demo-signal-wait" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gstd.workspace = true + +[build-dependencies] +gear-wasm-builder.workspace = true + +[features] +debug = ["gstd/debug"] +default = ["std"] +std = [] diff --git a/examples/signal-wait/build.rs b/examples/signal-wait/build.rs new file mode 100644 index 00000000000..4c502a3ddee --- /dev/null +++ b/examples/signal-wait/build.rs @@ -0,0 +1,21 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +fn main() { + gear_wasm_builder::build(); +} diff --git a/examples/signal-wait/src/lib.rs b/examples/signal-wait/src/lib.rs new file mode 100644 index 00000000000..43f5e17c84a --- /dev/null +++ b/examples/signal-wait/src/lib.rs @@ -0,0 +1,30 @@ +// This file is part of Gear. + +// Copyright (C) 2022-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![no_std] + +#[cfg(feature = "std")] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} + +#[cfg(feature = "std")] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +#[cfg(not(feature = "std"))] +mod wasm; diff --git a/examples/signal-wait/src/wasm.rs b/examples/signal-wait/src/wasm.rs new file mode 100644 index 00000000000..d00fb71420a --- /dev/null +++ b/examples/signal-wait/src/wasm.rs @@ -0,0 +1,39 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use gstd::{exec, prelude::*}; + +static mut FIRST_EXEC: bool = true; + +#[no_mangle] +extern "C" fn handle() { + if unsafe { FIRST_EXEC } { + unsafe { + FIRST_EXEC = false; + } + exec::system_reserve_gas(1_000_000_000).unwrap(); + exec::wait_for(1); + } else { + panic!(); + } +} + +#[no_mangle] +extern "C" fn handle_signal() { + exec::wait(); +} diff --git a/examples/sys-calls/Cargo.toml b/examples/syscalls/Cargo.toml similarity index 100% rename from examples/sys-calls/Cargo.toml rename to examples/syscalls/Cargo.toml diff --git a/examples/sys-calls/build.rs b/examples/syscalls/build.rs similarity index 100% rename from examples/sys-calls/build.rs rename to examples/syscalls/build.rs diff --git a/examples/sys-calls/src/lib.rs b/examples/syscalls/src/lib.rs similarity index 100% rename from examples/sys-calls/src/lib.rs rename to examples/syscalls/src/lib.rs diff --git a/examples/sys-calls/src/wasm.rs b/examples/syscalls/src/wasm.rs similarity index 100% rename from examples/sys-calls/src/wasm.rs rename to examples/syscalls/src/wasm.rs diff --git a/galloc/src/lib.rs b/galloc/src/lib.rs index dd3c6638f3f..8b89cf8abf2 100644 --- a/galloc/src/lib.rs +++ b/galloc/src/lib.rs @@ -19,6 +19,7 @@ #![no_std] #![cfg_attr(feature = "strict", deny(warnings))] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] // until https://github.com/alexcrichton/dlmalloc-rs/pull/26 is merged #[cfg(not(windows))] diff --git a/gcli/Cargo.toml b/gcli/Cargo.toml index 46d21f695c0..a562d8cefff 100644 --- a/gcli/Cargo.toml +++ b/gcli/Cargo.toml @@ -40,8 +40,6 @@ clap = { workspace = true, features = ["derive"] } thiserror.workspace = true tokio = { workspace = true, features = [ "full" ] } whoami.workspace = true -core-processor = { workspace = true, features = [ "std" ] } -gear-lazy-pages-interface = { workspace = true, features = [ "std" ] } reqwest = { workspace = true, default-features = false, features = [ "json", "rustls-tls" ] } etc.workspace = true sp-io = { workspace = true, features = [ "std" ] } diff --git a/gcli/src/cmd/program.rs b/gcli/src/cmd/program.rs index b3ccfdd9b0e..80db325eb2b 100644 --- a/gcli/src/cmd/program.rs +++ b/gcli/src/cmd/program.rs @@ -105,7 +105,6 @@ impl Program { Ok(()) } - /// Display meta. fn meta(path: &PathBuf, name: &Option) -> Result<()> { let ext = path .extension() diff --git a/gcli/src/meta/executor.rs b/gcli/src/meta/executor.rs index be76fd537c4..42381ae2328 100644 --- a/gcli/src/meta/executor.rs +++ b/gcli/src/meta/executor.rs @@ -23,8 +23,6 @@ use anyhow::{anyhow, Result}; use wasmi::{AsContextMut, Engine, Extern, Linker, Memory, MemoryType, Module, Store}; -const PAGE_STORAGE_PREFIX: [u8; 32] = *b"gcligcligcligcligcligcligcligcli"; - /// HostState for the WASM executor #[derive(Default)] pub struct HostState { @@ -39,10 +37,6 @@ pub fn call_metadata(wasm: &[u8]) -> Result> { /// Executes the WASM code. fn execute(wasm: &[u8], method: &str) -> Result> { - assert!(gear_lazy_pages_interface::try_to_enable_lazy_pages( - PAGE_STORAGE_PREFIX - )); - let engine = Engine::default(); let module = Module::new(&engine, wasm).unwrap(); diff --git a/gclient/src/lib.rs b/gclient/src/lib.rs index 8a3321300bb..1a09bd621a8 100644 --- a/gclient/src/lib.rs +++ b/gclient/src/lib.rs @@ -129,6 +129,7 @@ #![warn(missing_docs)] #![cfg_attr(feature = "strict", deny(warnings))] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] mod api; mod utils; diff --git a/gcore/src/errors.rs b/gcore/src/errors.rs index d6e84ad05d0..0609b2ce0c0 100644 --- a/gcore/src/errors.rs +++ b/gcore/src/errors.rs @@ -35,7 +35,7 @@ pub enum Error { } impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::SyscallUsage => write!(f, "syscall usage error"), Error::Ext(e) => write!(f, "{}", e), diff --git a/gcore/src/lib.rs b/gcore/src/lib.rs index ad3e03baf6a..bcc7ce64e02 100644 --- a/gcore/src/lib.rs +++ b/gcore/src/lib.rs @@ -66,10 +66,9 @@ #![warn(missing_docs)] #![cfg_attr(feature = "strict", deny(warnings))] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] #![doc(test(attr(deny(warnings), allow(unused_variables, unused_assignments))))] -extern crate alloc; - pub mod errors; pub mod exec; pub mod msg; diff --git a/gmeta/Cargo.toml b/gmeta/Cargo.toml index 7f79855f0cf..a2f56a365ab 100644 --- a/gmeta/Cargo.toml +++ b/gmeta/Cargo.toml @@ -14,7 +14,7 @@ repository.workspace = true scale-info.workspace = true blake2-rfc.workspace = true hex = { workspace = true, features = ["alloc"] } -gmeta-codegen = { path = "codegen", optional = true } +gmeta-codegen = { workspace = true, optional = true } derive_more.workspace = true [dev-dependencies] diff --git a/gmeta/src/lib.rs b/gmeta/src/lib.rs index 369e8baa2fc..e8db65678ea 100644 --- a/gmeta/src/lib.rs +++ b/gmeta/src/lib.rs @@ -209,6 +209,7 @@ #![no_std] #![warn(missing_docs)] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] extern crate alloc; diff --git a/gsdk/api-gen/Cargo.toml b/gsdk/api-gen/Cargo.toml index 5fd3ee11276..6ab0e2fc2ed 100644 --- a/gsdk/api-gen/Cargo.toml +++ b/gsdk/api-gen/Cargo.toml @@ -25,4 +25,4 @@ subxt-codegen.workspace = true # TODO: inherit from workspace # # use "2.0.15" because subxt-codegen-0.29.0 requires it. -syn = { version = "2.0.38", features = ["full", "parsing"] } +syn = { version = "2.0.39", features = ["full", "parsing"] } diff --git a/gsdk/codegen/Cargo.toml b/gsdk/codegen/Cargo.toml index 5d6fda229d1..ae54b23b204 100644 --- a/gsdk/codegen/Cargo.toml +++ b/gsdk/codegen/Cargo.toml @@ -14,6 +14,6 @@ repository.workspace = true proc-macro = true [dependencies] -syn = { workspace = true, features = ["full"] } +syn = { workspace = true, features = ["default", "full"] } quote.workspace = true proc-macro2.workspace = true diff --git a/gsdk/src/lib.rs b/gsdk/src/lib.rs index d97f0c4cff5..3ae38251b47 100644 --- a/gsdk/src/lib.rs +++ b/gsdk/src/lib.rs @@ -16,6 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] + //! Gear api pub use crate::{ api::Api, diff --git a/gstd/Cargo.toml b/gstd/Cargo.toml index 70c6033af9c..ab8ddff724f 100644 --- a/gstd/Cargo.toml +++ b/gstd/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true license.workspace = true [dependencies] +document-features = { version = "0.2.7", optional = true } galloc.workspace = true gcore = { workspace = true, features = ["codec"] } gstd-codegen = { path = "codegen" } @@ -13,7 +14,7 @@ gear-core-errors.workspace = true hashbrown.workspace = true bs58 = { workspace = true, features = ["alloc"] } hex = { workspace = true, features = ["alloc"] } -parity-scale-codec = { workspace = true, features = [ "derive" ] } +parity-scale-codec = { workspace = true, features = ["derive"] } primitive-types = { workspace = true, features = ["scale-info"] } scale-info = { workspace = true, features = ["derive"] } futures = { workspace = true, features = ["alloc"] } @@ -21,6 +22,32 @@ futures = { workspace = true, features = ["alloc"] } static_assertions.workspace = true [features] +#! ## Default features + default = ["panic-handler"] +## When enabled, a panic handler is provided by this crate. panic-handler = [] + +#! ## Nightly features + +## Enables all features below. +## These features depend on unstable Rust API and require nightly toolchain. +nightly = ["panic-messages", "oom-handler"] +## When enabled, additional context information is available from +## panic messages in debug mode. Relies on [`panic_info_message`][rust-66745]. +panic-messages = [] +## When enabled, an OOM error handler is provided. +## Relies on [`alloc_error_handler`][rust-51540], +oom-handler = [] + +#! ## Additional features + +## Enables debug logging; this heavily impacts gas cost +## and is therefore disabled by default. debug = ["galloc/debug", "gcore/debug"] + +#! [rust-66745]: https://github.com/rust-lang/rust/issues/66745 +#! [rust-51540]: https://github.com/rust-lang/rust/issues/51540 + +[package.metadata.docs.rs] +all-features = true diff --git a/gstd/codegen/src/lib.rs b/gstd/codegen/src/lib.rs index 71125b3e680..c3ff13bd24b 100644 --- a/gstd/codegen/src/lib.rs +++ b/gstd/codegen/src/lib.rs @@ -18,8 +18,6 @@ //! Provides macros for async runtime of Gear contracts. -extern crate proc_macro; - use core::fmt::Display; use proc_macro::TokenStream; use proc_macro2::Ident; @@ -77,7 +75,7 @@ impl MainAttrs { } impl Parse for MainAttrs { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let punctuated: Punctuated = Punctuated::parse_terminated(input)?; let mut attrs = MainAttrs { handle_reply: None, @@ -114,7 +112,7 @@ struct MainAttr { } impl Parse for MainAttr { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let name: Ident = input.parse()?; let _: Token![=] = input.parse()?; let path: Path = input.parse()?; diff --git a/gstd/src/async_runtime/signals.rs b/gstd/src/async_runtime/signals.rs index 5b4e946be63..7d8b3e979b8 100644 --- a/gstd/src/async_runtime/signals.rs +++ b/gstd/src/async_runtime/signals.rs @@ -89,7 +89,7 @@ impl WakeSignals { self.signals.contains_key(&reply_to) } - pub fn poll(&mut self, reply_to: MessageId, cx: &mut Context<'_>) -> ReplyPoll { + pub fn poll(&mut self, reply_to: MessageId, cx: &Context<'_>) -> ReplyPoll { match self.signals.remove(&reply_to) { None => ReplyPoll::None, Some(mut signal @ WakeSignal { payload: None, .. }) => { diff --git a/gstd/src/common/errors.rs b/gstd/src/common/errors.rs index 1b09d131992..71c8fb72a6b 100644 --- a/gstd/src/common/errors.rs +++ b/gstd/src/common/errors.rs @@ -63,7 +63,7 @@ impl Error { } impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Core(e) => fmt::Display::fmt(e, f), Error::Timeout(expected, now) => { diff --git a/gstd/src/common/handlers.rs b/gstd/src/common/handlers.rs index 9f1d3064534..b13ea53f285 100644 --- a/gstd/src/common/handlers.rs +++ b/gstd/src/common/handlers.rs @@ -25,43 +25,50 @@ //! debug and non-debug mode, for programs built in `wasm32` architecture. //! For `debug` mode it provides more extensive logging. -#[cfg(target_arch = "wasm32")] -use {crate::ext, core::alloc::Layout, core::panic::PanicInfo}; - -#[cfg(target_arch = "wasm32")] +#[cfg(feature = "oom-handler")] #[alloc_error_handler] -pub fn oom(_: Layout) -> ! { - ext::oom_panic() +pub fn oom(_: core::alloc::Layout) -> ! { + crate::ext::oom_panic() } -#[cfg(not(any(feature = "debug", debug_assertions)))] -#[cfg(target_arch = "wasm32")] -#[panic_handler] -pub fn panic(_: &PanicInfo) -> ! { - ext::panic("no info") -} +#[cfg(feature = "panic-handler")] +mod panic_handler { + use crate::ext; + use core::panic::PanicInfo; + + #[cfg(not(feature = "debug"))] + #[panic_handler] + pub fn panic(_: &PanicInfo) -> ! { + ext::panic("no info") + } -#[cfg(any(feature = "debug", debug_assertions))] -#[cfg(target_arch = "wasm32")] -#[panic_handler] -pub fn panic(panic_info: &PanicInfo) -> ! { - use crate::prelude::format; + #[cfg(feature = "debug")] + #[panic_handler] + pub fn panic(panic_info: &PanicInfo) -> ! { + use crate::prelude::format; + #[cfg(not(feature = "panic-messages"))] + let message = None::<&core::fmt::Arguments<'_>>; + #[cfg(feature = "panic-messages")] + let message = panic_info.message(); - let msg = match (panic_info.message(), panic_info.location()) { - (Some(msg), Some(loc)) => format!( - "'{:?}', {}:{}:{}", - msg, - loc.file(), - loc.line(), - loc.column() - ), - (Some(msg), None) => format!("'{msg:?}'"), - (None, Some(loc)) => { - format!("{}:{}:{}", loc.file(), loc.line(), loc.column()) - } - _ => ext::panic("no info"), - }; + let msg = match (message, panic_info.location()) { + (Some(msg), Some(loc)) => format!( + "'{:?}', {}:{}:{}", + msg, + loc.file(), + loc.line(), + loc.column() + ), + (Some(msg), None) => format!("'{msg:?}'"), + (None, Some(loc)) => { + format!("{}:{}:{}", loc.file(), loc.line(), loc.column()) + } + _ => ext::panic("no info"), + }; - crate::debug!("panic occurred: {msg}"); - ext::panic(&msg) + crate::debug!("panic occurred: {msg}"); + ext::panic(&msg) + } } +#[cfg(feature = "panic-handler")] +pub use panic_handler::*; diff --git a/gstd/src/common/mod.rs b/gstd/src/common/mod.rs index ce7c1985428..4ffb4ad9ebe 100644 --- a/gstd/src/common/mod.rs +++ b/gstd/src/common/mod.rs @@ -19,6 +19,6 @@ //! Common modules for each Gear smart contract. pub mod errors; -#[cfg(feature = "panic-handler")] +#[cfg(target_arch = "wasm32")] mod handlers; pub mod primitives; diff --git a/gstd/src/lib.rs b/gstd/src/lib.rs index e43cb0b80df..6749a219a01 100644 --- a/gstd/src/lib.rs +++ b/gstd/src/lib.rs @@ -28,6 +28,11 @@ //! asynchronous programming primitives, arbitrary types encoding/decoding, //! providing convenient instruments for creating programs from programs, etc. //! +//! # Crate features +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] //! # Examples //! //! Decode input payload using a custom type: @@ -124,12 +129,16 @@ #![no_std] #![warn(missing_docs)] #![cfg_attr( - all(target_arch = "wasm32", any(feature = "debug", debug_assertions)), + all(target_arch = "wasm32", feature = "panic-messages",), feature(panic_info_message) )] -#![cfg_attr(target_arch = "wasm32", feature(alloc_error_handler))] +#![cfg_attr( + all(target_arch = "wasm32", feature = "oom-handler"), + feature(alloc_error_handler) +)] #![cfg_attr(feature = "strict", deny(warnings))] #![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] #![doc(test(attr(deny(warnings), allow(unused_variables, unused_assignments))))] extern crate alloc; diff --git a/gstd/src/msg/async.rs b/gstd/src/msg/async.rs index 9cda3082b6e..140f0635844 100644 --- a/gstd/src/msg/async.rs +++ b/gstd/src/msg/async.rs @@ -32,7 +32,7 @@ use core::{ use futures::future::FusedFuture; use scale_info::scale::Decode; -fn poll(waiting_reply_to: MessageId, cx: &mut Context<'_>, f: F) -> Poll> +fn poll(waiting_reply_to: MessageId, cx: &Context<'_>, f: F) -> Poll> where F: Fn(Vec) -> Result, { diff --git a/gsys/src/lib.rs b/gsys/src/lib.rs index 814fd2261eb..6cb4cdeb15c 100644 --- a/gsys/src/lib.rs +++ b/gsys/src/lib.rs @@ -34,9 +34,14 @@ pub type BlockCount = u32; /// Represents block number type. pub type BlockTimestamp = u64; -/// Represents byte type. +/// Represents byte type, which is a start of a buffer. pub type BufferStart = u8; +/// Represents byte type, which is a start of a sized buffer. +/// +/// This usually goes along with `Length` param.` +pub type SizedBufferStart = u8; + /// Represents gas type. pub type Gas = u64; @@ -46,8 +51,8 @@ pub type Handle = u32; /// Represents hash type. pub type Hash = [u8; 32]; -/// Represents index type. -pub type Index = u32; +/// Represents offset type. +pub type Offset = u32; /// Represents length type. pub type Length = u32; @@ -410,7 +415,7 @@ extern "C" { /// /// Arguments type: /// - `version`: `u32` defining version of vars to get. - /// - `settings`: `mut ptr` for buffer to store requested version of vars. + /// - `vars`: `mut ptr` for buffer to store requested version of vars. pub fn gr_env_vars(version: u32, vars: *mut BufferStart); /// Infallible `gr_block_height` get syscall. @@ -439,9 +444,9 @@ extern "C" { /// and program id. pub fn gr_create_program_wgas( cid_value: *const HashWithValue, - salt: *const BufferStart, + salt: *const SizedBufferStart, salt_len: Length, - payload: *const BufferStart, + payload: *const SizedBufferStart, payload_len: Length, gas_limit: Gas, delay: BlockNumber, @@ -456,15 +461,14 @@ extern "C" { /// - `salt_len`: `u32` length of the salt buffer. /// - `payload`: `const ptr` for the begging of the payload buffer. /// - `payload_len`: `u32` length of the payload buffer. - /// - `gas_limit`: `u64` defining gas limit for sending. /// - `delay`: `u32` amount of blocks to delay. /// - `err_mid_pid`: `mut ptr` for concatenated error code, message id /// and program id. pub fn gr_create_program( cid_value: *const HashWithValue, - salt: *const BufferStart, + salt: *const SizedBufferStart, salt_len: Length, - payload: *const BufferStart, + payload: *const SizedBufferStart, payload_len: Length, delay: BlockNumber, err_mid_pid: *mut ErrorWithTwoHashes, @@ -483,7 +487,7 @@ extern "C" { /// Arguments type: /// - `payload`: `const ptr` for the begging of the payload buffer. /// - `len`: `u32` length of the payload buffer. - pub fn gr_debug(payload: *const BufferStart, len: Length); + pub fn gr_debug(payload: *const SizedBufferStart, len: Length); /// Infallible `gr_panic` control syscall. /// @@ -492,7 +496,7 @@ extern "C" { /// Arguments type: /// - `payload`: `const ptr` for the begging of the payload buffer. /// - `len`: `u32` length of the payload buffer. - pub fn gr_panic(payload: *const BufferStart, len: Length) -> !; + pub fn gr_panic(payload: *const SizedBufferStart, len: Length) -> !; /// Infallible `gr_oom_panic` control syscall. pub fn gr_oom_panic() -> !; @@ -530,7 +534,7 @@ extern "C" { /// - `message_id`: `const ptr` for message id. pub fn gr_message_id(message_id: *mut Hash); - /// Fallible `gr_pay_program_rent` syscall. + /// Fallible `gr_pay_program_rent` control syscall. /// /// Arguments type: /// - `rent_pid`: `const ptr` for program id and rent value. @@ -557,11 +561,11 @@ extern "C" { /// Fallible `gr_read` get syscall. /// /// Arguments type: - /// - `at`: `u32` defining offset to read from. + /// - `at`: `u32` defining offset in the input buffer to read from. /// - `len`: `u32` length of the buffer to read. /// - `buffer`: `mut ptr` for buffer to store requested data. /// - `err`: `mut ptr` for `u32` error code. - pub fn gr_read(at: Length, len: Length, buffer: *mut BufferStart, err: *mut ErrorCode); + pub fn gr_read(at: Offset, len: Length, buffer: *mut SizedBufferStart, err: *mut ErrorCode); /// Fallible `gr_reply_commit_wgas` send syscall. /// @@ -586,7 +590,7 @@ extern "C" { /// - `payload`: `const ptr` for the begging of the payload buffer. /// - `len`: `u32` length of the payload buffer. /// - `err`: `mut ptr` for error code. - pub fn gr_reply_push(payload: *const BufferStart, len: Length, err: *mut ErrorCode); + pub fn gr_reply_push(payload: *const SizedBufferStart, len: Length, err: *mut ErrorCode); /// Fallible `gr_reply_push_input` send syscall. /// @@ -594,7 +598,7 @@ extern "C" { /// - `offset`: `u32` defining start index of the input buffer to use. /// - `len`: `u32` defining slice length of the input buffer to use. /// - `err`: `mut ptr` for error code. - pub fn gr_reply_push_input(offset: Index, len: Length, err: *mut ErrorCode); + pub fn gr_reply_push_input(offset: Offset, len: Length, err: *mut ErrorCode); /// Fallible `gr_reply_to` get syscall. /// @@ -618,7 +622,7 @@ extern "C" { /// Ignored if equals u32::MAX (use this for zero value for optimization). /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_reply_input_wgas( - offset: Index, + offset: Offset, len: Length, gas_limit: Gas, value: *const Value, @@ -635,7 +639,7 @@ extern "C" { /// Ignored if equals u32::MAX (use this for zero value for optimization). /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_reply_wgas( - payload: *const BufferStart, + payload: *const SizedBufferStart, len: Length, gas_limit: Gas, value: *const Value, @@ -651,7 +655,7 @@ extern "C" { /// Ignored if equals u32::MAX (use this for zero value for optimization). /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_reply( - payload: *const BufferStart, + payload: *const SizedBufferStart, len: Length, value: *const Value, err_mid: *mut ErrorWithHash, @@ -666,7 +670,7 @@ extern "C" { /// Ignored if equals u32::MAX (use this for zero value for optimization). /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_reply_input( - offset: Index, + offset: Offset, len: Length, value: *const Value, err_mid: *mut ErrorWithHash, @@ -676,8 +680,6 @@ extern "C" { /// /// Arguments type: /// - `rid_value`: `const ptr` for concatenated reservation id and value. - /// - `payload`: `const ptr` for the begging of the payload buffer. - /// - `len`: `u32` length of the payload buffer. /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_reservation_reply_commit( rid_value: *const HashWithValue, @@ -693,7 +695,7 @@ extern "C" { /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_reservation_reply( rid_value: *const HashWithValue, - payload: *const BufferStart, + payload: *const SizedBufferStart, len: Length, err_mid: *mut ErrorWithHash, ); @@ -724,7 +726,7 @@ extern "C" { /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_reservation_send( rid_pid_value: *const TwoHashesWithValue, - payload: *const BufferStart, + payload: *const SizedBufferStart, len: Length, delay: BlockNumber, err_mid: *mut ErrorWithHash, @@ -734,7 +736,7 @@ extern "C" { /// /// Arguments type: /// - `gas`: `u64` defining amount of gas to reserve. - /// - `delay`: `u32` amount of blocks to delay. + /// - `duration`: `u32` reservation duration. /// - `err_rid`: `mut ptr` for concatenated error code and reservation id. pub fn gr_reserve_gas(gas: Gas, duration: BlockNumber, err_rid: *mut ErrorWithHash); @@ -783,7 +785,7 @@ extern "C" { /// - `err`: `mut ptr` for error code. pub fn gr_send_push( handle: Handle, - payload: *const BufferStart, + payload: *const SizedBufferStart, len: Length, err: *mut ErrorCode, ); @@ -795,7 +797,7 @@ extern "C" { /// - `offset`: `u32` defining start index of the input buffer to use. /// - `len`: `u32` defining slice length of the input buffer to use. /// - `err`: `mut ptr` for error code. - pub fn gr_send_push_input(handle: Handle, offset: Index, len: Length, err: *mut ErrorCode); + pub fn gr_send_push_input(handle: Handle, offset: Offset, len: Length, err: *mut ErrorCode); /// Fallible `gr_send_input_wgas` send syscall. /// @@ -808,7 +810,7 @@ extern "C" { /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_send_input_wgas( pid_value: *const HashWithValue, - offset: Index, + offset: Offset, len: Length, gas_limit: Gas, delay: BlockNumber, @@ -826,7 +828,7 @@ extern "C" { /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_send_wgas( pid_value: *const HashWithValue, - payload: *const BufferStart, + payload: *const SizedBufferStart, len: Length, gas_limit: Gas, delay: BlockNumber, @@ -843,7 +845,7 @@ extern "C" { /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_send( pid_value: *const HashWithValue, - payload: *const BufferStart, + payload: *const SizedBufferStart, len: Length, delay: BlockNumber, err_mid: *mut ErrorWithHash, @@ -853,13 +855,13 @@ extern "C" { /// /// Arguments type: /// - `pid_value`: `const ptr` for concatenated program id and value. - /// - `payload`: `const ptr` for the begging of the payload buffer. + /// - `offset`: `u32` defining start index of the input buffer to use. /// - `len`: `u32` length of the payload buffer. /// - `delay`: `u32` amount of blocks to delay. /// - `err_mid`: `mut ptr` for concatenated error code and message id. pub fn gr_send_input( pid_value: *const HashWithValue, - offset: Index, + offset: Offset, len: Length, delay: BlockNumber, err_mid: *mut ErrorWithHash, diff --git a/gtest/Cargo.toml b/gtest/Cargo.toml index 50c219ad817..c091b9b977a 100644 --- a/gtest/Cargo.toml +++ b/gtest/Cargo.toml @@ -9,12 +9,12 @@ license.workspace = true gear-core.workspace = true gear-core-errors.workspace = true core-processor = { workspace = true, features = ["std"] } -gear-lazy-pages-interface = { workspace = true, features = ["std"] } +gear-lazy-pages.workspace = true +gear-lazy-pages-common.workspace = true gear-utils.workspace = true +gear-wasm-instrument.workspace = true gsys.workspace = true -sp-io = { workspace = true, features = ["std"] } - codec = { workspace = true, features = ["derive"] } hex.workspace = true colored.workspace = true @@ -22,7 +22,6 @@ derive_more = { workspace = true, features = ["add", "add_assign", "display", "m env_logger.workspace = true path-clean.workspace = true rand = { workspace = true, features = ["std", "std_rng"] } -gear-wasm-instrument.workspace = true log.workspace = true [dev-dependencies] diff --git a/gtest/src/lib.rs b/gtest/src/lib.rs index fce61fbe391..40fab30a57a 100644 --- a/gtest/src/lib.rs +++ b/gtest/src/lib.rs @@ -28,6 +28,21 @@ //! Rust compiler is required for running tests based on `gtest`. It is //! predictable and robust when used in continuous integration. //! +//! ## Main concepts +//! +//! `gtest` is a library that provides a set of tools for testing Gear programs. +//! The most important structures are: +//! +//! - [`System`] — a structure that represents the environment of the Gear +//! network. It contains the current block number, timestamp, and other +//! parameters. It also stores the mailbox and the list of programs. +//! - [`Program`] — a structure that represents a Gear program. It contains the +//! information about program and allows sending messages to other programs. +//! - [`Log`] — a structure that represents a message log. It allows checking +//! the result of the program execution. +//! +//! Let's take a closer look at how to write tests using `gtest`. +//! //! ## Import `gtest` lib //! //! To use the `gtest` library, you must import it into your `Cargo.toml` file @@ -35,220 +50,326 @@ //! //! ```toml //! [package] -//! name = "first-gear-app" +//! name = "my-gear-app" //! version = "0.1.0" //! authors = ["Your Name"] //! edition = "2021" //! //! [dependencies] -//! gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.0" } +//! gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" } //! //! [build-dependencies] -//! gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.0" } +//! gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" } //! //! [dev-dependencies] -//! gtest = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.0" } +//! gtest = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" } //! ``` //! -//! ## `gtest` capabilities +//! ## Program example +//! +//! Let's write a simple program that will receive a message and reply to it. +//! +//! `lib.rs`: //! -//! - Initialization of the common environment for running smart contracts: //! ```ignore -//! // This emulates node's and chain's behavior. -//! // -//! // By default, sets: -//! // - current block equals 0 -//! // - current timestamp equals UNIX timestamp of your system. -//! // - minimal message id equal 0x010000.. -//! // - minimal program id equal 0x010000.. -//! let sys = System::new(); +//! #![no_std] +//! use gstd::msg; +//! +//! #[no_mangle] +//! extern "C" fn handle() { +//! let payload = msg::load_bytes().expect("Failed to load payload"); +//! +//! if payload == b"PING" { +//! msg::reply_bytes(b"PONG", 0).expect("Failed to send reply"); +//! } +//! } //! ``` -//! - Program initialization: +//! +//! `build.rs`: +//! //! ```ignore -//! // Initialization of program structure from file. -//! // -//! // Takes as arguments reference to the related `System` and the path to wasm binary relatively -//! // the root of the crate where the test was written. -//! // -//! // Sets free program id from the related `System` to this program. For this case it equals 0x010000.. -//! // Next program initialized without id specification will have id 0x020000.. and so on. -//! let _ = Program::from_file( +//! fn main() { +//! gear_wasm_builder::build(); +//! } +//! ``` +//! +//! We will add a test that will check the program's behavior. To do this, we +//! will use the `gtest` library. +//! +//! Our test will consist of the following steps: +//! +//! 1. Initialize the `System` structure. +//! 2. Initialize the `Program` structure. +//! 3. Send an init message to the program. Even though we don't have the `init` +//! function in our program, the first message to the program sent via +//! `gtest` is always the init one. +//! 4. Send a handle message to the program. +//! 5. Check the result of the program execution. +//! +//! Add these lines to the bottom of the `lib.rs` file: +//! +//! ```no_run +//! #[cfg(test)] +//! mod tests { +//! use gtest::{Log, Program, System}; +//! +//! const USER_ID: u64 = 100001; +//! +//! #[test] +//! fn test_ping_pong() { +//! // Initialization of the common environment for running smart contracts. +//! let sys = System::new(); +//! +//! // Initialization of the current program structure. +//! let prog = Program::current(&sys); +//! +//! // Send an init message to the program. +//! let res = prog.send_bytes(USER_ID, b"Doesn't matter"); +//! +//! // Check whether the program was initialized successfully. +//! assert!(!res.main_failed()); +//! +//! // Send a handle message to the program. +//! let res = prog.send_bytes(USER_ID, b"PING"); +//! +//! // Check the result of the program execution. +//! // 1. Create a log pattern with the expected result. +//! let log = Log::builder() +//! .source(prog.id()) +//! .dest(USER_ID) +//! .payload_bytes(b"PONG"); +//! +//! // 2. Check whether the program was executed successfully. +//! assert!(!res.main_failed()); +//! +//! // 3. Make sure the log entry is in the result. +//! assert!(res.contains(&log)); +//! } +//! } +//! ``` +//! +//! To run the test, use the following command: +//! +//! ```bash +//! cargo test +//! ``` +//! +//! # `gtest` capabilities +//! +//! Let's take a closer look at the `gtest` capabilities. +//! +//! ## Initialization of the network environment for running smart contracts +//! +//! ```no_run +//! # use gtest::System; +//! let sys = System::new(); +//! ``` +//! +//! This emulates node's and chain's behavior. By default, the [`System::new`] +//! function sets the following parameters: +//! +//! - current block equals `0` +//! - current timestamp equals UNIX timestamp of your system +//! - starting message id equals `0x010000..` +//! - starting program id equals `0x010000..` +//! +//! ## Program initialization +//! +//! There are a few ways to initialize a program: +//! +//! - Initialize the current program using the [`Program::current`] function: +//! +//! ```no_run +//! # use gtest::Program; +//! # let sys = gtest::System::new(); +//! let prog = Program::current(&sys); +//! ``` +//! +//! - Initialize a program from a Wasm-file with a default id using the +//! [`Program::from_file`] function: +//! +//! ```no_run +//! # use gtest::Program; +//! # let sys = gtest::System::new(); +//! let prog = Program::from_file( //! &sys, //! "./target/wasm32-unknown-unknown/release/demo_ping.wasm", //! ); +//! ``` +//! +//! - Initialize a program from a Wasm-file with a custom id using the +//! [`Program::from_file_with_id`] function: //! -//! // Also, you may use the `Program::current()` function to load the current program. -//! let _ = Program::current(&sys); -//! -//! // We can check the id of the program by calling `id()` function. -//! // -//! // It returns `ProgramId` type value. -//! let ping_pong_id = ping_pong.id(); -//! -//! // There is also a `from_file_with_id` constructor to manually specify the id of the program. -//! // -//! // Every place in this lib, where you need to specify some ids, -//! // it requires generic type 'ID`, which implements `Into`. -//! // -//! // `ProgramIdWrapper` may be built from: -//! // - u64; -//! // - [u8; 32]; -//! // - String; -//! // - &str; -//! // - ProgramId (from `gear_core` one's, not from `gstd`). -//! // -//! // String implementation means the input as hex (with or without "0x") -//! -//! // Numeric -//! let _ = Program::from_file_with_id( +//! ```no_run +//! # use gtest::Program; +//! # let sys = gtest::System::new(); +//! let prog = Program::from_file_with_id( //! &sys, //! 105, //! "./target/wasm32-unknown-unknown/release/demo_ping.wasm", //! ); +//! ``` //! -//! // Hex with "0x" -//! let _ = Program::from_file_with_id( -//! &sys, -//! "0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", -//! "./target/wasm32-unknown-unknown/release/demo_ping.wasm", -//! ); +//! Every place in this lib, where you need to specify some ids, it requires +//! generic type `ID`, which implements ``Into``. //! -//! // Hex without "0x" -//! let _ = Program::from_file_with_id( -//! &sys, -//! "e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df5e", -//! "./target/wasm32-unknown-unknown/release/demo_ping.wasm", -//! ); +//! `ProgramIdWrapper` may be built from: +//! - `u64` +//! - `[u8; 32]` +//! - `String` +//! - `&str` +//! - [`ProgramId`](https://docs.gear.rs/gear_core/ids/struct.ProgramId.html) +//! (from `gear_core` one's, not from `gstd`). //! -//! // Array [u8; 32] (e.g. filled with 5) -//! let _ = Program::from_file_with_id( -//! &sys, -//! [5; 32], -//! "./target/wasm32-unknown-unknown/release/demo_ping.wasm", -//! ); +//! `String` implementation means the input as hex (with or without "0x"). +//! +//! ## Getting the program from the system //! -//! // If you initialize program not in this scope, in cycle, in other conditions, -//! // where you didn't save the structure, you may get the object from the system by id. -//! let _ = sys.get_program(105); +//! If you initialize program not in this scope, in cycle, in other conditions, +//! where you didn't save the structure, you may get the object from the system +//! by id. +//! +//! ```no_run +//! # let sys = gtest::System::new(); +//! let prog = sys.get_program(105); //! ``` -//! - Getting the program from the system: -//! ```ignore -//! // If you initialize program not in this scope, in cycle, in other conditions, -//! // where you didn't save the structure, you may get the object from the system by id. -//! let _ = sys.get_program(105); +//! +//! ## Initialization of styled `env_logger` +//! +//! Initialization of styled `env_logger` to print logs (only from `gwasm` by +//! default) into stdout: +//! +//! ```no_run +//! # let sys = gtest::System::new(); +//! sys.init_logger(); //! ``` -//! - Initialization of styled `env_logger`: -//! ```ignore -//! // Initialization of styled `env_logger` to print logs (only from `gwasm` by default) into stdout. -//! // -//! // To specify printed logs, set the env variable `RUST_LOG`: -//! // `RUST_LOG="target_1=logging_level,target_2=logging_level" cargo test` -//! // -//! // Gear smart contracts use `gwasm` target with `debug` logging level -//! sys.init_logger(); +//! +//! To specify printed logs, set the env variable `RUST_LOG`: +//! +//! ```bash +//! RUST_LOG="target_1=logging_level,target_2=logging_level" cargo test //! ``` -//! - Sending messages: -//! ```ignore -//! // To send message to the program need to call one of two program's functions: -//! // `send()` or `send_bytes()` (or `send_with_value` and `send_bytes_with_value` if you need to send a message with attached funds). -//! // -//! // Both of the methods require sender id as the first argument and the payload as second. -//! // -//! // The difference between them is pretty simple and similar to `gstd` functions -//! // `msg::send()` and `msg::send_bytes()`. -//! // -//! // The first one requires payload to be CODEC Encodable, while the second requires payload -//! // implement `AsRef<[u8]>`, that means to be able to represent as bytes. -//! // -//! // `send()` uses `send_bytes()` under the hood with bytes from payload.encode(). -//! // -//! // First message to the initialized program structure is always the init message. -//! let res = program.send_bytes(100001, "INIT MESSAGE"); +//! +//! ## Sending messages +//! +//! To send message to the program need to call one of two program's functions: +//! +//! - [`Program::send`] (or [`Program::send_with_value`] if you need to send a +//! message with attached funds). +//! - [`Program::send_bytes`] (or [`Program::send_bytes_with_value`] if you need +//! to send a message with attached funds). +//! +//! Both of the methods require sender id as the first argument and the payload +//! as second. +//! +//! The difference between them is pretty simple and similar to [`gstd`](https://docs.gear.rs/gstd/) functions [`msg::send`](https://docs.gear.rs/gstd/msg/fn.send.html) and [`msg::send_bytes`](https://docs.gear.rs/gstd/msg/fn.send_bytes.html). +//! +//! The first one requires payload to be CODEC Encodable, while the second +//! requires payload implement `AsRef<[u8]>`, that means to be able to represent +//! as bytes. +//! +//! [`Program::send`] uses [`Program::send_bytes`] under the hood with bytes +//! from `payload.encode()`. +//! +//! First message to the initialized program structure is always the init +//! message. +//! +//! ```no_run +//! # let sys = gtest::System::new(); +//! # let prog = gtest::Program::current(&sys); +//! let res = prog.send_bytes(100001, "INIT MESSAGE"); //! ``` -//! - Processing the result of the program execution: -//! ```ignore -//! // Any sending functions in the lib returns `RunResult` structure. -//! // -//! // It contains the final result of the processing message and others, -//! // which were created during the execution. -//! // -//! // It has 4 main functions. -//! -//! // Returns the reference to the Vec produced to users messages. -//! // You may assert them as you wish, iterating through them. -//! assert!(res.log().is_empty()); -//! -//! // Returns bool which shows that there was panic during the execution -//! // of the main message. -//! assert!(!res.main_failed()); -//! -//! // Returns bool which shows that there was panic during the execution -//! // of the created messages during the main execution. -//! // -//! // Equals false if no others were called. -//! assert!(!res.others_failed()); -//! -//! // Returns bool which shows that logs contain a given log. -//! // -//! // Syntax sugar around `res.log().iter().any(|v| v == arg)`. -//! assert!(!res.contains(&Log::builder())); -//! -//! // To build a log for assertion you need to use `Log` structure with its builders. -//! // All fields here are optional. -//! // Assertion with Logs from core are made on the Some(..) fields -//! // You will run into panic if you try to set the already specified field. -//! // -//! // Constructor for success log. -//! let _ = Log::builder(); -//! -//! // Constructor for error reply log. -//! let _ = Log::error_builder(); -//! -//! // The first message to uploaded program is INIT message. -//! // -//! // Let’s send a new message after the program has been initialized. -//! // The initialized program expects to receive a byte string "PING" and replies with a byte string "PONG". -//! let res = ping_pong.send_bytes(100001, "PING"); -//! -//! // Other fields are set optionally by `dest()`, `source()`, `payload()`, `payload_bytes()`. -//! // -//! // The logic for `payload()` and `payload_bytes()` is the same as for `send()` and `send_bytes()`. -//! // First requires an encodable struct. The second requires bytes. -//! let log = Log::builder() -//! .source(ping_pong_id) -//! .dest(100001) -//! .payload_bytes("PONG"); -//! -//! assert!(res.contains(&log)); -//! -//! let wrong_log = Log::builder().source(100001); -//! -//! assert!(!res.contains(&wrong_log)); -//! -//! // Log also has `From` implementations from (ID, T) and from (ID_1, ID_2, T), -//! // where ID: Into, T: AsRef<[u8]> -//! let x = Log::builder().dest(5).payload_bytes("A"); -//! let x_from: Log = (5, "A").into(); -//! -//! assert_eq!(x, x_from); -//! -//! let y = Log::builder().dest(5).source(15).payload_bytes("A"); -//! let y_from: Log = (15, 5, "A").into(); -//! -//! assert_eq!(y, y_from); -//! -//! assert!(!res.contains(&(ping_pong_id, ping_pong_id, "PONG"))); -//! assert!(res.contains(&(1, 100001, "PONG"))); +//! +//! ## Processing the result of the program execution +//! +//! Any sending functions in the lib returns [`RunResult`] structure. +//! +//! It contains the final result of the processing message and others, which +//! were created during the execution. +//! +//! It has 4 main functions: +//! +//! - [`RunResult::log`] — returns the reference to the Vec produced to users +//! messages. You may assert them as you wish, iterating through them. +//! - [`RunResult::main_failed`] — returns bool which shows that there was panic +//! during the execution of the main message. +//! - [`RunResult::others_failed`] — returns bool which shows that there was +//! panic during the execution of the created messages during the main +//! execution. Equals false if no others were called. +//! - [`RunResult::contains`] — returns bool which shows that logs contain a +//! given log. Syntax sugar around `res.log().iter().any(|v| v == arg)`. +//! +//! To build a log for assertion you need to use [`Log`] structure with its +//! builders. All fields here are optional. Assertion with `Log`s from core are +//! made on the `Some(..)` fields. You will run into panic if you try to set the +//! already specified field. +//! +//! ```no_run +//! # use gtest::Log; +//! # use gear_core_errors::ErrorReplyReason; +//! // Constructor for success log. +//! let log = Log::builder(); +//! +//! // Constructor for error reply log. +//! let log = Log::error_builder(ErrorReplyReason::InactiveProgram); +//! # let sys = gtest::System::new(); +//! # let prog = gtest::Program::current(&sys); +//! // Other fields are set optionally by `dest()`, `source()`, `payload()`, `payload_bytes()`. +//! let log = Log::builder() +//! .source(prog.id()) +//! .dest(100001) +//! .payload_bytes("PONG"); //! ``` -//! - Spending blocks: -//! ```ignore -//! // You may control time in the system by spending blocks. -//! // -//! // It adds the amount of blocks passed as arguments to the current block of the system. -//! // Same for the timestamp. Note, that for now 1 block in Gear-based network is 3 sec -//! // duration. +//! +//! Log also has `From` implementations from `(ID, T)` and from `(ID_1, ID_2, +//! T)`, where `ID: Into`, `T: AsRef<[u8]>`. +//! +//! ```no_run +//! # use gtest::Log; +//! let x = Log::builder().dest(5).payload_bytes("A"); +//! let x_from: Log = (5, "A").into(); +//! assert_eq!(x, x_from); +//! +//! let y = Log::builder().dest(5).source(15).payload_bytes("A"); +//! let y_from: Log = (15, 5, "A").into(); +//! assert_eq!(y, y_from); +//! ``` +//! +//! ## Spending blocks +//! +//! You may control time in the system by spending blocks. +//! +//! It adds the amount of blocks passed as arguments to the current block of the +//! system. Same for the timestamp. Note, that for now 1 block in Gear-based +//! network is 3 sec duration. +//! +//! ```no_run +//! # let sys = gtest::System::new(); +//! // Spend 150 blocks (7.5 mins for 3 sec block). //! sys.spend_blocks(150); //! ``` +//! +//! Note that processing messages (e.g. by using +//! [`Program::send`]/[`Program::send_bytes`] methods) doesn't spend blocks, nor +//! changes the timestamp. If you write time dependent logic, you should spend +//! blocks manually. +//! +//! ## Balance: +//! +//! ```no_run +//! # use gtest::Program; +//! # let sys = gtest::System::new(); +//! // If you need to send a message with value you have to mint balance for the message sender: +//! let user_id = 42; +//! sys.mint_to(user_id, 5000); +//! assert_eq!(sys.balance_of(user_id), 5000); +//! +//! // To give the balance to the program you should use `mint` method: +//! let mut prog = Program::current(&sys); +//! prog.mint(1000); +//! assert_eq!(prog.balance(), 1000); +//! ``` +//! //! -//! - Balance: -//! ```ignore -//! // If you need to send a message with value you have to mint balance for the message sender: -//! let user_id = 42; -//! sys.mint_to(user_id, 5000); -//! assert_eq!(sys.balance_of(user_id), 5000); -//! -//! // To give the balance to the program you should use `mint` method: -//! let prog = Program::current(&sys); -//! prog.mint(1000); -//! assert_eq!(prog.balance(), 1000); -//! ``` #![deny(missing_docs)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] mod error; mod log; @@ -325,21 +436,75 @@ pub use error::{Result, TestError}; pub use program::{calculate_program_id, Gas, Program, WasmProgram}; pub use system::System; -const EXISTENTIAL_DEPOSIT: u128 = 500; -const MAILBOX_THRESHOLD: u64 = 3000; -const WAITLIST_COST: u64 = 100; -const RESERVE_FOR: u32 = 1; -const RESERVATION_COST: u64 = 100; -const READ_COST: u64 = 20; -const WRITE_COST: u64 = 100; -const READ_PER_BYTE_COST: u64 = 10; -const WRITE_PER_BYTE_COST: u64 = 10; -const MODULE_INSTANTIATION_BYTE_COST: u64 = 20; -const MAX_RESERVATIONS: u64 = 256; -const EPOCH_DURATION_IN_BLOCKS: u32 = 600; -const INITIAL_RANDOM_SEED: u64 = 42; -const MODULE_INSTRUMENTATION_BYTE_COST: u64 = 13; -const MODULE_INSTRUMENTATION_COST: u64 = 297; -const DISPATCH_HOLD_COST: u64 = 200; -const RENT_COST: u128 = 330; -const VALUE_PER_GAS: u128 = 25; +pub(crate) use constants::*; + +/// Module containing constants of Gear protocol. +pub mod constants { + /* Constant types */ + + /// Numeric type representing value in Gear protocol. + pub type Value = u128; + + /// Numeric type representing gas in Gear protocol. + pub type Gas = u64; + + /// Numeric type representing blocks in Gear protocol. + pub type Block = u32; + + /* Currency-related constants */ + + /// Value per token. + pub const UNITS: Value = 1_000_000_000_000; + /// Minimal amount of value able to be sent. Defines accounts existence + /// requirement. + pub const EXISTENTIAL_DEPOSIT: Value = 10 * UNITS; + /// Value per gas. + pub const VALUE_PER_GAS: Value = 25; + /// Duration of one block in msecs. + pub const BLOCK_DURATION_IN_MSECS: u64 = 3000; + /// Duration of one epoch. + pub const EPOCH_DURATION_IN_BLOCKS: Block = 600; + + /* Storage-related constants */ + // TODO: use proper weights of db accesses (#3509). + + /// Minimal amount of gas required to be inserted into Mailbox. + pub const MAILBOX_THRESHOLD: Gas = 3_000; + /// Extra amount of blocks must be reserved for storing in storage. + pub const RESERVE_FOR: Block = 1; + /// Cost of read access into storage. + pub const READ_COST: Gas = 25; + /// Per-byte extra cost of read access into storage. + pub const READ_PER_BYTE_COST: Gas = 10; + /// Cost of write access into storage. + pub const WRITE_COST: Gas = 100; + /// Per-byte extra cost of write access into storage. + pub const WRITE_PER_BYTE_COST: Gas = 10; + + /* Rent-related constants */ + + /// Cost of storing waitlisted message per block. + pub const WAITLIST_COST: Gas = 100; + /// Cost of storing reservation per block. + pub const RESERVATION_COST: Gas = 100; + /// Cost of storing delayed message per block. + pub const DISPATCH_HOLD_COST: Gas = 100; + /// Cost of storing program per block. + /// + /// (!) Currently disabled: storing programs are free. + pub const RENT_COST: Value = 330; + + /* Execution-related constants */ + // TODO: use proper weights of instantiation and instrumentation (#3509). + + /// Maximal amount of reservations program may have. + pub const MAX_RESERVATIONS: u64 = 256; + /// Cost of wasm module instantiation before execution per byte of code. + pub const MODULE_INSTANTIATION_BYTE_COST: Gas = 20; + /// Cost of instrumenting wasm code on upload. + pub const MODULE_INSTRUMENTATION_COST: Gas = 297; + /// Cost of instrumenting wasm code on upload per byte of code. + pub const MODULE_INSTRUMENTATION_BYTE_COST: Gas = 13; + /// Initial random seed for testing environment. + pub const INITIAL_RANDOM_SEED: u64 = 42; +} diff --git a/gtest/src/mailbox.rs b/gtest/src/mailbox.rs index 376a41dd64b..0a427732bc4 100644 --- a/gtest/src/mailbox.rs +++ b/gtest/src/mailbox.rs @@ -357,17 +357,20 @@ mod tests { receiver_id.into(), payload.encode().try_into().unwrap(), Default::default(), - 1000, + 2 * crate::EXISTENTIAL_DEPOSIT, None, ); - system.mint_to(sender_id, 1000); + system.mint_to(sender_id, 2 * crate::EXISTENTIAL_DEPOSIT); system.send_dispatch(Dispatch::new(DispatchKind::Handle, message)); let receiver_mailbox = system.get_mailbox(receiver_id); receiver_mailbox.claim_value(log); - assert_eq!(system.balance_of(receiver_id), 1000); + assert_eq!( + system.balance_of(receiver_id), + 2 * crate::EXISTENTIAL_DEPOSIT + ); } #[test] diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index 60b09973bb5..366d6836464 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -19,7 +19,7 @@ use crate::{ log::{CoreLog, RunResult}, program::{Gas, WasmProgram}, - Result, System, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT, + Result, TestError, DISPATCH_HOLD_COST, EPOCH_DURATION_IN_BLOCKS, EXISTENTIAL_DEPOSIT, INITIAL_RANDOM_SEED, MAILBOX_THRESHOLD, MAX_RESERVATIONS, MODULE_INSTANTIATION_BYTE_COST, MODULE_INSTRUMENTATION_BYTE_COST, MODULE_INSTRUMENTATION_COST, READ_COST, READ_PER_BYTE_COST, RENT_COST, RESERVATION_COST, RESERVE_FOR, VALUE_PER_GAS, WAITLIST_COST, WRITE_COST, @@ -39,15 +39,14 @@ use gear_core::{ StoredMessage, }, pages::{GearPage, PageU32Size, WasmPage}, - program::{MemoryInfix, Program as CoreProgram}, + program::Program as CoreProgram, reservation::{GasReservationMap, GasReserver}, }; use gear_core_errors::{ErrorReplyReason, SignalCode, SimpleExecutionError}; use gear_wasm_instrument::wasm_instrument::gas_metering::ConstantCostRules; use rand::{rngs::StdRng, RngCore, SeedableRng}; -use sp_io::TestExternalities; use std::{ - cell::RefCell, + cell::{Ref, RefCell, RefMut}, collections::{BTreeMap, BTreeSet, HashMap, VecDeque}, convert::TryInto, rc::Rc, @@ -100,21 +99,22 @@ impl TestActor { matches!(self, TestActor::Uninitialized(..)) } - fn get_pages_data_mut(&mut self) -> Option<(MemoryInfix, &mut BTreeMap)> { + pub fn get_pages_data(&self) -> Option<&BTreeMap> { match self { - TestActor::Initialized(Program::Genuine { - pages_data, - program, - .. - }) - | TestActor::Uninitialized( - _, - Some(Program::Genuine { - pages_data, - program, - .. - }), - ) => Some((program.memory_infix(), pages_data)), + TestActor::Initialized(Program::Genuine { pages_data, .. }) + | TestActor::Uninitialized(_, Some(Program::Genuine { pages_data, .. })) => { + Some(pages_data) + } + _ => None, + } + } + + fn get_pages_data_mut(&mut self) -> Option<&mut BTreeMap> { + match self { + TestActor::Initialized(Program::Genuine { pages_data, .. }) + | TestActor::Uninitialized(_, Some(Program::Genuine { pages_data, .. })) => { + Some(pages_data) + } _ => None, } } @@ -149,7 +149,7 @@ impl TestActor { }), ) => ( program.clone(), - pages_data.clone(), + pages_data, code_id, gas_reservation_map.clone(), ), @@ -163,7 +163,7 @@ impl TestActor { code_exports: program.code().exports().clone(), static_pages: program.code().static_pages(), initialized: program.is_initialized(), - pages_with_data: pages_data.keys().copied().collect(), + pages_with_data: pages_data.keys().cloned().collect(), gas_reservation_map, memory_infix: program.memory_infix(), }, @@ -185,25 +185,40 @@ pub(crate) enum Program { } impl Program { - pub(crate) fn new( - program: CoreProgram, - code_id: CodeId, - pages_data: BTreeMap, - gas_reservation_map: GasReservationMap, - ) -> Self { - Program::Genuine { - program, - code_id, - pages_data, - gas_reservation_map, - } - } - pub(crate) fn new_mock(mock: impl WasmProgram + 'static) -> Self { Program::Mock(Some(Box::new(mock))) } } +#[derive(Default, Debug, Clone)] +pub(crate) struct Actors(Rc>>); + +impl Actors { + pub fn borrow(&self) -> Ref<'_, BTreeMap> { + self.0.borrow() + } + + pub fn borrow_mut(&mut self) -> RefMut<'_, BTreeMap> { + self.0.borrow_mut() + } + + fn insert( + &mut self, + program_id: ProgramId, + actor_and_balance: (TestActor, Balance), + ) -> Option<(TestActor, Balance)> { + self.0.borrow_mut().insert(program_id, actor_and_balance) + } + + pub fn contains_key(&self, program_id: &ProgramId) -> bool { + self.0.borrow().contains_key(program_id) + } + + fn remove(&mut self, program_id: &ProgramId) -> Option<(TestActor, Balance)> { + self.0.borrow_mut().remove(program_id) + } +} + #[derive(Default, Debug)] pub(crate) struct ExtManager { // State metadata @@ -215,7 +230,7 @@ pub(crate) struct ExtManager { pub(crate) id_nonce: u64, // State - pub(crate) actors: BTreeMap, + pub(crate) actors: Actors, pub(crate) opt_binaries: BTreeMap>, pub(crate) meta_binaries: BTreeMap>, pub(crate) dispatches: VecDeque, @@ -225,8 +240,6 @@ pub(crate) struct ExtManager { pub(crate) gas_limits: BTreeMap, pub(crate) delayed_dispatches: HashMap>, - pub(crate) externalities: Rc>, - // Last run info pub(crate) origin: ProgramId, pub(crate) msg_id: MessageId, @@ -313,7 +326,7 @@ impl ExtManager { .map(|dispatches| { dispatches .into_iter() - .map(|dispatch| self.with_externalities(|this| this.run_dispatch(dispatch))) + .map(|dispatch| self.run_dispatch(dispatch)) .collect() }) .unwrap_or_default() @@ -333,31 +346,24 @@ impl ExtManager { } } - pub(crate) fn with_externalities(&mut self, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - let externalities = self.externalities.clone(); - let mut externalities = externalities.borrow_mut(); - externalities.execute_with(|| f(self)) - } - - fn update_storage_pages( - program_id: ProgramId, - memory_infix: MemoryInfix, - memory_pages: &BTreeMap, + #[track_caller] + pub(crate) fn update_storage_pages( + &mut self, + program_id: &ProgramId, + memory_pages: BTreeMap, ) { - // write pages into storage so lazy-pages can access them + let mut actors = self.actors.borrow_mut(); + let program = &mut actors + .get_mut(program_id) + .unwrap_or_else(|| panic!("Actor {program_id} not found")) + .0; + + let pages_data = program + .get_pages_data_mut() + .expect("No pages data found for program"); + for (page, buf) in memory_pages { - let page_no: u32 = (*page).into(); - let prefix = [ - System::PAGE_STORAGE_PREFIX.as_slice(), - program_id.into_bytes().as_slice(), - memory_infix.inner().to_le_bytes().as_slice(), - page_no.to_le_bytes().as_slice(), - ] - .concat(); - sp_io::storage::set(&prefix, buf); + pages_data.insert(page, buf); } } @@ -375,8 +381,8 @@ impl ExtManager { panic!("Sending messages allowed only from users id"); } - let (_, balance) = self - .actors + let mut actors = self.actors.borrow_mut(); + let (_, balance) = actors .entry(dispatch.source()) .or_insert((TestActor::User, 0)); @@ -398,7 +404,7 @@ impl ExtManager { pub(crate) fn validate_and_run_dispatch(&mut self, dispatch: Dispatch) -> RunResult { self.validate_dispatch(&dispatch); - self.with_externalities(|this| this.run_dispatch(dispatch)) + self.run_dispatch(dispatch) } #[track_caller] @@ -437,17 +443,20 @@ impl ExtManager { continue; } - let (actor, balance) = self - .actors + let mut actors = self.actors.borrow_mut(); + let (actor, balance) = actors .get_mut(&dest) .expect("Somehow message queue contains message for user"); let balance = *balance; if actor.is_dormant() { + drop(actors); self.process_dormant(balance, dispatch); } else if let Some((data, program)) = actor.get_executable_actor_data() { + drop(actors); self.process_normal(balance, data, program.code().clone(), dispatch); } else if let Some(mock) = actor.take_mock() { + drop(actors); self.process_mock(mock, dispatch); } else { unreachable!(); @@ -476,12 +485,14 @@ impl ExtManager { payload: Vec, program_id: &ProgramId, ) -> Result> { - let (actor, _balance) = self - .actors + let mut actors = self.actors.borrow_mut(); + let (actor, _balance) = actors .get_mut(program_id) .ok_or_else(|| TestError::ActorNotFound(*program_id))?; if let Some((_, program)) = actor.get_executable_actor_data() { + drop(actors); + core_processor::informational::execute_for_reply::( String::from("state"), program.code().clone(), @@ -536,7 +547,8 @@ impl ExtManager { } pub(crate) fn is_user(&self, id: &ProgramId) -> bool { - !self.actors.contains_key(id) || matches!(self.actors.get(id), Some((TestActor::User, _))) + !self.actors.contains_key(id) + || matches!(self.actors.borrow().get(id), Some((TestActor::User, _))) } pub(crate) fn mint_to(&mut self, id: &ProgramId, value: Balance) { @@ -548,12 +560,14 @@ impl ExtManager { ); } - let (_, balance) = self.actors.entry(*id).or_insert((TestActor::User, 0)); + let mut actors = self.actors.borrow_mut(); + let (_, balance) = actors.entry(*id).or_insert((TestActor::User, 0)); *balance = balance.saturating_add(value); } pub(crate) fn balance_of(&self, id: &ProgramId) -> Balance { self.actors + .borrow() .get(id) .map(|(_, balance)| *balance) .unwrap_or_default() @@ -582,14 +596,15 @@ impl ExtManager { ); } - let (_, actor_balance) = self.actors.entry(*id).or_insert((TestActor::User, 0)); + let mut actors = self.actors.borrow_mut(); + let (_, actor_balance) = actors.entry(*id).or_insert((TestActor::User, 0)); *actor_balance = balance; } #[track_caller] - pub(crate) fn read_memory_pages(&self, program_id: &ProgramId) -> &BTreeMap { - let program = &self - .actors + pub(crate) fn read_memory_pages(&self, program_id: &ProgramId) -> BTreeMap { + let actors = self.actors.borrow(); + let program = &actors .get(program_id) .unwrap_or_else(|| panic!("Actor {program_id} not found")) .0; @@ -601,47 +616,11 @@ impl ExtManager { }; match program { - Program::Genuine { pages_data, .. } => pages_data, + Program::Genuine { pages_data, .. } => pages_data.clone(), Program::Mock(_) => panic!("Can't read memory of mock program"), } } - #[track_caller] - pub(crate) fn override_memory_pages( - &mut self, - program_id: &ProgramId, - memory_pages: BTreeMap, - ) { - let program = &mut self - .actors - .get_mut(program_id) - .unwrap_or_else(|| panic!("Actor {program_id} not found")) - .0; - - let program = match program { - TestActor::Initialized(program) => program, - TestActor::Uninitialized(_, program) => program.as_mut().unwrap(), - TestActor::Dormant | TestActor::User => panic!("Actor {program_id} isn't a program"), - }; - - let memory_infix = match program { - Program::Genuine { - pages_data, - program, - .. - } => { - *pages_data = memory_pages.clone(); - - program.memory_infix() - } - Program::Mock(_) => panic!("Can't read memory of mock program"), - }; - - self.with_externalities(|_this| { - Self::update_storage_pages(*program_id, memory_infix, &memory_pages) - }) - } - #[track_caller] fn prepare_for(&mut self, dispatch: &Dispatch) { self.msg_id = dispatch.id(); @@ -668,25 +647,27 @@ impl ExtManager { #[track_caller] fn init_success(&mut self, message_id: MessageId, program_id: ProgramId) { - let (actor, _) = self - .actors + let mut actors = self.actors.borrow_mut(); + let (actor, _) = actors .get_mut(&program_id) .expect("Can't find existing program"); actor.set_initialized(); + drop(actors); self.move_waiting_msgs_to_queue(message_id, program_id); } #[track_caller] fn init_failure(&mut self, message_id: MessageId, program_id: ProgramId) { - let (actor, _) = self - .actors + let mut actors = self.actors.borrow_mut(); + let (actor, _) = actors .get_mut(&program_id) .expect("Can't find existing program"); *actor = TestActor::Dormant; + drop(actors); self.move_waiting_msgs_to_queue(message_id, program_id); self.mark_failed(message_id); } @@ -702,8 +683,8 @@ impl ExtManager { // When called for the `dispatch`, it must be in queue. #[track_caller] fn check_is_for_wait_list(&self, dispatch: &StoredDispatch) -> bool { - let (actor, _) = self - .actors + let actors = self.actors.borrow(); + let (actor, _) = actors .get(&dispatch.destination()) .expect("method called for unknown destination"); if let TestActor::Uninitialized(maybe_message_id, _) = actor { @@ -808,11 +789,14 @@ impl ExtManager { // After run either `init_success` is called or `init_failed`. // So only active (init success) program can be modified - self.actors.entry(program_id).and_modify(|(actor, _)| { - if let TestActor::Initialized(old_mock) = actor { - *old_mock = Program::Mock(Some(mock)); - } - }); + self.actors + .borrow_mut() + .entry(program_id) + .and_modify(|(actor, _)| { + if let TestActor::Initialized(old_mock) = actor { + *old_mock = Program::Mock(Some(mock)); + } + }); } fn process_normal( @@ -1037,26 +1021,15 @@ impl JournalHandler for ExtManager { fn update_pages_data( &mut self, program_id: ProgramId, - mut pages_data: BTreeMap, + pages_data: BTreeMap, ) { - let (actor, _) = self - .actors - .get_mut(&program_id) - .expect("Can't find existing program"); - - if let Some((memory_infix, actor_pages_data)) = actor.get_pages_data_mut() { - Self::update_storage_pages(program_id, memory_infix, &pages_data); - - actor_pages_data.append(&mut pages_data); - } else { - unreachable!("No pages data found for program") - } + self.update_storage_pages(&program_id, pages_data); } #[track_caller] fn update_allocations(&mut self, program_id: ProgramId, allocations: BTreeSet) { - let (actor, _) = self - .actors + let mut actors = self.actors.borrow_mut(); + let (actor, _) = actors .get_mut(&program_id) .expect("Can't find existing program"); @@ -1095,7 +1068,8 @@ impl JournalHandler for ExtManager { } if let Some(ref to) = to { if !self.is_user(&from) { - let (_, balance) = self.actors.get_mut(&from).expect("Can't fail"); + let mut actors = self.actors.borrow_mut(); + let (_, balance) = actors.get_mut(&from).expect("Can't fail"); if *balance < value { unreachable!("Actor {:?} balance is less then sent value", from); @@ -1129,7 +1103,12 @@ impl JournalHandler for ExtManager { let candidate = CoreProgram::new(candidate_id, Default::default(), code); self.store_new_actor( candidate_id, - Program::new(candidate, code_id, Default::default(), Default::default()), + Program::Genuine { + program: candidate, + code_id, + pages_data: Default::default(), + gas_reservation_map: Default::default(), + }, Some(init_message_id), ); } else { @@ -1171,8 +1150,8 @@ impl JournalHandler for ExtManager { #[track_caller] fn update_gas_reservation(&mut self, program_id: ProgramId, reserver: GasReserver) { let block_height = self.block_info.height; - let (actor, _) = self - .actors + let mut actors = self.actors.borrow_mut(); + let (actor, _) = actors .get_mut(&program_id) .expect("gas reservation update guaranteed to be called only on existing program"); diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 73104089fb9..f5bd306955c 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -362,7 +362,68 @@ impl<'a> Program<'a> { /// Create a program from file and initialize it with provided /// `path` and `id`. /// - /// See also [`Program::from_file`]. + /// `id` may be built from: + /// - `u64` + /// - `[u8; 32]` + /// - `String` + /// - `&str` + /// - [`ProgramId`](https://docs.gear.rs/gear_core/ids/struct.ProgramId.html) + /// (from `gear_core` one's, not from `gstd`). + /// + /// # Examples + /// + /// From numeric id: + /// + /// ```no_run + /// # use gtest::{Program, System}; + /// # let sys = System::new(); + /// let prog = Program::from_file_with_id( + /// &sys, + /// 105, + /// "./target/wasm32-unknown-unknown/release/demo_ping.wasm", + /// ); + /// ``` + /// + /// From hex string starting with `0x`: + /// + /// ```no_run + /// # use gtest::{Program, System}; + /// # let sys = System::new(); + /// let prog = Program::from_file_with_id( + /// &sys, + /// "0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", + /// "./target/wasm32-unknown-unknown/release/demo_ping.wasm", + /// ); + /// ``` + /// + /// From hex string starting without `0x`: + /// + /// ```no_run + /// # use gtest::{Program, System}; + /// # let sys = System::new(); + /// let prog = Program::from_file_with_id( + /// &sys, + /// "e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df5e", + /// "./target/wasm32-unknown-unknown/release/demo_ping.wasm", + /// ); + /// ``` + /// + /// From array of bytes (e.g. filled with `5`): + /// + /// ```no_run + /// # use gtest::{Program, System}; + /// # let sys = System::new(); + /// let prog = Program::from_file_with_id( + /// &sys, + /// [5; 32], + /// "./target/wasm32-unknown-unknown/release/demo_ping.wasm", + /// ); + /// ``` + /// + /// # See also + /// + /// - [`Program::from_file`] for creating a program from file with default + /// id. #[track_caller] pub fn from_file_with_id, I: Into + Clone + Debug>( system: &'a System, @@ -448,7 +509,12 @@ impl<'a> Program<'a> { Self::program_with_id( system, id, - InnerProgram::new(program, code_id, Default::default(), Default::default()), + InnerProgram::Genuine { + program, + code_id, + pages_data: Default::default(), + gas_reservation_map: Default::default(), + }, ) } @@ -502,7 +568,8 @@ impl<'a> Program<'a> { None, ); - let (actor, _) = system.actors.get_mut(&self.id).expect("Can't fail"); + let mut actors = system.actors.borrow_mut(); + let (actor, _) = actors.get_mut(&self.id).expect("Can't fail"); let kind = if let TestActor::Uninitialized(id @ None, _) = actor { *id = Some(message.id()); @@ -511,6 +578,7 @@ impl<'a> Program<'a> { DispatchKind::Handle }; + drop(actors); system.validate_and_run_dispatch(Dispatch::new(kind, message)) } @@ -528,12 +596,14 @@ impl<'a> Program<'a> { ); let message = SignalMessage::new(origin_msg_id, code); - let (actor, _) = system.actors.get_mut(&self.id).expect("Can't fail"); + let mut actors = system.actors.borrow_mut(); + let (actor, _) = actors.get_mut(&self.id).expect("Can't fail"); if let TestActor::Uninitialized(id @ None, _) = actor { *id = Some(message.id()); }; + drop(actors); let dispatch = message.into_dispatch(origin_msg_id, self.id); system.validate_and_run_dispatch(dispatch) } @@ -547,7 +617,7 @@ impl<'a> Program<'a> { pub fn read_state_bytes(&self, payload: Vec) -> Result> { self.manager .borrow_mut() - .with_externalities(|this| this.read_state_bytes(payload, &self.id)) + .read_state_bytes(payload, &self.id) } /// Reads the program’s transformed state as a byte vector. The transformed @@ -603,9 +673,9 @@ impl<'a> Program<'a> { wasm: Vec, args: Option>, ) -> Result> { - self.manager.borrow_mut().with_externalities(|this| { - this.read_state_bytes_using_wasm(payload, &self.id, fn_name, wasm, args) - }) + self.manager + .borrow_mut() + .read_state_bytes_using_wasm(payload, &self.id, fn_name, wasm, args) } /// Reads and decodes the program's state . @@ -729,7 +799,7 @@ impl<'a> Program<'a> { self.manager .borrow_mut() - .override_memory_pages(&self.id, mem); + .update_storage_pages(&self.id, mem); self.manager .borrow_mut() .override_balance(&self.id, balance); @@ -804,8 +874,8 @@ mod tests { sys.init_logger(); let user_id = 42; - sys.mint_to(user_id, 5000); - assert_eq!(sys.balance_of(user_id), 5000); + sys.mint_to(user_id, 10 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(user_id), 10 * crate::EXISTENTIAL_DEPOSIT); let mut prog = Program::from_opt_and_meta_code_with_id( &sys, @@ -814,16 +884,16 @@ mod tests { None, ); - prog.mint(1000); - assert_eq!(prog.balance(), 1000); + prog.mint(2 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(prog.balance(), 2 * crate::EXISTENTIAL_DEPOSIT); - prog.send_with_value(user_id, "init".to_string(), 500); - assert_eq!(prog.balance(), 1500); - assert_eq!(sys.balance_of(user_id), 4500); + prog.send_with_value(user_id, "init".to_string(), crate::EXISTENTIAL_DEPOSIT); + assert_eq!(prog.balance(), 3 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(user_id), 9 * crate::EXISTENTIAL_DEPOSIT); - prog.send_with_value(user_id, "PING".to_string(), 1000); - assert_eq!(prog.balance(), 2500); - assert_eq!(sys.balance_of(user_id), 3500); + prog.send_with_value(user_id, "PING".to_string(), 2 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(prog.balance(), 5 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(user_id), 7 * crate::EXISTENTIAL_DEPOSIT); } #[test] @@ -837,9 +907,9 @@ mod tests { let sender2 = 45; // Top-up senders balances - sys.mint_to(sender0, 10000); - sys.mint_to(sender1, 10000); - sys.mint_to(sender2, 10000); + sys.mint_to(sender0, 20 * crate::EXISTENTIAL_DEPOSIT); + sys.mint_to(sender1, 20 * crate::EXISTENTIAL_DEPOSIT); + sys.mint_to(sender2, 20 * crate::EXISTENTIAL_DEPOSIT); let prog = Program::from_opt_and_meta_code_with_id( &sys, @@ -852,27 +922,32 @@ mod tests { assert_eq!(prog.balance(), 0); // Send values to the program - prog.send_bytes_with_value(sender0, b"insert", 1000); - assert_eq!(sys.balance_of(sender0), 9000); - prog.send_bytes_with_value(sender1, b"insert", 2000); - assert_eq!(sys.balance_of(sender1), 8000); - prog.send_bytes_with_value(sender2, b"insert", 3000); - assert_eq!(sys.balance_of(sender2), 7000); + prog.send_bytes_with_value(sender0, b"insert", 2 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(sender0), 18 * crate::EXISTENTIAL_DEPOSIT); + prog.send_bytes_with_value(sender1, b"insert", 4 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(sender1), 16 * crate::EXISTENTIAL_DEPOSIT); + prog.send_bytes_with_value(sender2, b"insert", 6 * crate::EXISTENTIAL_DEPOSIT); + assert_eq!(sys.balance_of(sender2), 14 * crate::EXISTENTIAL_DEPOSIT); // Check program's balance - assert_eq!(prog.balance(), 1000 + 2000 + 3000); + assert_eq!(prog.balance(), (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT); // Request to smash the piggy bank and send the value to the receiver address prog.send_bytes(receiver, b"smash"); sys.claim_value_from_mailbox(receiver); - assert_eq!(sys.balance_of(receiver), 1000 + 2000 + 3000); + assert_eq!( + sys.balance_of(receiver), + (2 + 4 + 6) * crate::EXISTENTIAL_DEPOSIT + ); // Check program's balance is empty assert_eq!(prog.balance(), 0); } #[test] - #[should_panic(expected = "An attempt to mint value (1) less than existential deposit (500)")] + #[should_panic( + expected = "An attempt to mint value (1) less than existential deposit (10000000000000)" + )] fn mint_less_than_deposit() { System::new().mint_to(1, 1); } @@ -880,7 +955,7 @@ mod tests { #[test] #[should_panic(expected = "Insufficient value: user \ (0x0100000000000000000000000000000000000000000000000000000000000000) tries \ - to send (501) value, while his balance (500)")] + to send (10000000000001) value, while his balance (10000000000000)")] fn fails_on_insufficient_balance() { let sys = System::new(); @@ -907,7 +982,7 @@ mod tests { let sender = 42; let receiver = 84; - sys.mint_to(sender, 10000); + sys.mint_to(sender, 20 * crate::EXISTENTIAL_DEPOSIT); let prog = Program::from_opt_and_meta_code_with_id( &sys, diff --git a/gtest/src/system.rs b/gtest/src/system.rs index 70775d9b6c1..b0796098c5e 100644 --- a/gtest/src/system.rs +++ b/gtest/src/system.rs @@ -19,15 +19,63 @@ use crate::{ log::RunResult, mailbox::Mailbox, - manager::{Balance, ExtManager}, + manager::{Actors, Balance, ExtManager}, program::{Program, ProgramIdWrapper}, + BLOCK_DURATION_IN_MSECS, }; +use codec::{Decode, DecodeAll}; use colored::Colorize; use env_logger::{Builder, Env}; -use gear_core::{ids::CodeId, message::Dispatch}; +use gear_core::{ + ids::{CodeId, ProgramId}, + message::Dispatch, + pages::GearPage, +}; +use gear_lazy_pages::{LazyPagesStorage, LazyPagesVersion}; +use gear_lazy_pages_common::LazyPagesInitContext; use path_clean::PathClean; use std::{borrow::Cow, cell::RefCell, env, fs, io::Write, path::Path, thread}; +#[derive(Decode)] +#[codec(crate = codec)] +struct PageKey { + _page_storage_prefix: [u8; 32], + program_id: ProgramId, + _memory_infix: u32, + page: GearPage, +} + +#[derive(Debug)] +struct PagesStorage { + actors: Actors, +} + +impl LazyPagesStorage for PagesStorage { + fn page_exists(&self, mut key: &[u8]) -> bool { + let PageKey { + program_id, page, .. + } = PageKey::decode_all(&mut key).expect("Invalid key"); + self.actors + .borrow() + .get(&program_id) + .and_then(|(actor, _)| actor.get_pages_data()) + .map(|pages_data| pages_data.contains_key(&page)) + .unwrap_or(false) + } + + fn load_page(&mut self, mut key: &[u8], buffer: &mut [u8]) -> Option { + let PageKey { + program_id, page, .. + } = PageKey::decode_all(&mut key).expect("Invalid key"); + let actors = self.actors.borrow(); + let (actor, _balance) = actors.get(&program_id)?; + let pages_data = actor.get_pages_data()?; + let page_buf = pages_data.get(&page)?; + buffer.copy_from_slice(page_buf); + Some(page_buf.len() as u32) + } +} + /// The testing environment which simulates the chain state and its /// transactions but somehow the real on-chain execution environment /// could be different. @@ -45,7 +93,7 @@ pub struct System(pub(crate) RefCell); impl Default for System { fn default() -> Self { - Self(RefCell::new(ExtManager::new())) + Self::new() } } @@ -55,10 +103,18 @@ impl System { /// Create a new testing environment. pub fn new() -> Self { - assert!(gear_lazy_pages_interface::try_to_enable_lazy_pages( - Self::PAGE_STORAGE_PREFIX - )); - Default::default() + let ext_manager = ExtManager::new(); + + let actors = ext_manager.actors.clone(); + let pages_storage = PagesStorage { actors }; + gear_lazy_pages::init( + LazyPagesVersion::Version1, + LazyPagesInitContext::new(Self::PAGE_STORAGE_PREFIX), + pages_storage, + ) + .expect("Failed to init lazy-pages"); + + Self(RefCell::new(ext_manager)) } /// Init logger with "gwasm" target set to `debug` level. @@ -119,7 +175,10 @@ impl System { let next_block_number = manager.block_info.height + 1; manager.block_info.height = next_block_number; - manager.block_info.timestamp += 1000; + manager.block_info.timestamp = manager + .block_info + .timestamp + .saturating_add(BLOCK_DURATION_IN_MSECS); manager.process_delayed_dispatches(next_block_number) }) .collect::>>() diff --git a/images/internal_flow.jpg b/images/internal_flow.jpg index 5c8c85fba17..5cce3a3cfa0 100644 Binary files a/images/internal_flow.jpg and b/images/internal_flow.jpg differ diff --git a/images/title-grey.png b/images/title-grey.png index cb168c086fd..a9bbf769a79 100644 Binary files a/images/title-grey.png and b/images/title-grey.png differ diff --git a/lazy-pages/Cargo.toml b/lazy-pages/Cargo.toml index 8cbe5413800..2a29d357a81 100644 --- a/lazy-pages/Cargo.toml +++ b/lazy-pages/Cargo.toml @@ -10,13 +10,10 @@ repository.workspace = true [dependencies] log = { workspace = true, features = ["std"] } -sp-io = { workspace = true, features = ["std"] } -sp-std = { workspace = true, features = ["std"] } sp-wasm-interface = { workspace = true, features = ["std"] } cfg-if.workspace = true region.workspace = true derive_more.workspace = true -once_cell.workspace = true gear-sandbox-host.workspace = true gear-core.workspace = true diff --git a/lazy-pages/common/src/lib.rs b/lazy-pages/common/src/lib.rs index a925d4daf0f..398025d0735 100644 --- a/lazy-pages/common/src/lib.rs +++ b/lazy-pages/common/src/lib.rs @@ -20,12 +20,23 @@ #![no_std] +extern crate alloc; + +use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use core::{any::Any, fmt::Debug}; -use gear_core::{costs::CostPerPage, memory::HostPointer, pages::GearPage, str::LimitedStr}; +use gear_core::{ + costs::CostPerPage, + memory::HostPointer, + pages::{GearPage, PageU32Size, WasmPage}, + str::LimitedStr, +}; use num_enum::{IntoPrimitive, TryFromPrimitive}; -/// Memory access error during sys-call that lazy-pages have caught. +// TODO #3057 +const GLOBAL_NAME_GAS: &str = "gear_gas"; + +/// Memory access error during syscall that lazy-pages have caught. /// 0 index is reserved for an ok result. #[derive(Debug, Clone, IntoPrimitive, TryFromPrimitive)] #[repr(u8)] @@ -127,3 +138,20 @@ impl Status { *self == Self::Normal } } + +#[derive(Debug, Clone)] +pub struct LazyPagesInitContext { + pub page_sizes: Vec, + pub global_names: Vec>, + pub pages_storage_prefix: Vec, +} + +impl LazyPagesInitContext { + pub fn new(prefix: [u8; 32]) -> Self { + Self { + page_sizes: vec![WasmPage::size(), GearPage::size()], + global_names: vec![LimitedStr::from_small_str(GLOBAL_NAME_GAS)], + pages_storage_prefix: prefix.to_vec(), + } + } +} diff --git a/lazy-pages/interface/Cargo.toml b/lazy-pages/interface/Cargo.toml index d434bc509a7..72f395a815b 100644 --- a/lazy-pages/interface/Cargo.toml +++ b/lazy-pages/interface/Cargo.toml @@ -16,7 +16,6 @@ gear-core.workspace = true gear-common.workspace = true gear-lazy-pages-common.workspace = true gear-runtime-interface.workspace = true -gear-wasm-instrument.workspace = true sp-std.workspace = true diff --git a/lazy-pages/interface/src/lib.rs b/lazy-pages/interface/src/lib.rs index 7865a92fc3e..ef0d7d73f87 100644 --- a/lazy-pages/interface/src/lib.rs +++ b/lazy-pages/interface/src/lib.rs @@ -30,12 +30,10 @@ use gear_core::{ memory::{HostPointer, Memory, MemoryInterval}, pages::{GearPage, PageNumber, PageU32Size, WasmPage}, program::MemoryInfix, - str::LimitedStr, }; use gear_lazy_pages_common::{GlobalsAccessConfig, LazyPagesWeights, ProcessAccessError, Status}; -use gear_runtime_interface::{gear_ri, LazyPagesProgramContext, LazyPagesRuntimeContext}; -use gear_wasm_instrument::GLOBAL_NAME_GAS; -use sp_std::{mem, vec, vec::Vec}; +use gear_runtime_interface::{gear_ri, LazyPagesProgramContext}; +use sp_std::{mem, vec::Vec}; fn mprotect_lazy_pages(mem: &mut impl Memory, protect: bool) { if mem.get_buffer_host_addr().is_none() { @@ -48,13 +46,7 @@ fn mprotect_lazy_pages(mem: &mut impl Memory, protect: bool) { /// Try to enable and initialize lazy pages env pub fn try_to_enable_lazy_pages(prefix: [u8; 32]) -> bool { - let ctx = LazyPagesRuntimeContext { - page_sizes: vec![WasmPage::size(), GearPage::size()], - global_names: vec![LimitedStr::from_small_str(GLOBAL_NAME_GAS)], - pages_storage_prefix: prefix.to_vec(), - }; - - gear_ri::init_lazy_pages(ctx) + gear_ri::init_lazy_pages(gear_lazy_pages_common::LazyPagesInitContext::new(prefix).into()) } /// Protect and save storage keys for pages which has no data diff --git a/lazy-pages/src/common.rs b/lazy-pages/src/common.rs index a1841e56dcf..9e10aab5e2b 100644 --- a/lazy-pages/src/common.rs +++ b/lazy-pages/src/common.rs @@ -18,14 +18,13 @@ //! Lazy-pages structures for common usage. -use std::{collections::BTreeSet, mem::size_of, num::NonZeroU32}; - use crate::{globals::GlobalsContext, mprotect::MprotectError}; use gear_core::{ pages::{GearPage, PageDynSize, PageSizeNo, SizeManager, WasmPage}, str::LimitedStr, }; use gear_lazy_pages_common::{GlobalsAccessError, Status}; +use std::{collections::BTreeSet, fmt, mem, num::NonZeroU32}; // TODO: investigate error allocations #2441 #[derive(Debug, derive_more::Display, derive_more::From)] @@ -77,46 +76,100 @@ pub(crate) struct LazyPagesContext { } impl LazyPagesContext { + pub fn contexts( + &self, + ) -> Result<(&LazyPagesRuntimeContext, &LazyPagesExecutionContext), ContextError> { + Ok((self.runtime_context()?, self.execution_context()?)) + } + + pub fn contexts_mut( + &mut self, + ) -> Result<(&mut LazyPagesRuntimeContext, &mut LazyPagesExecutionContext), ContextError> { + let rt_ctx = self + .runtime_context + .as_mut() + .ok_or(ContextError::RuntimeContextIsNotSet)?; + let exec_ctx = self + .execution_context + .as_mut() + .ok_or(ContextError::ExecutionContextIsNotSet)?; + Ok((rt_ctx, exec_ctx)) + } + + pub fn runtime_context(&self) -> Result<&LazyPagesRuntimeContext, ContextError> { + self.runtime_context + .as_ref() + .ok_or(ContextError::RuntimeContextIsNotSet) + } + pub fn runtime_context_mut(&mut self) -> Result<&mut LazyPagesRuntimeContext, ContextError> { self.runtime_context .as_mut() .ok_or(ContextError::RuntimeContextIsNotSet) } + pub fn execution_context(&self) -> Result<&LazyPagesExecutionContext, ContextError> { self.execution_context .as_ref() .ok_or(ContextError::ExecutionContextIsNotSet) } - pub fn execution_context_mut( - &mut self, - ) -> Result<&mut LazyPagesExecutionContext, ContextError> { - self.execution_context - .as_mut() - .ok_or(ContextError::ExecutionContextIsNotSet) - } + pub fn set_runtime_context(&mut self, ctx: LazyPagesRuntimeContext) { self.runtime_context = Some(ctx); } + pub fn set_execution_context(&mut self, ctx: LazyPagesExecutionContext) { self.execution_context = Some(ctx); } } pub(crate) type Weights = [u64; WeightNo::Amount as usize]; -pub(crate) type PageSizes = [NonZeroU32; PageSizeNo::Amount as usize]; pub(crate) type GlobalNames = Vec>; +pub type PageSizes = [NonZeroU32; PageSizeNo::Amount as usize]; #[derive(Debug)] pub(crate) struct LazyPagesRuntimeContext { pub page_sizes: PageSizes, pub global_names: GlobalNames, pub pages_storage_prefix: Vec, + pub program_storage: Box, +} + +impl LazyPagesRuntimeContext { + pub fn page_has_data_in_storage(&self, prefix: &mut PagePrefix, page: GearPage) -> bool { + let key = prefix.key_for_page(page); + self.program_storage.page_exists(key) + } + + pub fn load_page_data_from_storage( + &mut self, + prefix: &mut PagePrefix, + page: GearPage, + buffer: &mut [u8], + ) -> Result { + let key = prefix.key_for_page(page); + if let Some(size) = self.program_storage.load_page(key, buffer) { + if size != GearPage::size(self) { + return Err(Error::InvalidPageDataSize { + expected: GearPage::size(self), + actual: size, + }); + } + Ok(true) + } else { + Ok(false) + } + } +} + +pub trait LazyPagesStorage: fmt::Debug { + fn page_exists(&self, key: &[u8]) -> bool; + + fn load_page(&mut self, key: &[u8], buffer: &mut [u8]) -> Option; } #[derive(Debug)] pub(crate) struct LazyPagesExecutionContext { - /// Lazy-pages page size. - pub page_sizes: PageSizes, /// Lazy-pages accesses weights. pub weights: Weights, /// Pointer to the begin of wasm memory buffer @@ -148,12 +201,6 @@ pub enum LazyPagesVersion { Version1, } -impl SizeManager for LazyPagesExecutionContext { - fn size_non_zero(&self) -> NonZeroU32 { - self.page_sizes[P::SIZE_NO] - } -} - impl SizeManager for LazyPagesRuntimeContext { fn size_non_zero(&self) -> NonZeroU32 { self.page_sizes[P::SIZE_NO] @@ -181,32 +228,6 @@ impl LazyPagesExecutionContext { } } - pub fn key_for_page(&mut self, page: GearPage) -> &[u8] { - self.program_storage_prefix.calc_key_for_page(page) - } - - pub fn page_has_data_in_storage(&mut self, page: GearPage) -> bool { - sp_io::storage::exists(self.key_for_page(page)) - } - - pub fn load_page_data_from_storage( - &mut self, - page: GearPage, - buffer: &mut [u8], - ) -> Result { - if let Some(size) = sp_io::storage::read(self.key_for_page(page), buffer, 0) { - if size != GearPage::size(self) { - return Err(Error::InvalidPageDataSize { - expected: GearPage::size(self), - actual: size, - }); - } - Ok(true) - } else { - Ok(false) - } - } - pub fn weight(&self, no: WeightNo) -> u64 { self.weights[no as usize] } @@ -225,18 +246,19 @@ pub(crate) struct PagePrefix { impl PagePrefix { /// New page prefix from program prefix - pub fn new_from_program_prefix(mut program_prefix: Vec) -> Self { - program_prefix.extend_from_slice(&u32::MAX.to_le_bytes()); + pub(crate) fn new_from_program_prefix(mut storage_prefix: Vec) -> Self { + storage_prefix.extend_from_slice(&u32::MAX.to_le_bytes()); Self { - buffer: program_prefix, + buffer: storage_prefix, } } /// Returns key in storage for `page`. - fn calc_key_for_page(&mut self, page: GearPage) -> &[u8] { + fn key_for_page(&mut self, page: GearPage) -> &[u8] { let len = self.buffer.len(); let page_no: u32 = page.into(); - self.buffer[len - size_of::()..len].copy_from_slice(page_no.to_le_bytes().as_slice()); + self.buffer[len - mem::size_of::()..len] + .copy_from_slice(page_no.to_le_bytes().as_slice()); &self.buffer } } diff --git a/lazy-pages/src/host_func.rs b/lazy-pages/src/host_func.rs index b6dc7d793ca..8fe031585f0 100644 --- a/lazy-pages/src/host_func.rs +++ b/lazy-pages/src/host_func.rs @@ -19,7 +19,7 @@ //! Host function call `pre_process_memory_accesses` support in lazy-pages. use crate::{ - common::{Error, GasCharger, LazyPagesExecutionContext, WeightNo}, + common::{Error, GasCharger, LazyPagesExecutionContext, LazyPagesRuntimeContext, WeightNo}, process::{self, AccessHandler}, LAZY_PAGES_CONTEXT, }; @@ -46,7 +46,7 @@ impl<'a> AccessHandler for HostFuncAccessHandler<'a> { } fn check_status_is_gas_exceeded() -> Result<(), Error> { - // Currently, we charge gas for sys-call after memory processing, so this can appear. + // Currently, we charge gas for syscall after memory processing, so this can appear. // In this case we do nothing, because all memory is already unprotected, and no need // to take in account pages data from storage, because gas is exceeded. Ok(()) @@ -97,7 +97,7 @@ impl<'a> AccessHandler for HostFuncAccessHandler<'a> { } fn accesses_pages( - ctx: &mut LazyPagesExecutionContext, + ctx: &LazyPagesRuntimeContext, accesses: &[MemoryInterval], pages: &mut BTreeSet, ) -> Result<(), Error> { @@ -137,24 +137,25 @@ pub fn pre_process_memory_accesses( LAZY_PAGES_CONTEXT .with(|ctx| { let mut ctx = ctx.borrow_mut(); - let ctx = ctx.execution_context_mut()?; + let (rt_ctx, exec_ctx) = ctx.contexts_mut()?; let gas_charger = { GasCharger { - read_cost: ctx.weight(WeightNo::HostFuncRead), - write_cost: ctx.weight(WeightNo::HostFuncWrite), - write_after_read_cost: ctx.weight(WeightNo::HostFuncWriteAfterRead), - load_data_cost: ctx.weight(WeightNo::LoadPageDataFromStorage), + read_cost: exec_ctx.weight(WeightNo::HostFuncRead), + write_cost: exec_ctx.weight(WeightNo::HostFuncWrite), + write_after_read_cost: exec_ctx.weight(WeightNo::HostFuncWriteAfterRead), + load_data_cost: exec_ctx.weight(WeightNo::LoadPageDataFromStorage), } }; let mut status = Status::Normal; if !reads.is_empty() { let mut read_pages = BTreeSet::new(); - accesses_pages(ctx, reads, &mut read_pages)?; + accesses_pages(rt_ctx, reads, &mut read_pages)?; status = process::process_lazy_pages( - ctx, + rt_ctx, + exec_ctx, HostFuncAccessHandler { is_write: false, gas_counter, @@ -171,10 +172,11 @@ pub fn pre_process_memory_accesses( if !writes.is_empty() { let mut write_pages = BTreeSet::new(); - accesses_pages(ctx, writes, &mut write_pages)?; + accesses_pages(rt_ctx, writes, &mut write_pages)?; status = process::process_lazy_pages( - ctx, + rt_ctx, + exec_ctx, HostFuncAccessHandler { is_write: true, gas_counter, diff --git a/lazy-pages/src/init_flag.rs b/lazy-pages/src/init_flag.rs index 4e1b490ce42..f4d7884ce22 100644 --- a/lazy-pages/src/init_flag.rs +++ b/lazy-pages/src/init_flag.rs @@ -19,13 +19,13 @@ #[cfg(not(test))] mod not_tests { use crate::InitError; - use once_cell::sync::OnceCell; + use std::sync::OnceLock; - pub struct InitializationFlag(OnceCell>); + pub struct InitializationFlag(OnceLock>); impl InitializationFlag { pub const fn new() -> Self { - Self(OnceCell::new()) + Self(OnceLock::new()) } pub fn get_or_init( diff --git a/lazy-pages/src/lib.rs b/lazy-pages/src/lib.rs index 304b826fa9d..e24ecd3ea77 100644 --- a/lazy-pages/src/lib.rs +++ b/lazy-pages/src/lib.rs @@ -27,11 +27,8 @@ //! It's not necessary behavior, but more simple and safe. #![allow(clippy::items_after_test_module)] - -use common::{LazyPagesExecutionContext, LazyPagesRuntimeContext}; -use gear_core::pages::{PageDynSize, PageNumber, PageSizeNo, WasmPage}; -use sp_std::vec::Vec; -use std::{cell::RefCell, convert::TryInto, num::NonZeroU32}; +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] mod common; mod globals; @@ -43,22 +40,24 @@ mod signal; mod sys; mod utils; -use crate::{ - common::{ContextError, LazyPagesContext, PagePrefix, PageSizes, WeightNo, Weights}, - globals::{GlobalNo, GlobalsContext}, - init_flag::InitializationFlag, -}; - #[cfg(test)] mod tests; -pub use common::LazyPagesVersion; -use gear_core::str::LimitedStr; -use gear_lazy_pages_common::{GlobalsAccessConfig, Status}; +pub use crate::common::LazyPagesStorage; +pub use common::{LazyPagesVersion, PageSizes}; pub use host_func::pre_process_memory_accesses; +use crate::{ + common::{ContextError, LazyPagesContext, PagePrefix, WeightNo, Weights}, + globals::{GlobalNo, GlobalsContext}, + init_flag::InitializationFlag, +}; +use common::{LazyPagesExecutionContext, LazyPagesRuntimeContext}; +use gear_core::pages::{PageDynSize, PageNumber, PageSizeNo, WasmPage}; +use gear_lazy_pages_common::{GlobalsAccessConfig, LazyPagesInitContext, Status}; use mprotect::MprotectError; use signal::{DefaultUserSignalHandler, UserSignalHandler}; +use std::{cell::RefCell, convert::TryInto, num::NonZeroU32}; /// Initialize lazy-pages once for process. static LAZY_PAGES_INITIALIZED: InitializationFlag = InitializationFlag::new(); @@ -150,17 +149,11 @@ pub fn initialize_for_program( })?; let execution_ctx = LazyPagesExecutionContext { - page_sizes: runtime_ctx.page_sizes, weights, wasm_mem_addr, wasm_mem_size, program_storage_prefix: PagePrefix::new_from_program_prefix( - runtime_ctx - .pages_storage_prefix - .iter() - .chain(program_key.iter()) - .copied() - .collect(), + [runtime_ctx.pages_storage_prefix.as_slice(), &program_key].concat(), ), accessed_pages: Default::default(), write_accessed_pages: Default::default(), @@ -175,7 +168,7 @@ pub fn initialize_for_program( // Set protection if wasm memory exist. if let Some(addr) = wasm_mem_addr { - let stack_end_offset = execution_ctx.stack_end.offset(&execution_ctx); + let stack_end_offset = execution_ctx.stack_end.offset(runtime_ctx); log::trace!("{addr:#x} {stack_end_offset:#x}"); // `+` and `-` are safe because we checked // that `stack_end` is less or equal to `wasm_mem_size` and wasm end addr fits usize. @@ -198,29 +191,29 @@ pub fn initialize_for_program( pub fn set_lazy_pages_protection() -> Result<(), Error> { LAZY_PAGES_CONTEXT.with(|ctx| { let ctx = ctx.borrow(); - let ctx = ctx.execution_context()?; - let mem_addr = ctx.wasm_mem_addr.ok_or(Error::WasmMemAddrIsNotSet)?; - let start_offset = ctx.stack_end.offset(ctx); - let mem_size = ctx.wasm_mem_size.offset(ctx); + let (rt_ctx, exec_ctx) = ctx.contexts()?; + let mem_addr = exec_ctx.wasm_mem_addr.ok_or(Error::WasmMemAddrIsNotSet)?; + let start_offset = exec_ctx.stack_end.offset(rt_ctx); + let mem_size = exec_ctx.wasm_mem_size.offset(rt_ctx); // Set r/w protection for all pages except stack pages and write accessed pages. mprotect::mprotect_mem_interval_except_pages( mem_addr, start_offset as usize, mem_size as usize, - ctx.write_accessed_pages.iter().copied(), - ctx, + exec_ctx.write_accessed_pages.iter().copied(), + rt_ctx, false, false, )?; // Set only write protection for already accessed, but not write accessed pages. - let read_only_pages = ctx + let read_only_pages = exec_ctx .accessed_pages .iter() - .filter(|&&page| !ctx.write_accessed_pages.contains(&page)) + .filter(|&&page| !exec_ctx.write_accessed_pages.contains(&page)) .copied(); - mprotect::mprotect_pages(mem_addr, read_only_pages, ctx, true, false)?; + mprotect::mprotect_pages(mem_addr, read_only_pages, rt_ctx, true, false)?; // After that protections are: // 1) Only execution protection for stack pages. @@ -236,9 +229,9 @@ pub fn set_lazy_pages_protection() -> Result<(), Error> { pub fn unset_lazy_pages_protection() -> Result<(), Error> { LAZY_PAGES_CONTEXT.with(|ctx| { let ctx = ctx.borrow(); - let ctx = ctx.execution_context()?; - let addr = ctx.wasm_mem_addr.ok_or(Error::WasmMemAddrIsNotSet)?; - let size = ctx.wasm_mem_size.offset(ctx); + let (rt_ctx, exec_ctx) = ctx.contexts()?; + let addr = exec_ctx.wasm_mem_addr.ok_or(Error::WasmMemAddrIsNotSet)?; + let size = exec_ctx.wasm_mem_size.offset(rt_ctx); mprotect::mprotect_interval(addr, size as usize, true, true)?; Ok(()) }) @@ -252,7 +245,7 @@ pub fn change_wasm_mem_addr_and_size(addr: Option, size: Option) -> LAZY_PAGES_CONTEXT.with(|ctx| { let mut ctx = ctx.borrow_mut(); - let ctx = ctx.execution_context_mut()?; + let (rt_ctx, exec_ctx) = ctx.contexts_mut()?; let addr = match addr { Some(addr) => match addr % region::page::size() { @@ -260,21 +253,21 @@ pub fn change_wasm_mem_addr_and_size(addr: Option, size: Option) -> _ => return Err(Error::WasmMemAddrIsNotAligned(addr)), }, - None => match ctx.wasm_mem_addr { + None => match exec_ctx.wasm_mem_addr { Some(addr) => addr, None => return Err(Error::WasmMemAddrIsNotSet), }, }; let size = match size { - Some(size) => WasmPage::new(size, ctx).ok_or(Error::WasmMemSizeOverflow)?, - None => ctx.wasm_mem_size, + Some(size) => WasmPage::new(size, rt_ctx).ok_or(Error::WasmMemSizeOverflow)?, + None => exec_ctx.wasm_mem_size, }; - check_memory_interval(addr, size.offset(ctx))?; + check_memory_interval(addr, size.offset(rt_ctx))?; - ctx.wasm_mem_addr = Some(addr); - ctx.wasm_mem_size = size; + exec_ctx.wasm_mem_addr = Some(addr); + exec_ctx.wasm_mem_size = size; Ok(()) }) @@ -381,14 +374,19 @@ pub(crate) fn reset_init_flag() { } /// Initialize lazy-pages for current thread. -fn init_with_handler( +fn init_with_handler( _version: LazyPagesVersion, - page_sizes: Vec, - global_names: Vec>, - pages_storage_prefix: Vec, + ctx: LazyPagesInitContext, + pages_storage: S, ) -> Result<(), InitError> { use InitError::*; + let LazyPagesInitContext { + page_sizes, + global_names, + pages_storage_prefix, + } = ctx; + // Check that sizes are not zero let page_sizes = page_sizes .into_iter() @@ -430,6 +428,7 @@ fn init_with_handler( page_sizes, global_names, pages_storage_prefix, + program_storage: Box::new(pages_storage), }) }); @@ -440,16 +439,10 @@ fn init_with_handler( Ok(()) } -pub fn init( +pub fn init( version: LazyPagesVersion, - page_sizes: Vec, - global_names: Vec>, - pages_storage_prefix: Vec, + ctx: LazyPagesInitContext, + pages_storage: S, ) -> Result<(), InitError> { - init_with_handler::( - version, - page_sizes, - global_names, - pages_storage_prefix, - ) + init_with_handler::(version, ctx, pages_storage) } diff --git a/lazy-pages/src/process.rs b/lazy-pages/src/process.rs index 4e54e9ad54a..17c09db8d38 100644 --- a/lazy-pages/src/process.rs +++ b/lazy-pages/src/process.rs @@ -19,7 +19,7 @@ //! Lazy-pages memory accesses processing main logic. use crate::{ - common::{Error, LazyPagesExecutionContext}, + common::{Error, LazyPagesExecutionContext, LazyPagesRuntimeContext}, mprotect, }; use gear_core::pages::{GearPage, PageDynSize}; @@ -82,29 +82,30 @@ pub(crate) trait AccessHandler { /// program's wasm memory. /// 3) Charge gas for access and data loading. pub(crate) fn process_lazy_pages( - ctx: &mut LazyPagesExecutionContext, + rt_ctx: &mut LazyPagesRuntimeContext, + exec_ctx: &mut LazyPagesExecutionContext, mut handler: H, pages: H::Pages, ) -> Result { - let wasm_mem_size = ctx.wasm_mem_size.offset(ctx); + let wasm_mem_size = exec_ctx.wasm_mem_size.offset(rt_ctx); unsafe { if let Some(last_page) = H::last_page(&pages) { // Check that all pages are inside wasm memory. - if last_page.end_offset(ctx) >= wasm_mem_size { + if last_page.end_offset(rt_ctx) >= wasm_mem_size { return Err(Error::OutOfWasmMemoryAccess); } } else { // Accessed pages are empty - nothing to do. - return handler.into_output(ctx); + return handler.into_output(exec_ctx); } - if ctx.status != Status::Normal { + if exec_ctx.status != Status::Normal { H::check_status_is_gas_exceeded()?; - return handler.into_output(ctx); + return handler.into_output(exec_ctx); } - let stack_end = ctx.stack_end; - let wasm_mem_addr = ctx.wasm_mem_addr.ok_or(Error::WasmMemAddrIsNotSet)?; + let stack_end = exec_ctx.stack_end; + let wasm_mem_addr = exec_ctx.wasm_mem_addr.ok_or(Error::WasmMemAddrIsNotSet)?; // Returns `true` if new status is not `Normal`. let update_status = |ctx: &mut LazyPagesExecutionContext, status| { @@ -126,33 +127,33 @@ pub(crate) fn process_lazy_pages( } }; - let page_size = GearPage::size(ctx) as usize; + let page_size = GearPage::size(rt_ctx) as usize; let process_one = |page: GearPage| { - let page_offset = page.offset(ctx); + let page_offset = page.offset(rt_ctx); let page_buffer_ptr = (wasm_mem_addr as *mut u8).add(page_offset as usize); let protect_page = |prot_write| { mprotect::mprotect_interval(page_buffer_ptr as usize, page_size, true, prot_write) }; - if page_offset < stack_end.offset(ctx) { + if page_offset < stack_end.offset(rt_ctx) { // Nothing to do, page has r/w accesses and data is in correct state. H::check_stack_memory_access()?; - } else if ctx.is_write_accessed(page) { + } else if exec_ctx.is_write_accessed(page) { // Nothing to do, page has r/w accesses and data is in correct state. H::check_write_accessed_memory_access()?; - } else if ctx.is_accessed(page) { + } else if exec_ctx.is_accessed(page) { if handler.is_write() { // Charges for page write access let status = handler.charge_for_page_access(page, true)?; - if update_status(ctx, status)? { + if update_status(exec_ctx, status)? { return Ok(()); } // Sets read/write protection access for page and add page to write accessed protect_page(true)?; - ctx.set_write_accessed(page)?; + exec_ctx.set_write_accessed(page)?; } else { // Nothing to do, page has read accesses and data is in correct state. H::check_read_from_accessed_memory()?; @@ -160,14 +161,16 @@ pub(crate) fn process_lazy_pages( } else { // Charge for page access. let status = handler.charge_for_page_access(page, false)?; - if update_status(ctx, status)? { + if update_status(exec_ctx, status)? { return Ok(()); } - let unprotected = if ctx.page_has_data_in_storage(page) { + let unprotected = if rt_ctx + .page_has_data_in_storage(&mut exec_ctx.program_storage_prefix, page) + { // Charge for page data loading from storage. let status = handler.charge_for_page_data_loading()?; - if update_status(ctx, status)? { + if update_status(exec_ctx, status)? { return Ok(()); } @@ -176,7 +179,11 @@ pub(crate) fn process_lazy_pages( // Load and write data to memory. let buffer_as_slice = slice::from_raw_parts_mut(page_buffer_ptr, page_size); - if !ctx.load_page_data_from_storage(page, buffer_as_slice)? { + if !rt_ctx.load_page_data_from_storage( + &mut exec_ctx.program_storage_prefix, + page, + buffer_as_slice, + )? { unreachable!("`read` returns, that page has no data, but `exist` returns that there is one"); } true @@ -184,12 +191,12 @@ pub(crate) fn process_lazy_pages( false }; - ctx.set_accessed(page); + exec_ctx.set_accessed(page); if handler.is_write() { if !unprotected { protect_page(true)?; } - ctx.set_write_accessed(page)?; + exec_ctx.set_write_accessed(page)?; } else { // Set only read access for page. protect_page(false)?; @@ -201,6 +208,6 @@ pub(crate) fn process_lazy_pages( H::process_pages(pages, process_one)?; - handler.into_output(ctx) + handler.into_output(exec_ctx) } } diff --git a/lazy-pages/src/signal.rs b/lazy-pages/src/signal.rs index c04e4790200..927650491c4 100644 --- a/lazy-pages/src/signal.rs +++ b/lazy-pages/src/signal.rs @@ -19,7 +19,7 @@ //! Lazy-pages system signals accesses support. use crate::{ - common::{Error, GasCharger, LazyPagesExecutionContext, WeightNo}, + common::{Error, GasCharger, LazyPagesExecutionContext, LazyPagesRuntimeContext, WeightNo}, globals::{self, GlobalNo}, process::{self, AccessHandler}, LAZY_PAGES_CONTEXT, @@ -31,7 +31,7 @@ use std::convert::TryFrom; pub(crate) trait UserSignalHandler { /// # Safety /// - /// It's expected handler calls sys-calls to protect memory + /// It's expected handler calls syscalls to protect memory unsafe fn handle(info: ExceptionInfo) -> Result<(), Error>; } @@ -59,12 +59,13 @@ pub(crate) struct ExceptionInfo { /// instruction, which cause signal. Now memory which this instruction accesses /// is not protected and with correct data. unsafe fn user_signal_handler_internal( - ctx: &mut LazyPagesExecutionContext, + rt_ctx: &mut LazyPagesRuntimeContext, + exec_ctx: &mut LazyPagesExecutionContext, info: ExceptionInfo, ) -> Result<(), Error> { let native_addr = info.fault_addr as usize; let is_write = info.is_write.ok_or_else(|| Error::ReadOrWriteIsUnknown)?; - let wasm_mem_addr = ctx + let wasm_mem_addr = exec_ctx .wasm_mem_addr .ok_or_else(|| Error::WasmMemAddrIsNotSet)?; @@ -74,14 +75,14 @@ unsafe fn user_signal_handler_internal( let offset = u32::try_from(native_addr - wasm_mem_addr).map_err(|_| Error::OutOfWasmMemoryAccess)?; - let page = GearPage::from_offset(ctx, offset); + let page = GearPage::from_offset(rt_ctx, offset); - let gas_ctx = if let Some(globals_config) = ctx.globals_context.as_ref() { + let gas_ctx = if let Some(globals_config) = exec_ctx.globals_context.as_ref() { let gas_charger = GasCharger { - read_cost: ctx.weight(WeightNo::SignalRead), - write_cost: ctx.weight(WeightNo::SignalWrite), - write_after_read_cost: ctx.weight(WeightNo::SignalWriteAfterRead), - load_data_cost: ctx.weight(WeightNo::LoadPageDataFromStorage), + read_cost: exec_ctx.weight(WeightNo::SignalRead), + write_cost: exec_ctx.weight(WeightNo::SignalWrite), + write_after_read_cost: exec_ctx.weight(WeightNo::SignalWriteAfterRead), + load_data_cost: exec_ctx.weight(WeightNo::LoadPageDataFromStorage), }; let gas_counter = globals::apply_for_global( @@ -96,7 +97,7 @@ unsafe fn user_signal_handler_internal( }; let handler = SignalAccessHandler { is_write, gas_ctx }; - process::process_lazy_pages(ctx, handler, page) + process::process_lazy_pages(rt_ctx, exec_ctx, handler, page) } /// User signal handler. Logic can depends on lazy-pages version. @@ -105,8 +106,8 @@ pub(crate) unsafe fn user_signal_handler(info: ExceptionInfo) -> Result<(), Erro log::debug!("Interrupted, exception info = {:?}", info); LAZY_PAGES_CONTEXT.with(|ctx| { let mut ctx = ctx.borrow_mut(); - let ctx = ctx.execution_context_mut()?; - user_signal_handler_internal(ctx, info) + let (rt_ctx, exec_ctx) = ctx.contexts_mut()?; + user_signal_handler_internal(rt_ctx, exec_ctx, info) }) } diff --git a/lazy-pages/src/sys/unix.rs b/lazy-pages/src/sys/unix.rs index 90b08abde5c..df0fe9fcab8 100644 --- a/lazy-pages/src/sys/unix.rs +++ b/lazy-pages/src/sys/unix.rs @@ -27,8 +27,7 @@ use nix::{ libc::{c_void, siginfo_t}, sys::{signal, signal::SigHandler}, }; -use once_cell::sync::OnceCell; -use std::io; +use std::{io, sync::OnceLock}; /// Signal handler which has been set before lazy-pages initialization. /// Currently use to support wasmer signal handler. @@ -37,7 +36,7 @@ use std::io; /// see https://github.com/gear-tech/substrate/blob/gear-stable/client/executor/common/src/sandbox/wasmer_backend.rs /// and https://github.com/wasmerio/wasmer/blob/e6857d116134bdc9ab6a1dabc3544cf8e6aee22b/lib/vm/src/trap/traphandlers.rs#L548 /// So, if we receive signal from unknown memory we should try to use old (wasmer) signal handler. -static mut OLD_SIG_HANDLER: OnceCell = OnceCell::new(); +static OLD_SIG_HANDLER: OnceLock = OnceLock::new(); cfg_if! { if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { diff --git a/lazy-pages/src/tests.rs b/lazy-pages/src/tests.rs index a5af3986526..d7e47544ab9 100644 --- a/lazy-pages/src/tests.rs +++ b/lazy-pages/src/tests.rs @@ -17,15 +17,40 @@ // along with this program. If not, see . use crate::{ - common::Error, init_with_handler, mprotect, signal::ExceptionInfo, LazyPagesVersion, - UserSignalHandler, + common::Error, init_with_handler, mprotect, signal::ExceptionInfo, LazyPagesStorage, + LazyPagesVersion, UserSignalHandler, }; use gear_core::{ pages::{GearPage, PageDynSize, PageU32Size, WasmPage}, str::LimitedStr, }; +use gear_lazy_pages_common::LazyPagesInitContext; use region::Protection; +#[derive(Debug)] +struct NoopStorage; + +impl LazyPagesStorage for NoopStorage { + fn page_exists(&self, _key: &[u8]) -> bool { + unreachable!() + } + + fn load_page(&mut self, _key: &[u8], _buffer: &mut [u8]) -> Option { + unreachable!() + } +} + +fn init_ctx() -> LazyPagesInitContext { + LazyPagesInitContext { + page_sizes: vec![ + ::size(), + ::size(), + ], + global_names: vec![LimitedStr::from_small_str("gear_gas")], + pages_storage_prefix: Default::default(), + } +} + fn handler_tester(f: F) { crate::reset_init_flag(); f(); @@ -77,16 +102,8 @@ fn read_write_flag_works() { } } - init_with_handler::( - LazyPagesVersion::Version1, - vec![ - ::size(), - ::size(), - ], - vec![LimitedStr::from_small_str("gear_gas")], - Default::default(), - ) - .unwrap(); + init_with_handler::(LazyPagesVersion::Version1, init_ctx(), NoopStorage) + .unwrap(); let page_size = region::page::size(); let addr = region::alloc(page_size, Protection::NONE).unwrap(); @@ -135,16 +152,8 @@ fn test_mprotect_pages() { env_logger::init(); - init_with_handler::( - LazyPagesVersion::Version1, - vec![ - ::size(), - ::size(), - ], - vec![LimitedStr::from_small_str("gear_gas")], - Default::default(), - ) - .unwrap(); + init_with_handler::(LazyPagesVersion::Version1, init_ctx(), NoopStorage) + .unwrap(); let mut v = vec![0u8; 3 * ::size() as usize]; let buff = v.as_mut_ptr() as usize; diff --git a/node/cli/src/command.rs b/node/cli/src/command.rs index 231885232a7..33a27b69a0b 100644 --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -156,9 +156,10 @@ pub fn run() -> sc_cli::Result<()> { .execution .get_or_insert(ExecutionStrategy::Wasm); + let is_dev = base.shared_params.dev; + // Checking if node supposed to be validator (explicitly or by shortcuts). let is_validator = base.validator - || base.shared_params.dev || base.alice || base.bob || base.charlie @@ -169,7 +170,7 @@ pub fn run() -> sc_cli::Result<()> { || base.two; // Denying ability to validate blocks with non-wasm execution. - if is_validator && *execution_strategy != ExecutionStrategy::Wasm { + if !is_dev && is_validator && *execution_strategy != ExecutionStrategy::Wasm { return Err( "Node can be --validator only with wasm execution strategy. To enable it run the node with `--execution wasm` or without the flag for default value." .into(), diff --git a/pallets/gas/src/lib.rs b/pallets/gas/src/lib.rs index 21775b42b7d..94265b83234 100644 --- a/pallets/gas/src/lib.rs +++ b/pallets/gas/src/lib.rs @@ -123,6 +123,8 @@ //! The Gear Gas Pallet doesn't depend on the `GenesisConfig`. #![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] use common::{ storage::{MapStorage, ValueStorage}, diff --git a/pallets/gear-messenger/src/lib.rs b/pallets/gear-messenger/src/lib.rs index 0c0330ec073..e20633b9eab 100644 --- a/pallets/gear-messenger/src/lib.rs +++ b/pallets/gear-messenger/src/lib.rs @@ -133,6 +133,8 @@ //! length overflow (see Gear Payment Pallet). #![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] // Runtime mock for running tests. #[cfg(test)] diff --git a/pallets/gear-program/src/lib.rs b/pallets/gear-program/src/lib.rs index f6ea7129d3e..481d976c1dc 100644 --- a/pallets/gear-program/src/lib.rs +++ b/pallets/gear-program/src/lib.rs @@ -128,6 +128,8 @@ //! The Gear Program Pallet doesn't depend on the `GenesisConfig`. #![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] use sp_std::{convert::TryInto, prelude::*}; diff --git a/pallets/gear-scheduler/src/lib.rs b/pallets/gear-scheduler/src/lib.rs index 1049aefdd1b..d022637da47 100644 --- a/pallets/gear-scheduler/src/lib.rs +++ b/pallets/gear-scheduler/src/lib.rs @@ -19,6 +19,8 @@ //! # Gear Scheduler Pallet #![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] // Runtime mock for running tests. #[cfg(test)] diff --git a/pallets/gear/Cargo.toml b/pallets/gear/Cargo.toml index b0e76956394..a70eaf7fe1c 100644 --- a/pallets/gear/Cargo.toml +++ b/pallets/gear/Cargo.toml @@ -106,6 +106,7 @@ demo-rwlock.workspace = true demo-reservation-manager.workspace = true demo-send-from-reservation.workspace = true demo-signal-entry.workspace = true +demo-signal-wait.workspace = true demo-state-rollback.workspace = true demo-async-signal-entry.workspace = true demo-async-custom-entry.workspace = true @@ -113,6 +114,7 @@ demo-out-of-memory.workspace = true demo-ping.workspace = true demo-sync-duplicate.workspace = true demo-custom.workspace = true +demo-delayed-reservation-sender.workspace = true test-syscalls.workspace = true page_size.workspace = true frame-support-test = { workspace = true, features = ["std"] } diff --git a/pallets/gear/rpc/runtime-api/src/lib.rs b/pallets/gear/rpc/runtime-api/src/lib.rs index 7dddda3272b..1cd1adcbda7 100644 --- a/pallets/gear/rpc/runtime-api/src/lib.rs +++ b/pallets/gear/rpc/runtime-api/src/lib.rs @@ -17,6 +17,8 @@ // along with this program. If not, see . #![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] pub use pallet_gear::{manager::HandleKind, GasInfo}; use sp_core::H256; diff --git a/pallets/gear/rpc/src/lib.rs b/pallets/gear/rpc/src/lib.rs index e0dab285f06..3f7b72c4bdb 100644 --- a/pallets/gear/rpc/src/lib.rs +++ b/pallets/gear/rpc/src/lib.rs @@ -20,6 +20,8 @@ #![allow(clippy::too_many_arguments)] #![allow(where_clauses_object_safety)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] use gear_common::Origin; use gear_core::ids::{CodeId, MessageId, ProgramId}; diff --git a/pallets/gear/src/benchmarking/code.rs b/pallets/gear/src/benchmarking/code.rs index 70f86827b96..90821ae1a37 100644 --- a/pallets/gear/src/benchmarking/code.rs +++ b/pallets/gear/src/benchmarking/code.rs @@ -43,7 +43,7 @@ use gear_wasm_instrument::{ self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Section, ValueType, }, }, - syscalls::SysCallName, + syscalls::SyscallName, STACK_END_EXPORT_NAME, }; use sp_std::{borrow::ToOwned, convert::TryFrom, marker::PhantomData, prelude::*}; @@ -70,7 +70,7 @@ pub struct ModuleDefinition { /// Creates the supplied amount of i64 mutable globals initialized with random values. pub num_globals: u32, /// List of syscalls that the module should import. They start with index 0. - pub imported_functions: Vec, + pub imported_functions: Vec, /// Function body of the exported `init` function. Body is empty if `None`. /// Its index is `imported_functions.len()`. pub init_body: Option, diff --git a/pallets/gear/src/benchmarking/mod.rs b/pallets/gear/src/benchmarking/mod.rs index cc866af80d1..f829491f962 100644 --- a/pallets/gear/src/benchmarking/mod.rs +++ b/pallets/gear/src/benchmarking/mod.rs @@ -102,7 +102,7 @@ use gear_core_errors::*; use gear_sandbox::{default_executor::Store, SandboxMemory, SandboxStore}; use gear_wasm_instrument::{ parity_wasm::elements::{BlockType, BrTableData, Instruction, SignExtInstruction, ValueType}, - syscalls::SysCallName, + syscalls::SyscallName, }; use pallet_authorship::Pallet as AuthorshipPallet; use sp_consensus_babe::{ @@ -862,7 +862,7 @@ benchmarks! { gr_message_id { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::MessageId, r)?; + let exec = Benches::::getter(SyscallName::MessageId, r)?; }: { res.replace(run_process(exec)); } @@ -873,7 +873,7 @@ benchmarks! { gr_program_id { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::ProgramId, r)?; + let exec = Benches::::getter(SyscallName::ProgramId, r)?; }: { res.replace(run_process(exec)); } @@ -884,7 +884,7 @@ benchmarks! { gr_source { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::Source, r)?; + let exec = Benches::::getter(SyscallName::Source, r)?; }: { res.replace(run_process(exec)); } @@ -895,7 +895,7 @@ benchmarks! { gr_value { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::Value, r)?; + let exec = Benches::::getter(SyscallName::Value, r)?; }: { res.replace(run_process(exec)); } @@ -906,7 +906,7 @@ benchmarks! { gr_value_available { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::ValueAvailable, r)?; + let exec = Benches::::getter(SyscallName::ValueAvailable, r)?; }: { res.replace(run_process(exec)); } @@ -917,7 +917,7 @@ benchmarks! { gr_gas_available { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::GasAvailable, r)?; + let exec = Benches::::getter(SyscallName::GasAvailable, r)?; }: { res.replace(run_process(exec)); } @@ -928,7 +928,7 @@ benchmarks! { gr_size { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::Size, r)?; + let exec = Benches::::getter(SyscallName::Size, r)?; }: { res.replace(run_process(exec)); } @@ -972,7 +972,7 @@ benchmarks! { gr_block_height { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::BlockHeight, r)?; + let exec = Benches::::getter(SyscallName::BlockHeight, r)?; }: { res.replace(run_process(exec)); } @@ -983,7 +983,7 @@ benchmarks! { gr_block_timestamp { let r in 0 .. API_BENCHMARK_BATCHES; let mut res = None; - let exec = Benches::::getter(SysCallName::BlockTimestamp, r)?; + let exec = Benches::::getter(SyscallName::BlockTimestamp, r)?; }: { res.replace(run_process(exec)); } @@ -1452,7 +1452,7 @@ benchmarks! { gr_exit { let r in 0 .. 1; let mut res = None; - let exec = Benches::::termination_bench(SysCallName::Exit, Some(0xff), r)?; + let exec = Benches::::termination_bench(SyscallName::Exit, Some(0xff), r)?; }: { res.replace(run_process(exec)); } @@ -1465,7 +1465,7 @@ benchmarks! { gr_leave { let r in 0 .. 1; let mut res = None; - let exec = Benches::::termination_bench(SysCallName::Leave, None, r)?; + let exec = Benches::::termination_bench(SyscallName::Leave, None, r)?; }: { res.replace(run_process(exec)); } @@ -1478,7 +1478,7 @@ benchmarks! { gr_wait { let r in 0 .. 1; let mut res = None; - let exec = Benches::::termination_bench(SysCallName::Wait, None, r)?; + let exec = Benches::::termination_bench(SyscallName::Wait, None, r)?; }: { res.replace(run_process(exec)); } @@ -1491,7 +1491,7 @@ benchmarks! { gr_wait_for { let r in 0 .. 1; let mut res = None; - let exec = Benches::::termination_bench(SysCallName::WaitFor, Some(10), r)?; + let exec = Benches::::termination_bench(SyscallName::WaitFor, Some(10), r)?; }: { res.replace(run_process(exec)); } @@ -1504,7 +1504,7 @@ benchmarks! { gr_wait_up_to { let r in 0 .. 1; let mut res = None; - let exec = Benches::::termination_bench(SysCallName::WaitUpTo, Some(100), r)?; + let exec = Benches::::termination_bench(SyscallName::WaitUpTo, Some(100), r)?; }: { res.replace(run_process(exec)); } @@ -1536,7 +1536,7 @@ benchmarks! { gr_create_program_per_kb { let p in 0 .. MAX_PAYLOAD_LEN_KB; - // salt cannot be zero because we cannot execute batch of sys-calls + // salt cannot be zero because we cannot execute batch of syscalls // as salt will be the same and we will get `ProgramAlreadyExists` error let s in 1 .. MAX_PAYLOAD_LEN_KB; let mut res = None; @@ -1561,7 +1561,7 @@ benchmarks! { gr_create_program_wgas_per_kb { let p in 0 .. MAX_PAYLOAD_LEN_KB; - // salt cannot be zero because we cannot execute batch of sys-calls + // salt cannot be zero because we cannot execute batch of syscalls // as salt will be the same and we will get `ProgramAlreadyExists` error let s in 1 .. MAX_PAYLOAD_LEN_KB; let mut res = None; diff --git a/pallets/gear/src/benchmarking/syscalls.rs b/pallets/gear/src/benchmarking/syscalls.rs index 48e6869b26e..4e0b6484fa9 100644 --- a/pallets/gear/src/benchmarking/syscalls.rs +++ b/pallets/gear/src/benchmarking/syscalls.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Benchmarks for gear sys-calls. +//! Benchmarks for gear syscalls. use super::{ code::{ @@ -42,7 +42,7 @@ use gear_core::{ reservation::GasReservationSlot, }; use gear_core_errors::*; -use gear_wasm_instrument::{parity_wasm::elements::Instruction, syscalls::SysCallName}; +use gear_wasm_instrument::{parity_wasm::elements::Instruction, syscalls::SyscallName}; use sp_core::Get; use sp_runtime::{codec::Encode, traits::UniqueSaturatedInto}; @@ -182,7 +182,7 @@ where program.gas_reservation_map.insert( ReservationId::from(x as u64), GasReservationSlot { - amount: 1_000, + amount: 100_000, start: 1, finish: 100, }, @@ -226,7 +226,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(0)), - imported_functions: vec![SysCallName::Alloc], + imported_functions: vec![SyscallName::Alloc], handle_body: Some(body::repeated( repetitions * API_BENCHMARK_BATCH_SIZE, &instructions, @@ -254,7 +254,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(0)), - imported_functions: vec![SysCallName::Alloc, SysCallName::Free], + imported_functions: vec![SyscallName::Alloc, SyscallName::Free], handle_body: Some(body::from_instructions(instructions)), ..Default::default() }; @@ -271,7 +271,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::ReserveGas], + imported_functions: vec![SyscallName::ReserveGas], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -303,7 +303,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::UnreserveGas], + imported_functions: vec![SyscallName::UnreserveGas], data_segments: vec![DataSegment { offset: reservation_id_offset, value: reservation_id_bytes, @@ -328,7 +328,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::SystemReserveGas], + imported_functions: vec![SyscallName::SystemReserveGas], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -343,7 +343,7 @@ where Self::prepare_handle(module, 0) } - pub fn getter(name: SysCallName, r: u32) -> Result, &'static str> { + pub fn getter(name: SyscallName, r: u32) -> Result, &'static str> { let repetitions = r * API_BENCHMARK_BATCH_SIZE; let res_offset = COMMON_OFFSET; @@ -369,7 +369,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::EnvVars], + imported_functions: vec![SyscallName::EnvVars], handle_body: Some(body::syscall( repetitions, &[ @@ -395,7 +395,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::Read], + imported_functions: vec![SyscallName::Read], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -424,7 +424,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::Read], + imported_functions: vec![SyscallName::Read], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -450,7 +450,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::Random], + imported_functions: vec![SyscallName::Random], handle_body: Some(body::syscall( repetitions, &[ @@ -477,7 +477,7 @@ where // so `gr_reply_deposit` can be called and won't fail. let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::ReplyDeposit, SysCallName::Send], + imported_functions: vec![SyscallName::ReplyDeposit, SyscallName::Send], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -532,9 +532,9 @@ where let name = if wgas { params.insert(3, InstrI64Const(100_000_000)); - SysCallName::SendWGas + SyscallName::SendWGas } else { - SysCallName::Send + SyscallName::Send }; let module = ModuleDefinition { @@ -553,7 +553,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::SendInit], + imported_functions: vec![SyscallName::SendInit], handle_body: Some(body::fallible_syscall(repetitions, res_offset, &[])), ..Default::default() }; @@ -593,7 +593,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::SendPush, SysCallName::SendInit], + imported_functions: vec![SyscallName::SendPush, SyscallName::SendInit], handle_body: Some(body::from_instructions(instructions)), ..Default::default() }; @@ -631,7 +631,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::SendPush, SysCallName::SendInit], + imported_functions: vec![SyscallName::SendPush, SyscallName::SendInit], handle_body: Some(body::from_instructions(instructions)), ..Default::default() }; @@ -666,9 +666,9 @@ where ]; let name = if wgas { commit_params.insert(3, InstrI64Const(100_000_000)); - SysCallName::SendCommitWGas + SyscallName::SendCommitWGas } else { - SysCallName::SendCommit + SyscallName::SendCommit }; instructions.extend(body::fallible_syscall_instr( @@ -680,7 +680,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![name, SysCallName::SendInit], + imported_functions: vec![name, SyscallName::SendInit], handle_body: Some(body::from_instructions(instructions)), ..Default::default() }; @@ -712,7 +712,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::ReservationSend], + imported_functions: vec![SyscallName::ReservationSend], data_segments: vec![DataSegment { offset: rid_pid_value_offset, value: rid_pid_values, @@ -779,7 +779,7 @@ where let module = ModuleDefinition { // `SMALL_MEM_SIZE + 2` in order to fit data segments and err handle offsets. memory: Some(ImportedMemory::new(SMALL_MEM_SIZE + 2)), - imported_functions: vec![SysCallName::ReservationSendCommit, SysCallName::SendInit], + imported_functions: vec![SyscallName::ReservationSendCommit, SyscallName::SendInit], data_segments: vec![DataSegment { offset: rid_pid_value_offset, value: rid_pid_values, @@ -818,9 +818,9 @@ where let name = match wgas { true => { params.insert(2, InstrI64Const(100_000_000)); - SysCallName::ReplyWGas + SyscallName::ReplyWGas } - false => SysCallName::Reply, + false => SyscallName::Reply, }; let module = ModuleDefinition { @@ -847,14 +847,14 @@ where InstrI32Const(value_offset), ]; - (SysCallName::ReplyCommitWGas, params) + (SyscallName::ReplyCommitWGas, params) } else { let params = vec![ // value offset InstrI32Const(value_offset), ]; - (SysCallName::ReplyCommit, params) + (SyscallName::ReplyCommit, params) }; let module = ModuleDefinition { @@ -875,7 +875,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::ReplyPush], + imported_functions: vec![SyscallName::ReplyPush], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -900,7 +900,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::ReplyPush], + imported_functions: vec![SyscallName::ReplyPush], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -942,7 +942,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::ReservationReply], + imported_functions: vec![SyscallName::ReservationReply], data_segments: vec![DataSegment { offset: rid_value_offset, value: rid_values, @@ -983,7 +983,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::ReservationReplyCommit], + imported_functions: vec![SyscallName::ReservationReplyCommit], data_segments: vec![DataSegment { offset: rid_value_offset, value: rid_values, @@ -1011,7 +1011,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::ReservationReply], + imported_functions: vec![SyscallName::ReservationReply], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -1036,7 +1036,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::ReplyTo], + imported_functions: vec![SyscallName::ReplyTo], reply_body: Some(body::fallible_syscall(repetitions, res_offset, &[])), ..Default::default() }; @@ -1074,7 +1074,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::SignalCode], + imported_functions: vec![SyscallName::SignalCode], signal_body: Some(body::fallible_syscall(repetitions, res_offset, &[])), ..Default::default() }; @@ -1088,7 +1088,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::SignalFrom], + imported_functions: vec![SyscallName::SignalFrom], signal_body: Some(body::fallible_syscall(repetitions, res_offset, &[])), ..Default::default() }; @@ -1121,9 +1121,9 @@ where let name = match wgas { true => { params.insert(2, InstrI64Const(100_000_000)); - SysCallName::ReplyInputWGas + SyscallName::ReplyInputWGas } - false => SysCallName::ReplyInput, + false => SyscallName::ReplyInput, }; let module = ModuleDefinition { @@ -1154,7 +1154,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::ReplyPushInput], + imported_functions: vec![SyscallName::ReplyPushInput], handle_body: Some(body::fallible_syscall( repetitions, res_offset, @@ -1199,9 +1199,9 @@ where let name = match wgas { true => { params.insert(3, InstrI64Const(100_000_000)); - SysCallName::SendInputWGas + SyscallName::SendInputWGas } - false => SysCallName::SendInput, + false => SyscallName::SendInput, }; let module = ModuleDefinition { @@ -1247,7 +1247,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::SendPushInput, SysCallName::SendInit], + imported_functions: vec![SyscallName::SendPushInput, SyscallName::SendInit], handle_body: Some(body::from_instructions(instructions)), ..Default::default() }; @@ -1261,7 +1261,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::ReplyCode], + imported_functions: vec![SyscallName::ReplyCode], reply_body: Some(body::fallible_syscall(repetitions, res_offset, &[])), ..Default::default() }; @@ -1300,7 +1300,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::Debug], + imported_functions: vec![SyscallName::Debug], handle_body: Some(body::syscall( repetitions, &[ @@ -1323,7 +1323,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::Debug], + imported_functions: vec![SyscallName::Debug], handle_body: Some(body::syscall( repetitions, &[ @@ -1340,7 +1340,7 @@ where } pub fn termination_bench( - name: SysCallName, + name: SyscallName, param: Option, r: u32, ) -> Result, &'static str> { @@ -1378,7 +1378,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::Wake], + imported_functions: vec![SyscallName::Wake], data_segments: vec![DataSegment { offset: message_id_offset, value: message_ids, @@ -1444,9 +1444,9 @@ where let name = match wgas { true => { params.insert(5, InstrI64Const(100_000_000)); - SysCallName::CreateProgramWGas + SyscallName::CreateProgramWGas } - false => SysCallName::CreateProgram, + false => SyscallName::CreateProgram, }; let module = ModuleDefinition { @@ -1469,7 +1469,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::new(SMALL_MEM_SIZE)), - imported_functions: vec![SysCallName::PayProgramRent], + imported_functions: vec![SyscallName::PayProgramRent], handle_body: Some(body::fallible_syscall( r, res_offset, @@ -1542,7 +1542,7 @@ where pub fn lazy_pages_host_func_read(wasm_pages: WasmPage) -> Result, &'static str> { let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::Debug], + imported_functions: vec![SyscallName::Debug], handle_body: Some(body::from_instructions(vec![ // payload offset Instruction::I32Const(0), @@ -1560,7 +1560,7 @@ where pub fn lazy_pages_host_func_write(wasm_pages: WasmPage) -> Result, &'static str> { let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::Read], + imported_functions: vec![SyscallName::Read], handle_body: Some(body::from_instructions(vec![ // at Instruction::I32Const(0), @@ -1605,7 +1605,7 @@ where let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::Read], + imported_functions: vec![SyscallName::Read], handle_body: Some(body::from_instructions(instrs)), stack_end: Some(0.into()), ..Default::default() diff --git a/pallets/gear/src/benchmarking/tests/lazy_pages.rs b/pallets/gear/src/benchmarking/tests/lazy_pages.rs index 631a7e70adb..be56fe5ad50 100644 --- a/pallets/gear/src/benchmarking/tests/lazy_pages.rs +++ b/pallets/gear/src/benchmarking/tests/lazy_pages.rs @@ -226,7 +226,7 @@ where }) } else if prob_number >= load_prob + store_prob { // Generate syscall - // We use syscall random here, because it has read and write access, + // We use syscall gr_random here, because it has read and write access, // and cannot cause errors because of input params let subject_size = gsys::Hash::max_encoded_len() as u32; let bn_random_size = core::mem::size_of::() as u32; @@ -263,7 +263,7 @@ where // Upload program with code let module = ModuleDefinition { memory: Some(ImportedMemory::max::()), - imported_functions: vec![SysCallName::Random], + imported_functions: vec![SyscallName::Random], handle_body: Some(body::from_instructions(instrs)), stack_end: Some(0.into()), ..Default::default() diff --git a/pallets/gear/src/benchmarking/tests/mod.rs b/pallets/gear/src/benchmarking/tests/mod.rs index 52ddf0870f0..a13b7ffb1aa 100644 --- a/pallets/gear/src/benchmarking/tests/mod.rs +++ b/pallets/gear/src/benchmarking/tests/mod.rs @@ -28,7 +28,6 @@ pub mod syscalls_integrity; mod utils; use crate::{ - alloc::string::ToString, benchmarking::{ code::{body, WasmModule}, utils as common_utils, @@ -36,7 +35,6 @@ use crate::{ HandleKind, }; use common::benchmarking; -use gear_core_backend::error::TrapExplanation; use gear_wasm_instrument::parity_wasm::elements::Instruction; pub fn check_stack_overflow() @@ -44,19 +42,11 @@ where T: Config, T::AccountId: Origin, { - let instrs = vec![ - Instruction::I64Const(10), - Instruction::GetGlobal(0), - Instruction::I64Add, - Instruction::SetGlobal(0), - Instruction::Call(0), - ]; + let instrs = vec![Instruction::Call(0)]; let module: WasmModule = ModuleDefinition { - memory: Some(ImportedMemory::max::()), + memory: Some(ImportedMemory::new(0)), init_body: Some(body::from_instructions(instrs)), - stack_end: Some(0.into()), - num_globals: 1, ..Default::default() } .into(); @@ -70,18 +60,24 @@ where ) .unwrap(); - core_processor::process::(&exec.block_config, exec.context, exec.random_data) - .unwrap() - .into_iter() - .find_map(|note| match note { - JournalNote::MessageDispatched { outcome, .. } => Some(outcome), - _ => None, - }) - .map(|outcome| match outcome { - DispatchOutcome::InitFailure { reason, .. } => { - assert_eq!(reason, TrapExplanation::Unknown.to_string()); - } - _ => panic!("Unexpected dispatch outcome: {:?}", outcome), - }) - .unwrap(); + let dispatch = + core_processor::process::(&exec.block_config, exec.context, exec.random_data) + .unwrap() + .into_iter() + .find_map(|note| match note { + JournalNote::SendDispatch { dispatch, .. } => Some(dispatch), + _ => None, + }) + .unwrap(); + + let code = dispatch + .reply_details() + .expect("reply details") + .to_reply_code(); + assert_eq!( + code, + ReplyCode::Error(ErrorReplyReason::Execution( + SimpleExecutionError::UnreachableInstruction + )) + ); } diff --git a/pallets/gear/src/benchmarking/tests/syscalls_integrity.rs b/pallets/gear/src/benchmarking/tests/syscalls_integrity.rs index 683a3ceed03..3a6938a92bc 100644 --- a/pallets/gear/src/benchmarking/tests/syscalls_integrity.rs +++ b/pallets/gear/src/benchmarking/tests/syscalls_integrity.rs @@ -16,14 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Testing integration level of sys-calls +//! Testing integration level of syscalls //! //! Integration level is the level between the user (`gcore`/`gstd`) and `core-backend`. //! Tests here does not check complex business logic, but only the fact that all the //! requested data is received properly, i.e., pointers receive expected types, no export func //! signature map errors. //! -//! `gr_read` is tested in the `test_syscall` program by calling `msg::load` to decode each sys-call type. +//! `gr_read` is tested in the `test_syscall` program by calling `msg::load` to decode each syscall type. //! `gr_exit` and `gr_wait*` call are not intended to be tested with the integration level tests, but only //! with business logic tests in the separate module. @@ -34,7 +34,7 @@ use common::event::DispatchStatus; use frame_support::traits::Randomness; use gear_core::ids::{CodeId, ReservationId}; use gear_core_errors::{ReplyCode, SuccessReplyReason}; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; use pallet_timestamp::Pallet as TimestampPallet; use parity_scale_codec::Decode; use sp_runtime::SaturatedConversion; @@ -132,64 +132,64 @@ where T: Config, T::AccountId: Origin, { - SysCallName::all().for_each(|sys_call| { - log::info!("run test for {sys_call:?}"); - match sys_call { - SysCallName::Send => check_send::(None), - SysCallName::SendWGas => check_send::(Some(25_000_000_000)), - SysCallName::SendCommit => check_send_raw::(None), - SysCallName::SendCommitWGas => check_send_raw::(Some(25_000_000_000)), - SysCallName::SendInit | SysCallName:: SendPush => {/* skipped, due to test being run in SendCommit* variants */}, - SysCallName::SendInput => check_send_input::(None), - SysCallName::SendPushInput => check_send_push_input::(), - SysCallName::SendInputWGas => check_send_input::(Some(25_000_000_000)), - SysCallName::Reply => check_reply::(None), - SysCallName::ReplyWGas => check_reply::(Some(25_000_000_000)), - SysCallName::ReplyCommit => check_reply_raw::(None), - SysCallName::ReplyCommitWGas => check_reply_raw::(Some(25_000_000_000)), - SysCallName::ReplyTo => check_reply_details::(), - SysCallName::SignalFrom => check_signal_details::(), - SysCallName::ReplyPush => {/* skipped, due to test being run in SendCommit* variants */}, - SysCallName::ReplyInput => check_reply_input::(None), - SysCallName::ReplyPushInput => check_reply_push_input::(), - SysCallName::ReplyInputWGas => check_reply_input::(Some(25_000_000_000)), - SysCallName::CreateProgram => check_create_program::(None), - SysCallName::CreateProgramWGas => check_create_program::(Some(25_000_000_000)), - SysCallName::ReplyDeposit => check_gr_reply_deposit::(), - SysCallName::Read => {/* checked in all the calls internally */}, - SysCallName::Size => check_gr_size::(), - SysCallName::ReplyCode => {/* checked in reply_to */}, - SysCallName::SignalCode => {/* checked in signal_from */}, - SysCallName::MessageId => check_gr_message_id::(), - SysCallName::ProgramId => check_gr_program_id::(), - SysCallName::Source => check_gr_source::(), - SysCallName::Value => check_gr_value::(), - SysCallName::EnvVars => check_gr_env_vars::(), - SysCallName::BlockHeight => check_gr_block_height::(), - SysCallName::BlockTimestamp => check_gr_block_timestamp::(), - SysCallName::GasAvailable => check_gr_gas_available::(), - SysCallName::ValueAvailable => check_gr_value_available::(), - SysCallName::Exit - | SysCallName::Leave - | SysCallName::Wait - | SysCallName::WaitFor - | SysCallName::WaitUpTo - | SysCallName::Wake - | SysCallName::Debug - | SysCallName::Panic - | SysCallName::OomPanic => {/* tests here aren't required, read module docs for more info */}, - SysCallName::Alloc => check_mem::(false), - SysCallName::Free => check_mem::(true), - SysCallName::OutOfGas => { /*no need for tests */} - SysCallName::Random => check_gr_random::(), - SysCallName::ReserveGas => check_gr_reserve_gas::(), - SysCallName::UnreserveGas => check_gr_unreserve_gas::(), - SysCallName::ReservationSend => check_gr_reservation_send::(), - SysCallName::ReservationSendCommit => check_gr_reservation_send_commit::(), - SysCallName::ReservationReply => check_gr_reservation_reply::(), - SysCallName::ReservationReplyCommit => check_gr_reservation_reply_commit::(), - SysCallName::SystemReserveGas => check_gr_system_reserve_gas::(), - SysCallName::PayProgramRent => check_gr_pay_program_rent::(), + SyscallName::all().for_each(|syscall| { + log::info!("run test for {syscall:?}"); + match syscall { + SyscallName::Send => check_send::(None), + SyscallName::SendWGas => check_send::(Some(25_000_000_000)), + SyscallName::SendCommit => check_send_raw::(None), + SyscallName::SendCommitWGas => check_send_raw::(Some(25_000_000_000)), + SyscallName::SendInit | SyscallName:: SendPush => {/* skipped, due to test being run in SendCommit* variants */}, + SyscallName::SendInput => check_send_input::(None), + SyscallName::SendPushInput => check_send_push_input::(), + SyscallName::SendInputWGas => check_send_input::(Some(25_000_000_000)), + SyscallName::Reply => check_reply::(None), + SyscallName::ReplyWGas => check_reply::(Some(25_000_000_000)), + SyscallName::ReplyCommit => check_reply_raw::(None), + SyscallName::ReplyCommitWGas => check_reply_raw::(Some(25_000_000_000)), + SyscallName::ReplyTo => check_reply_details::(), + SyscallName::SignalFrom => check_signal_details::(), + SyscallName::ReplyPush => {/* skipped, due to test being run in SendCommit* variants */}, + SyscallName::ReplyInput => check_reply_input::(None), + SyscallName::ReplyPushInput => check_reply_push_input::(), + SyscallName::ReplyInputWGas => check_reply_input::(Some(25_000_000_000)), + SyscallName::CreateProgram => check_create_program::(None), + SyscallName::CreateProgramWGas => check_create_program::(Some(25_000_000_000)), + SyscallName::ReplyDeposit => check_gr_reply_deposit::(), + SyscallName::Read => {/* checked in all the calls internally */}, + SyscallName::Size => check_gr_size::(), + SyscallName::ReplyCode => {/* checked in reply_to */}, + SyscallName::SignalCode => {/* checked in signal_from */}, + SyscallName::MessageId => check_gr_message_id::(), + SyscallName::ProgramId => check_gr_program_id::(), + SyscallName::Source => check_gr_source::(), + SyscallName::Value => check_gr_value::(), + SyscallName::EnvVars => check_gr_env_vars::(), + SyscallName::BlockHeight => check_gr_block_height::(), + SyscallName::BlockTimestamp => check_gr_block_timestamp::(), + SyscallName::GasAvailable => check_gr_gas_available::(), + SyscallName::ValueAvailable => check_gr_value_available::(), + SyscallName::Exit + | SyscallName::Leave + | SyscallName::Wait + | SyscallName::WaitFor + | SyscallName::WaitUpTo + | SyscallName::Wake + | SyscallName::Debug + | SyscallName::Panic + | SyscallName::OomPanic => {/* tests here aren't required, read module docs for more info */}, + SyscallName::Alloc => check_mem::(false), + SyscallName::Free => check_mem::(true), + SyscallName::OutOfGas => { /*no need for tests */} + SyscallName::Random => check_gr_random::(), + SyscallName::ReserveGas => check_gr_reserve_gas::(), + SyscallName::UnreserveGas => check_gr_unreserve_gas::(), + SyscallName::ReservationSend => check_gr_reservation_send::(), + SyscallName::ReservationSendCommit => check_gr_reservation_send_commit::(), + SyscallName::ReservationReply => check_gr_reservation_reply::(), + SyscallName::ReservationReplyCommit => check_gr_reservation_reply_commit::(), + SyscallName::SystemReserveGas => check_gr_system_reserve_gas::(), + SyscallName::PayProgramRent => check_gr_pay_program_rent::(), } }); } @@ -789,7 +789,7 @@ where let reply_to = MailboxOf::::iter_key(default_sender) .last() .map(|(m, _)| m) - .expect("no mail found after invoking sys-call test program"); + .expect("no mail found after invoking syscall test program"); assert_eq!(reply_to.id(), expected_mid, "mailbox check failed"); @@ -828,7 +828,7 @@ where let reply_to = MailboxOf::::iter_key(default_sender) .last() .map(|(m, _)| m) - .expect("no mail found after invoking sys-call test program"); + .expect("no mail found after invoking syscall test program"); assert_eq!(reply_to.id(), expected_mid, "mailbox check failed"); @@ -991,7 +991,7 @@ where Id: Clone + Origin, // Post check P: FnOnce(), - // Get sys call and post check + // Get syscall and post check S: FnOnce(ProgramId, CodeId) -> (TestCall, Option

), { #[cfg(feature = "std")] @@ -1036,7 +1036,7 @@ where 0u128.unique_saturated_into(), false, ) - .expect("sys-call check program deploy failed"); + .expect("syscall check program deploy failed"); utils::run_to_next_block::(None); @@ -1281,7 +1281,7 @@ where ModuleDefinition { memory: Some(ImportedMemory::new(1)), - imported_functions: vec![SysCallName::Alloc, SysCallName::Free], + imported_functions: vec![SyscallName::Alloc, SyscallName::Free], init_body: Some(FuncBody::new( vec![], Instructions::new(vec![ diff --git a/pallets/gear/src/internal.rs b/pallets/gear/src/internal.rs index 5fbb0831d39..7a25243946f 100644 --- a/pallets/gear/src/internal.rs +++ b/pallets/gear/src/internal.rs @@ -535,8 +535,7 @@ where let hold_builder = HoldBoundBuilder::::new(StorageType::DispatchStash); // Calculating correct gas amount for delay. - let bn_delay = delay.saturated_into::>(); - let delay_hold = hold_builder.clone().duration(bn_delay); + let delay_hold = hold_builder.duration(delay.saturated_into()); let gas_for_delay = delay_hold.lock_amount(); let interval_finish = if to_user { @@ -557,7 +556,11 @@ where // If available gas is greater then threshold, // than threshold can be used. - (gas_limit >= threshold).then_some(threshold) + // + // Here we subtract gas for delay from gas limit to prevent + // case when gasless message steal threshold from gas for + // delay payment and delay payment becomes insufficient. + (gas_limit.saturating_sub(gas_for_delay) >= threshold).then_some(threshold) }) .unwrap_or_default(); @@ -591,22 +594,18 @@ where Self::remove_gas_reservation_with_task(dispatch.source(), reservation_id); } - // Calculating correct hold bound to lock gas. - let maximal_hold = hold_builder.maximum_for_message(dispatch.id()); - let hold = delay_hold.min(maximal_hold); - // Locking funds for holding. - let lock_id = hold.lock_id().unwrap_or_else(|| { + let lock_id = delay_hold.lock_id().unwrap_or_else(|| { unreachable!("DispatchStash storage is guaranteed to have an associated lock id") }); - GasHandlerOf::::lock(dispatch.id(), lock_id, hold.lock_amount()) + GasHandlerOf::::lock(dispatch.id(), lock_id, delay_hold.lock_amount()) .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); - if hold.expected_duration().is_zero() { + if delay_hold.expected_duration().is_zero() { unreachable!("Hold duration cannot be zero"); } - hold.expected() + delay_hold.expected() } else { match (dispatch.gas_limit(), reservation) { (Some(gas_limit), None) => Self::split_with_value( @@ -629,25 +628,18 @@ where } } - // `HoldBound` builder. - let hold_builder = HoldBoundBuilder::::new(StorageType::DispatchStash); - - // Calculating correct hold bound to lock gas. - let maximal_hold = hold_builder.maximum_for_message(dispatch.id()); - let hold = delay_hold.min(maximal_hold); - // Locking funds for holding. - let lock_id = hold.lock_id().unwrap_or_else(|| { + let lock_id = delay_hold.lock_id().unwrap_or_else(|| { unreachable!("DispatchStash storage is guaranteed to have an associated lock id") }); - GasHandlerOf::::lock(dispatch.id(), lock_id, hold.lock_amount()) + GasHandlerOf::::lock(dispatch.id(), lock_id, delay_hold.lock_amount()) .unwrap_or_else(|e| unreachable!("GasTree corrupted! {:?}", e)); - if hold.expected_duration().is_zero() { + if delay_hold.expected_duration().is_zero() { unreachable!("Hold duration cannot be zero"); } - hold.expected() + delay_hold.expected() }; if !dispatch.value().is_zero() { diff --git a/pallets/gear/src/lib.rs b/pallets/gear/src/lib.rs index 0a173351c35..28ede177994 100644 --- a/pallets/gear/src/lib.rs +++ b/pallets/gear/src/lib.rs @@ -18,6 +18,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "1024")] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] extern crate alloc; diff --git a/pallets/gear/src/runtime_api.rs b/pallets/gear/src/runtime_api.rs index 6accfc8c9a4..8885c6d44ff 100644 --- a/pallets/gear/src/runtime_api.rs +++ b/pallets/gear/src/runtime_api.rs @@ -22,7 +22,7 @@ use common::ActiveProgram; use core::convert::TryFrom; use core_processor::common::PrechargedDispatch; use gear_core::{code::TryNewCodeConfig, pages::WasmPage, program::MemoryInfix}; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; // Multiplier 6 was experimentally found as median value for performance, // security and abilities for calculations on-chain. @@ -119,7 +119,7 @@ where })?; let mut block_config = Self::block_config(); - block_config.forbidden_funcs = [SysCallName::GasAvailable].into(); + block_config.forbidden_funcs = [SyscallName::GasAvailable].into(); let mut min_limit = 0; let mut reserved = 0; diff --git a/pallets/gear/src/schedule.rs b/pallets/gear/src/schedule.rs index d04168769a5..5e86704f868 100644 --- a/pallets/gear/src/schedule.rs +++ b/pallets/gear/src/schedule.rs @@ -51,6 +51,13 @@ pub const API_BENCHMARK_BATCH_SIZE: u32 = 80; /// as for `API_BENCHMARK_BATCH_SIZE`. pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 500; +// Constant for `stack_height` is calculated via `calc-stack-height` utility to be small enough +// to avoid stack overflow in wasmer and wasmi executors. +// To avoid potential stack overflow problems we have a panic in sandbox in case, +// execution is ended with stack overflow error. So, process queue execution will be +// stopped and we will be able to investigate the problem and decrease this constant if needed. +pub const STACK_HEIGHT_LIMIT: u32 = 36_743; + /// Definition of the cost schedule and other parameterization for the wasm vm. /// /// Its [`Default`] implementation is the designated way to initialize this type. It uses @@ -732,16 +739,7 @@ impl Default for Schedule { impl Default for Limits { fn default() -> Self { Self { - // Constant for `stack_height` is chosen to be small enough to avoid stack overflow in - // wasmer and wasmi executors. Currently it's just heuristic value. - // Unfortunately it's very hard to calculate this value precisely, - // because of difference of how stack height is calculated in injection and - // how wasmer and wasmi actually uses stack. - // To avoid potential stack overflow problems we have a panic in sandbox in case, - // execution is ended with stack overflow error. So, process queue execution will be - // stopped and we will be able to investigate the problem and decrease this constant if needed. - // TODO #3435. Disabled stack height is a temp solution. - stack_height: cfg!(not(feature = "fuzz")).then_some(20_000), + stack_height: Some(STACK_HEIGHT_LIMIT), globals: 256, locals: 1024, parameters: 128, diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 5ba34e0e344..d16d7e827c6 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -45,10 +45,10 @@ use crate::{ }, pallet, runtime_api::RUNTIME_API_BLOCK_LIMITS_COUNT, - BlockGasLimitOf, Config, CostsPerBlockOf, CurrencyOf, DbWeightOf, Error, Event, GasAllowanceOf, - GasBalanceOf, GasHandlerOf, GasInfo, GearBank, MailboxOf, ProgramStorageOf, QueueOf, - RentCostPerBlockOf, RentFreePeriodOf, ResumeMinimalPeriodOf, ResumeSessionDurationOf, Schedule, - TaskPoolOf, WaitlistOf, + BlockGasLimitOf, Config, CostsPerBlockOf, CurrencyOf, DbWeightOf, DispatchStashOf, Error, + Event, GasAllowanceOf, GasBalanceOf, GasHandlerOf, GasInfo, GearBank, MailboxOf, + ProgramStorageOf, QueueOf, RentCostPerBlockOf, RentFreePeriodOf, ResumeMinimalPeriodOf, + ResumeSessionDurationOf, Schedule, TaskPoolOf, WaitlistOf, }; use common::{ event::*, scheduler::*, storage::*, ActiveProgram, CodeStorage, GasTree, LockId, LockableTree, @@ -83,6 +83,376 @@ use utils::*; type Gas = <::GasProvider as common::GasProvider>::GasTree; +#[test] +fn delayed_send_from_reservation_not_for_mailbox() { + use demo_delayed_reservation_sender::{ReservationSendingShowcase, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount: ::MailboxThreshold::get(), + reservation_delay: 1_000, + sending_delay: 1, + } + .encode(), + BlockGasLimitOf::::get(), + 0, + false, + )); + + let mid = utils::get_last_message_id(); + + run_to_next_block(None); + assert_succeed(mid); + + let outgoing = MessageId::generate_outgoing(mid, 0); + + assert!(DispatchStashOf::::contains_key(&outgoing)); + + run_to_next_block(None); + + assert!(!DispatchStashOf::::contains_key(&outgoing)); + assert!(!MailboxOf::::contains(&USER_1, &outgoing)); + + let message = maybe_any_last_message().expect("Should be"); + assert_eq!(message.id(), outgoing); + assert_eq!(message.destination(), USER_1.into()); + }); +} + +#[test] +fn cascading_delayed_gasless_send_work() { + use demo_delayed_sender::{DELAY, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + 0u32.to_le_bytes().to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + let GasInfo { min_limit, .. } = Gear::calculate_gas_info( + USER_1.into_origin(), + HandleKind::Handle(pid), + EMPTY_PAYLOAD.to_vec(), + 0, + true, + true, + ) + .expect("calculate_gas_info failed"); + + // Case when one of two goes into mailbox. + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + EMPTY_PAYLOAD.to_vec(), + min_limit - ::MailboxThreshold::get(), + 0, + false, + )); + + let mid = get_last_message_id(); + + let first_outgoing = MessageId::generate_outgoing(mid, 0); + let second_outgoing = MessageId::generate_outgoing(mid, 1); + + run_to_next_block(None); + + assert_succeed(mid); + + run_for_blocks(DELAY, None); + assert!(MailboxOf::::contains(&USER_1, &first_outgoing)); + assert!(!MailboxOf::::contains(&USER_1, &second_outgoing)); + + // Similar case when two of two goes into mailbox. + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + EMPTY_PAYLOAD.to_vec(), + min_limit, + 0, + false, + )); + + let mid = get_last_message_id(); + + let first_outgoing = MessageId::generate_outgoing(mid, 0); + let second_outgoing = MessageId::generate_outgoing(mid, 1); + + run_to_next_block(None); + + assert_succeed(mid); + + run_for_blocks(DELAY, None); + assert!(MailboxOf::::contains(&USER_1, &first_outgoing)); + assert!(MailboxOf::::contains(&USER_1, &second_outgoing)); + + // Similar case when none of them goes into mailbox + // (impossible because delayed sent after gasless). + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + EMPTY_PAYLOAD.to_vec(), + min_limit - 2 * ::MailboxThreshold::get(), + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_next_block(None); + + assert_failed( + mid, + ActorExecutionErrorReplyReason::Trap(TrapExplanation::GasLimitExceeded), + ); + }); +} + +#[test] +fn calculate_gas_delayed_reservations_sending() { + use demo_delayed_reservation_sender::{ReservationSendingShowcase, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + // I. In-place case + assert!(Gear::calculate_gas_info( + USER_1.into_origin(), + HandleKind::Handle(pid), + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + sending_delay: 10, + } + .encode(), + 0, + true, + true, + ) + .is_ok()); + + // II. After-wait case (never failed before, added for test coverage). + assert!(Gear::calculate_gas_info( + USER_1.into_origin(), + HandleKind::Handle(pid), + ReservationSendingShowcase::ToSourceAfterWait { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + wait_for: 3, + sending_delay: 10, + } + .encode(), + 0, + true, + true, + ) + .is_ok()); + }); +} + +#[test] +fn delayed_reservations_sending_validation() { + use demo_delayed_reservation_sender::{ + ReservationSendingShowcase, SENDING_EXPECT, WASM_BINARY, + }; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + // I. In place sending can't appear if not enough gas limit in gas reservation. + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + sending_delay: 1_000 * ::MailboxThreshold::get() as u32 + + CostsPerBlockOf::::reserve_for() + / CostsPerBlockOf::::dispatch_stash() as u32, + } + .encode(), + BlockGasLimitOf::::get(), + 0, + false, + )); + + let mid = utils::get_last_message_id(); + + run_to_next_block(None); + + let error_text = if cfg!(any(feature = "debug", debug_assertions)) { + format!( + "{SENDING_EXPECT}: {:?}", + GstdError::Core( + ExtError::Message(MessageError::InsufficientGasForDelayedSending).into() + ) + ) + } else { + String::from("no info") + }; + + assert_failed( + mid, + ActorExecutionErrorReplyReason::Trap(TrapExplanation::Panic(error_text.into())), + ); + + // II. After-wait sending can't appear if not enough gas limit in gas reservation. + let wait_for = 5; + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + ReservationSendingShowcase::ToSourceAfterWait { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1_000, + wait_for, + sending_delay: 1_000 * ::MailboxThreshold::get() as u32 + + CostsPerBlockOf::::reserve_for() + / CostsPerBlockOf::::dispatch_stash() as u32, + } + .encode(), + BlockGasLimitOf::::get(), + 0, + false, + )); + + let mid = utils::get_last_message_id(); + + run_for_blocks(wait_for + 1, None); + + let error_text = if cfg!(any(feature = "debug", debug_assertions)) { + format!( + "{SENDING_EXPECT}: {:?}", + GstdError::Core( + ExtError::Message(MessageError::InsufficientGasForDelayedSending).into() + ) + ) + } else { + String::from("no info") + }; + + assert_failed( + mid, + ActorExecutionErrorReplyReason::Trap(TrapExplanation::Panic(error_text.into())), + ); + }); +} + +#[test] +fn delayed_reservations_to_mailbox() { + use demo_delayed_reservation_sender::{ReservationSendingShowcase, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000u64, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + assert!(Gear::is_initialized(pid)); + + let sending_delay = 10; + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + ReservationSendingShowcase::ToSourceInPlace { + reservation_amount: 10 * ::MailboxThreshold::get(), + reservation_delay: 1, + sending_delay, + } + .encode(), + BlockGasLimitOf::::get(), + 0, + false, + )); + + let mid = utils::get_last_message_id(); + + run_to_next_block(None); + + assert_succeed(mid); + + assert!(MailboxOf::::is_empty(&USER_1)); + + run_for_blocks(sending_delay, None); + + assert!(!MailboxOf::::is_empty(&USER_1)); + + let mailed_msg = utils::get_last_mail(USER_1); + let expiration = utils::get_mailbox_expiration(mailed_msg.id()); + + run_to_block(expiration, None); + + assert!(MailboxOf::::is_empty(&USER_1)); + }); +} + #[test] fn default_wait_lock_timeout() { use demo_async_tester::{Kind, WASM_BINARY}; @@ -167,7 +537,12 @@ fn value_counter_set_correctly_for_interruptions() { .send_value(Arg::new([0u8; 32]), Arg::new(vec![]), "value_store") .wait_for(1); - let scheme = Scheme::predefined(Calls::builder().noop(), handle, Calls::builder().noop()); + let scheme = Scheme::predefined( + Calls::builder().noop(), + handle, + Calls::builder().noop(), + Calls::builder().noop(), + ); init_logger(); new_test_ext().execute_with(|| { @@ -1602,115 +1977,10 @@ fn delayed_send_program_message_payment() { )); let program_address = utils::get_last_program_id(); - - // Upload program that sends message to another program. - let (_init_mid, proxy) = - init_constructor(demo_proxy_with_gas::scheme(program_address.into(), delay)); - assert!(Gear::is_initialized(program_address)); - - assert_ok!(Gear::send_message( - RuntimeOrigin::signed(USER_1), - proxy, - 0u64.encode(), - DEFAULT_GAS_LIMIT * 100, - 0, - false, - )); - let proxy_msg_id = utils::get_last_message_id(); - - // Run blocks to make message get into dispatch stash. - run_to_block(3, None); - - let delay_holding_fee = gas_price( - CostsPerBlockOf::::dispatch_stash().saturating_mul( - delay - .saturating_add(CostsPerBlockOf::::reserve_for()) - .saturated_into(), - ), - ); - - let reserve_for_fee = gas_price( - CostsPerBlockOf::::dispatch_stash() - .saturating_mul(CostsPerBlockOf::::reserve_for().saturated_into()), - ); - - // Gas should be reserved while message is being held in storage. - assert_eq!(GearBank::::account_total(&USER_1), delay_holding_fee); - let total_balance = - Balances::free_balance(USER_1) + GearBank::::account_total(&USER_1); - - // Run blocks to release message. - run_to_block(delay + 2, None); - - let delayed_id = MessageId::generate_outgoing(proxy_msg_id, 0); - - // Check that delayed task was created. - assert!(TaskPoolOf::::contains( - &(delay + 3), - &ScheduledTask::SendDispatch(delayed_id) - )); - - // Block where message processed. - run_to_next_block(None); - - // Check that last event is MessagesDispatched. - assert_last_dequeued(2); - - // Check that gas was charged correctly. - assert_eq!(GearBank::::account_total(&USER_1), 0); - assert_eq!( - total_balance - delay_holding_fee + reserve_for_fee, - Balances::free_balance(USER_1) - ); - } - - init_logger(); - - for i in 2..4 { - new_test_ext().execute_with(|| scenario(i)); - } -} - -#[test] -fn delayed_send_program_message_with_reservation() { - use demo_proxy_reservation_with_gas::{InputArgs, WASM_BINARY as PROXY_WGAS_WASM_BINARY}; - - // Testing that correct gas amount will be reserved and paid for holding. - fn scenario(delay: BlockNumber) { - // Upload empty program that receive the message. - assert_ok!(Gear::upload_program( - RuntimeOrigin::signed(USER_1), - ProgramCodeKind::OutgoingWithValueInHandle.to_bytes(), - DEFAULT_SALT.to_vec(), - EMPTY_PAYLOAD.to_vec(), - DEFAULT_GAS_LIMIT * 100, - 0, - false, - )); - - let program_address = utils::get_last_program_id(); - let reservation_amount = 6_000_000_000u64; - - // Upload program that sends message to another program. - assert_ok!(Gear::upload_program( - RuntimeOrigin::signed(USER_1), - PROXY_WGAS_WASM_BINARY.to_vec(), - DEFAULT_SALT.to_vec(), - InputArgs { - destination: <[u8; 32]>::from(program_address).into(), - delay: delay.saturated_into(), - reservation_amount, - } - .encode(), - DEFAULT_GAS_LIMIT * 100, - 0, - false, - )); - - let proxy = utils::get_last_program_id(); - - run_to_next_block(None); - assert!(Gear::is_initialized(proxy)); + + // Upload program that sends message to another program. + let (_init_mid, proxy) = + init_constructor(demo_proxy_with_gas::scheme(program_address.into(), delay)); assert!(Gear::is_initialized(program_address)); assert_ok!(Gear::send_message( @@ -1734,47 +2004,39 @@ fn delayed_send_program_message_with_reservation() { ), ); - let reservation_holding_fee = gas_price( - 80u64 - .saturating_add(CostsPerBlockOf::::reserve_for().unique_saturated_into()) - .saturating_mul(CostsPerBlockOf::::reservation()), + let reserve_for_fee = gas_price( + CostsPerBlockOf::::dispatch_stash() + .saturating_mul(CostsPerBlockOf::::reserve_for().saturated_into()), ); - let delayed_id = MessageId::generate_outgoing(proxy_msg_id, 0); - - // Check that delayed task was created - assert!(TaskPoolOf::::contains( - &(delay + 3), - &ScheduledTask::SendDispatch(delayed_id) - )); - - // Check that correct amount locked for dispatch stash - let gas_locked_in_gas_node = - gas_price(Gas::get_lock(delayed_id, LockId::DispatchStash).unwrap()); - assert_eq!(gas_locked_in_gas_node, delay_holding_fee); - // Gas should be reserved while message is being held in storage. - assert_eq!( - GearBank::::account_total(&USER_1), - gas_price(reservation_amount) + reservation_holding_fee - ); + assert_eq!(GearBank::::account_total(&USER_1), delay_holding_fee); + let total_balance = + Balances::free_balance(USER_1) + GearBank::::account_total(&USER_1); // Run blocks to release message. run_to_block(delay + 2, None); - // Check that delayed task was created + let delayed_id = MessageId::generate_outgoing(proxy_msg_id, 0); + + // Check that delayed task was created. assert!(TaskPoolOf::::contains( &(delay + 3), &ScheduledTask::SendDispatch(delayed_id) )); - // Block where message processed + // Block where message processed. run_to_next_block(None); // Check that last event is MessagesDispatched. assert_last_dequeued(2); + // Check that gas was charged correctly. assert_eq!(GearBank::::account_total(&USER_1), 0); + assert_eq!( + total_balance - delay_holding_fee + reserve_for_fee, + Balances::free_balance(USER_1) + ); } init_logger(); @@ -1785,7 +2047,7 @@ fn delayed_send_program_message_with_reservation() { } #[test] -fn delayed_send_program_message_with_low_reservation() { +fn delayed_send_program_message_with_reservation() { use demo_proxy_reservation_with_gas::{InputArgs, WASM_BINARY as PROXY_WGAS_WASM_BINARY}; // Testing that correct gas amount will be reserved and paid for holding. @@ -1802,7 +2064,7 @@ fn delayed_send_program_message_with_low_reservation() { )); let program_address = utils::get_last_program_id(); - let reservation_amount = ::MailboxThreshold::get(); + let reservation_amount = 6_000_000_000u64; // Upload program that sends message to another program. assert_ok!(Gear::upload_program( @@ -1811,7 +2073,7 @@ fn delayed_send_program_message_with_low_reservation() { DEFAULT_SALT.to_vec(), InputArgs { destination: <[u8; 32]>::from(program_address).into(), - delay, + delay: delay.saturated_into(), reservation_amount, } .encode(), @@ -1902,11 +2164,11 @@ fn delayed_program_creation_no_code() { init_logger(); let wat = r#" - (module - (import "env" "memory" (memory 1)) + (module + (import "env" "memory" (memory 1)) (import "env" "gr_create_program_wgas" (func $create_program_wgas (param i32 i32 i32 i32 i32 i64 i32 i32))) - (export "init" (func $init)) - (func $init + (export "init" (func $init)) + (func $init i32.const 0 ;; zeroed cid_value ptr i32.const 0 ;; salt ptr i32.const 0 ;; salt len @@ -1925,7 +2187,7 @@ fn delayed_program_creation_no_code() { (else) ) ) - )"#; + )"#; new_test_ext().execute_with(|| { let code = ProgramCodeKind::Custom(wat).to_bytes(); @@ -2284,14 +2546,14 @@ fn read_state_using_wasm_errors() { use demo_new_meta::{MessageInitIn, WASM_BINARY}; let wat = r#" - (module - (export "loop" (func $loop)) + (module + (export "loop" (func $loop)) (export "empty" (func $empty)) (func $empty) (func $loop (loop) ) - )"#; + )"#; init_logger(); new_test_ext().execute_with(|| { @@ -3055,17 +3317,17 @@ fn unused_gas_released_back_works() { fn restrict_start_section() { // This test checks, that code with start section cannot be handled in process queue. let wat = r#" - (module - (import "env" "memory" (memory 1)) - (export "handle" (func $handle)) - (export "init" (func $init)) - (start $start) - (func $init) + (module + (import "env" "memory" (memory 1)) + (export "handle" (func $handle)) + (export "init" (func $init)) + (start $start) + (func $init) (func $handle) (func $start unreachable ) - )"#; + )"#; init_logger(); new_test_ext().execute_with(|| { @@ -3326,6 +3588,79 @@ fn memory_access_cases() { }); } +#[test] +fn gas_limit_exceeded_oob_case() { + let wat = r#"(module + (import "env" "memory" (memory 512)) + (import "env" "gr_send_init" (func $send_init (param i32))) + (import "env" "gr_send_push" (func $send_push (param i32 i32 i32 i32))) + (export "init" (func $init)) + (func $init + (local $addr i32) + (local $handle i32) + + ;; init message sending + i32.const 0x0 + call $send_init + + ;; load handle and set it to local + i32.const 0x0 + i32.load + local.set $handle + + ;; push message payload out of bounds + ;; each iteration we change gear page where error is returned + (loop + local.get $handle + i32.const 0x1000_0000 ;; out of bounds payload addr + i32.const 0x1 + local.get $addr + call $send_push + + local.get $addr + i32.const 0x4000 + i32.add + local.tee $addr + i32.const 0x0200_0000 + i32.ne + br_if 0 + ) + ) + )"#; + + init_logger(); + new_test_ext().execute_with(|| { + let gas_limit = 10_000_000_000; + let code = ProgramCodeKind::Custom(wat).to_bytes(); + let salt = DEFAULT_SALT.to_vec(); + Gear::upload_program( + RuntimeOrigin::signed(USER_1), + code, + salt, + EMPTY_PAYLOAD.to_vec(), + gas_limit, + 0, + false, + ) + .unwrap(); + + let message_id = get_last_message_id(); + + run_to_block(2, None); + assert_last_dequeued(1); + + // We have sent message with `gas_limit`, but it must not be enough, + // because one write access to memory costs 100_000_000 gas (storage write cost). + // Fallible syscall error is written in each iteration to new gear page, + // so to successfully finish execution must be at least 100_000_000 * 512 * 4 = 204_800_000_000 gas, + // which is bigger than provided `gas_limit`. + assert_failed( + message_id, + ActorExecutionErrorReplyReason::Trap(TrapExplanation::GasLimitExceeded), + ); + }); +} + #[test] fn lazy_pages() { use gear_core::pages::{GearPage, PageU32Size}; @@ -3336,12 +3671,12 @@ fn lazy_pages() { // and check that lazy-pages (see gear-lazy-pages) works correct: // For each page, which has been loaded from storage <=> page has been accessed. let wat = r#" - (module - (import "env" "memory" (memory 1)) + (module + (import "env" "memory" (memory 1)) (import "env" "alloc" (func $alloc (param i32) (result i32))) - (export "handle" (func $handle)) - (export "init" (func $init)) - (func $init + (export "handle" (func $handle)) + (export "init" (func $init)) + (func $init ;; allocate 9 pages in init, so mem will contain 10 pages i32.const 0x0 i32.const 0x9 @@ -3371,8 +3706,8 @@ fn lazy_pages() { i32.const 0x8fffc i64.const 0xffffffffffffffff i64.store - ) - )"#; + ) + )"#; init_logger(); new_test_ext().execute_with(|| { @@ -3565,11 +3900,11 @@ fn block_gas_limit_works() { // Same as `ProgramCodeKind::GreedyInit`, but greedy handle let wat2 = r#" - (module - (import "env" "memory" (memory 1)) - (export "handle" (func $handle)) - (export "init" (func $init)) - (func $init) + (module + (import "env" "memory" (memory 1)) + (export "handle" (func $handle)) + (export "init" (func $init)) + (func $init) (func $doWork (param $size i32) (local $counter i32) i32.const 0 @@ -3590,8 +3925,8 @@ fn block_gas_limit_works() { (func $handle i32.const 10 call $doWork - ) - )"#; + ) + )"#; init_logger(); @@ -3904,26 +4239,26 @@ fn program_lifecycle_works() { #[test] fn events_logging_works() { let wat_trap_in_handle = r#" - (module - (import "env" "memory" (memory 1)) - (export "handle" (func $handle)) - (export "init" (func $init)) - (func $handle - unreachable - ) - (func $init) - )"#; + (module + (import "env" "memory" (memory 1)) + (export "handle" (func $handle)) + (export "init" (func $init)) + (func $handle + unreachable + ) + (func $init) + )"#; let wat_trap_in_init = r#" - (module - (import "env" "memory" (memory 1)) - (export "handle" (func $handle)) - (export "init" (func $init)) - (func $handle) - (func $init + (module + (import "env" "memory" (memory 1)) + (export "handle" (func $handle)) + (export "init" (func $init)) + (func $handle) + (func $init unreachable ) - )"#; + )"#; init_logger(); new_test_ext().execute_with(|| { @@ -5937,7 +6272,12 @@ fn pause_terminated_exited_program() { let (_, terminated_pid) = submit_constructor_with_args( USER_1, DEFAULT_SALT, - Scheme::predefined(init, Default::default(), Default::default()), + Scheme::predefined( + init, + Default::default(), + Default::default(), + Default::default(), + ), 0, ); @@ -7912,6 +8252,11 @@ fn gas_spent_vs_balance() { #[test] fn gas_spent_precalculated() { + use gear_wasm_instrument::parity_wasm::{ + self, + elements::{Instruction, Module}, + }; + // After instrumentation will be: // (export "handle" (func $handle_export)) // (func $add @@ -7952,102 +8297,25 @@ fn gas_spent_precalculated() { ) )"#; - // After instrumentation will be: - // (export "init" (func $init_export)) - // (func $init) - // (func $init_export - // <-- call gas_charge --> - // <-- stack limit check and increase --> - // call $init - // <-- stack limit decrease --> - // ) - let wat_empty_init = r#" - (module - (import "env" "memory" (memory 1)) - (export "init" (func $init)) - (func $init) - )"#; - - // After instrumentation will be: - // (export "init" (func $init_export)) - // (func $f1) - // (func $init - // <-- call gas_charge --> - // <-- stack limit check and increase --> - // call $f1 - // <-- stack limit decrease --> - // ) - // (func $init_export - // <-- call gas_charge --> - // <-- stack limit check and increase --> - // call $init - // <-- stack limit decrease --> - // ) - let wat_two_stack_limits = r#" - (module - (import "env" "memory" (memory 1)) - (export "init" (func $init)) - (func $f1) - (func $init - (call $f1) - ) - )"#; - - // After instrumentation will be: - // (export "init" (func $init_export)) - // (func $init - // <-- call gas_charge --> - // <-- stack limit check and increase --> - // i32.const 1 - // local.set $1 - // <-- stack limit decrease --> - // ) - // (func $init_export - // <-- call gas_charge --> - // <-- stack limit check and increase --> - // call $init - // <-- stack limit decrease --> - // ) - let wat_two_gas_charge = r#" - (module - (import "env" "memory" (memory 1)) - (export "init" (func $init)) - (func $init - (local $1 i32) - i32.const 1 - local.set $1 - ) - )"#; - init_logger(); new_test_ext().execute_with(|| { let pid = upload_program_default(USER_1, ProgramCodeKind::Custom(wat)) .expect("submit result was asserted"); - let empty_init_pid = - upload_program_default(USER_3, ProgramCodeKind::Custom(wat_empty_init)) - .expect("submit result was asserted"); - let init_two_gas_charge_pid = - upload_program_default(USER_3, ProgramCodeKind::Custom(wat_two_gas_charge)) - .expect("submit result was asserted"); - let init_two_stack_limits_pid = - upload_program_default(USER_3, ProgramCodeKind::Custom(wat_two_stack_limits)) - .expect("submit result was asserted"); run_to_block(2, None); - let get_program_code_len = |pid| { + let get_program_code = |pid| { let code_id = CodeId::from_origin( ProgramStorageOf::::get_program(pid) .and_then(|program| common::ActiveProgram::try_from(program).ok()) .expect("program must exist") .code_hash, ); - ::CodeStorage::get_code(code_id) - .unwrap() - .code() - .len() as u64 + ::CodeStorage::get_code(code_id).unwrap() }; + let get_program_code_len = |pid| get_program_code(pid).code().len() as u64; + let get_gas_charged_for_code = |pid| { let schedule = ::Schedule::get(); let per_byte_cost = schedule.db_read_per_byte.ref_time(); @@ -8058,22 +8326,37 @@ fn gas_spent_precalculated() { + module_instantiation_per_byte * code_len }; - let calc_gas_spent_for_init = |wat| { - Gear::calculate_gas_info( - USER_1.into_origin(), - HandleKind::Init(ProgramCodeKind::Custom(wat).to_bytes()), - EMPTY_PAYLOAD.to_vec(), - 0, - true, - true, - ) - .unwrap() - .min_limit - }; + let instrumented_code = get_program_code(pid); + let module = parity_wasm::deserialize_buffer::(instrumented_code.code()) + .expect("invalid wasm bytes"); + + let (handle_export_func_body, gas_charge_func_body) = module + .code_section() + .and_then(|section| match section.bodies() { + [.., handle_export, gas_charge] => Some((handle_export, gas_charge)), + _ => None, + }) + .expect("failed to locate `handle_export()` and `gas_charge()` functions"); - let gas_two_gas_charge = calc_gas_spent_for_init(wat_two_gas_charge); - let gas_two_stack_limits = calc_gas_spent_for_init(wat_two_stack_limits); - let gas_empty_init = calc_gas_spent_for_init(wat_empty_init); + let gas_charge_call_cost = gas_charge_func_body + .code() + .elements() + .iter() + .find_map(|instruction| match instruction { + Instruction::I64Const(cost) => Some(*cost as u64), + _ => None, + }) + .expect("failed to get cost of `gas_charge()` function"); + + let handle_export_instructions = handle_export_func_body.code().elements(); + assert!(matches!( + handle_export_instructions, + [ + Instruction::I32Const(_), //stack check limit cost + Instruction::Call(_), //call to `gas_charge()` + .. + ] + )); macro_rules! cost { ($name:ident) => { @@ -8081,30 +8364,14 @@ fn gas_spent_precalculated() { }; } - // `wat_empty_init` has 1 gas_charge call and - // `wat_two_gas_charge` has 2 gas_charge calls, so we can calculate - // gas_charge function call cost as difference between them, - // taking in account difference in other aspects. - let gas_charge_call_cost = (gas_two_gas_charge - gas_empty_init) - // Take in account difference in executed instructions - - cost!(i64const) - - cost!(local_set) - // Take in account difference in gas depended on code len - - (get_gas_charged_for_code(init_two_gas_charge_pid) - - get_gas_charged_for_code(empty_init_pid)); - - // `wat_empty_init` has 1 stack limit check and - // `wat_two_stack_limits` has 2 stack limit checks, so we can calculate - // stack limit check cost as difference between them, - // taking in account difference in other aspects. - let stack_check_limit_cost = (gas_two_stack_limits - gas_empty_init) - // Take in account difference in executed instructions - - cost!(call) - // Take in account additional gas_charge call - - gas_charge_call_cost - // Take in account difference in gas depended on code len - - (get_gas_charged_for_code(init_two_stack_limits_pid) - - get_gas_charged_for_code(empty_init_pid)); + let stack_check_limit_cost = handle_export_instructions + .iter() + .find_map(|instruction| match instruction { + Instruction::I32Const(cost) => Some(*cost as u64), + _ => None, + }) + .expect("failed to get stack check limit cost") + - cost!(call); let gas_spent_expected = { let execution_cost = cost!(call) * 2 @@ -8219,7 +8486,7 @@ fn test_two_contracts_composition_works() { } // Before introducing this test, upload_program extrinsic didn't check the value. -// Also value wasn't check in `create_program` sys-call. There could be the next test case, which could affect badly. +// Also value wasn't check in `create_program` syscall. There could be the next test case, which could affect badly. // // User submits program with value X, which is not checked. Say X < ED. If we send handle and reply messages with // values during the init message processing, internal checks will result in errors (either, because sending value @@ -8335,7 +8602,7 @@ fn test_create_program_with_value_lt_ed() { } // Before introducing this test, upload_program extrinsic didn't check the value. -// Also value wasn't check in `create_program` sys-call. There could be the next test case, which could affect badly. +// Also value wasn't check in `create_program` syscall. There could be the next test case, which could affect badly. // // For instance, we have a guarantee that provided init message value is more than ED before executing message. // User sends init message to the program, which, for example, in init function sends different kind of messages. @@ -8555,7 +8822,7 @@ fn demo_constructor_is_demo_ping() { let handle_reply = Calls::builder().panic("I don't like replies"); - let scheme = Scheme::predefined(init, handle, handle_reply); + let scheme = Scheme::predefined(init, handle, handle_reply, Default::default()); // checking init let (_init_mid, constructor_id) = utils::init_constructor(scheme); @@ -8744,7 +9011,8 @@ fn delayed_wake() { assert_ok!(Gear::send_message( RuntimeOrigin::signed(USER_1), prog, - vec![], + // Non zero size payload to trigger other demos repr case. + vec![0], BlockGasLimitOf::::get(), 0, false, @@ -14105,7 +14373,12 @@ fn double_read_works() { .load("read2") .bytes_eq("is_eq", "read1", "read2") .if_else("is_eq", noop_branch, panic_branch); - let predefined_scheme = Scheme::predefined(Default::default(), handle, Default::default()); + let predefined_scheme = Scheme::predefined( + Default::default(), + handle, + Default::default(), + Default::default(), + ); let (_, pid) = utils::init_constructor(predefined_scheme); @@ -14334,7 +14607,7 @@ fn test_send_to_terminated_from_program() { // Using `USER_2` not to pollute `USER_1` mailbox to make test easier. USER_2, b"salt1", - Scheme::predefined(init, handle, Calls::default()), + Scheme::predefined(init, handle, Calls::default(), Calls::default()), 0, ); @@ -14351,7 +14624,7 @@ fn test_send_to_terminated_from_program() { // Using `USER_2` not to pollute `USER_1` mailbox to make test easier. USER_2, b"salt2", - Scheme::predefined(Calls::default(), handle, handle_reply), + Scheme::predefined(Calls::default(), handle, handle_reply, Calls::default()), 0, ); @@ -14650,7 +14923,7 @@ fn test_gas_info_of_terminated_program() { let (_, pid_dead) = utils::submit_constructor_with_args( USER_1, b"salt1", - Scheme::predefined(init_dead, handle_dead, Calls::default()), + Scheme::predefined(init_dead, handle_dead, Calls::default(), Calls::default()), 0, ); @@ -14659,7 +14932,12 @@ fn test_gas_info_of_terminated_program() { let (_, proxy_pid) = utils::submit_constructor_with_args( USER_1, b"salt2", - Scheme::predefined(Calls::default(), handle_proxy, Calls::default()), + Scheme::predefined( + Calls::default(), + handle_proxy, + Calls::default(), + Calls::default(), + ), 0, ); @@ -14677,6 +14955,64 @@ fn test_gas_info_of_terminated_program() { }) } +#[test] +fn test_handle_signal_wait() { + use demo_signal_wait::WASM_BINARY; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + EMPTY_PAYLOAD.to_vec(), + 100_000_000_000, + 0, + false, + )); + + let pid = get_last_program_id(); + + run_to_next_block(None); + + assert!(Gear::is_active(pid)); + assert!(Gear::is_initialized(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + EMPTY_PAYLOAD.to_vec(), + 50_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_next_block(None); + + assert_ok!(GasHandlerOf::::get_system_reserve(mid)); + assert!(WaitlistOf::::contains(&pid, &mid)); + + run_to_next_block(None); + + let signal_mid = MessageId::generate_signal(mid); + assert!(WaitlistOf::::contains(&pid, &signal_mid)); + + let (mid, block) = get_last_message_waited(); + + assert_eq!(mid, signal_mid); + + System::set_block_number(block - 1); + Gear::set_block_number(block - 1); + run_to_next_block(None); + + assert!(!WaitlistOf::::contains(&pid, &signal_mid)); + + assert_total_dequeued(4); + }); +} + mod utils { #![allow(unused)] diff --git a/pallets/payment/src/lib.rs b/pallets/payment/src/lib.rs index 68e6ecaa67d..ea678fa647c 100644 --- a/pallets/payment/src/lib.rs +++ b/pallets/payment/src/lib.rs @@ -17,6 +17,8 @@ // along with this program. If not, see . #![cfg_attr(not(feature = "std"), no_std)] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] use common::{storage::*, ExtractCall}; use frame_support::{ diff --git a/runtime-interface/Cargo.toml b/runtime-interface/Cargo.toml index 58553b0c706..f1b7f225064 100644 --- a/runtime-interface/Cargo.toml +++ b/runtime-interface/Cargo.toml @@ -11,15 +11,16 @@ repository.workspace = true [dependencies] gear-core.workspace = true gear-lazy-pages-common.workspace = true +gear-lazy-pages = { workspace = true, optional = true } +gear-sandbox-host = { workspace = true, optional = true } log = { workspace = true, optional = true } sp-runtime-interface.workspace = true sp-std.workspace = true sp-wasm-interface.workspace = true -gear-lazy-pages = { workspace = true, optional = true } +sp-io.workspace = true codec = { workspace = true } static_assertions.workspace = true -gear-sandbox-host = { workspace = true, optional = true } byteorder.workspace = true [target.'cfg(windows)'.dependencies] diff --git a/runtime-interface/src/lib.rs b/runtime-interface/src/lib.rs index 85cf8d6b139..9ac73217680 100644 --- a/runtime-interface/src/lib.rs +++ b/runtime-interface/src/lib.rs @@ -30,6 +30,8 @@ use gear_core::{ memory::{HostPointer, MemoryInterval}, str::LimitedStr, }; +#[cfg(feature = "std")] +use gear_lazy_pages::LazyPagesStorage; use gear_lazy_pages_common::{GlobalsAccessConfig, ProcessAccessError, Status}; use sp_runtime_interface::{ pass_by::{Codec, PassBy}, @@ -71,14 +73,60 @@ impl PassBy for LazyPagesProgramContext { #[derive(Debug, Clone, Encode, Decode)] #[codec(crate = codec)] -pub struct LazyPagesRuntimeContext { +pub struct LazyPagesInitContext { pub page_sizes: Vec, pub global_names: Vec>, pub pages_storage_prefix: Vec, } -impl PassBy for LazyPagesRuntimeContext { - type PassBy = Codec; +impl From for LazyPagesInitContext { + fn from(ctx: gear_lazy_pages_common::LazyPagesInitContext) -> Self { + let gear_lazy_pages_common::LazyPagesInitContext { + page_sizes, + global_names, + pages_storage_prefix, + } = ctx; + + Self { + page_sizes, + global_names, + pages_storage_prefix, + } + } +} + +impl From for gear_lazy_pages_common::LazyPagesInitContext { + fn from(ctx: LazyPagesInitContext) -> Self { + let LazyPagesInitContext { + page_sizes, + global_names, + pages_storage_prefix, + } = ctx; + + Self { + page_sizes, + global_names, + pages_storage_prefix, + } + } +} + +impl PassBy for LazyPagesInitContext { + type PassBy = Codec; +} + +#[derive(Debug, Default)] +struct SpIoProgramStorage; + +#[cfg(feature = "std")] +impl LazyPagesStorage for SpIoProgramStorage { + fn page_exists(&self, key: &[u8]) -> bool { + sp_io::storage::exists(key) + } + + fn load_page(&mut self, key: &[u8], buffer: &mut [u8]) -> Option { + sp_io::storage::read(key, buffer, 0) + } } fn deserialize_mem_intervals(bytes: &[u8], intervals: &mut Vec) { @@ -166,17 +214,12 @@ pub trait GearRI { /// Init lazy-pages. /// Returns whether initialization was successful. - fn init_lazy_pages(ctx: LazyPagesRuntimeContext) -> bool { + fn init_lazy_pages(ctx: LazyPagesInitContext) -> bool { use gear_lazy_pages::LazyPagesVersion; - gear_lazy_pages::init( - LazyPagesVersion::Version1, - ctx.page_sizes, - ctx.global_names, - ctx.pages_storage_prefix, - ) - .map_err(|err| log::error!("Cannot initialize lazy-pages: {}", err)) - .is_ok() + gear_lazy_pages::init(LazyPagesVersion::Version1, ctx.into(), SpIoProgramStorage) + .map_err(|err| log::error!("Cannot initialize lazy-pages: {}", err)) + .is_ok() } /// Init lazy pages context for current program. diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index 539aace14cc..5b06a82defd 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -151,7 +151,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // The version of the runtime specification. A full node will not attempt to use its native // runtime in substitute for the on-chain Wasm runtime unless all of `spec_name`, // `spec_version`, and `authoring_version` are the same between Wasm and native. - spec_version: 1020, + spec_version: 1030, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fa43cef1d4e..a7adebc0c1e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2023-09-18" +channel = "nightly-2023-09-05" components = [ "llvm-tools" ] targets = [ "wasm32-unknown-unknown" ] profile = "default" diff --git a/sandbox/env/Cargo.toml b/sandbox/env/Cargo.toml index 844eaa9bc90..9c4a8891467 100644 --- a/sandbox/env/Cargo.toml +++ b/sandbox/env/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "gear-sandbox-env" -version = "0.1.0" -authors = ["Gear Technologies"] -edition = "2021" -license = "GPL-3.0" -homepage = "https://gear-tech.io" -repository = "https://github.com/gear-tech/gear" description = "This crate provides means to instantiate and execute wasm modules." +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +version.workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index d34332673a2..99b8c0770a0 100644 --- a/sandbox/host/Cargo.toml +++ b/sandbox/host/Cargo.toml @@ -1,31 +1,30 @@ [package] name = "gear-sandbox-host" -version = "0.1.0" -authors = ["Gear Technologies"] -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://gear-tech.io" -repository = "https://github.com/gear-tech/gear" description = "A set of common definitions that are needed for defining execution engines." readme = "README.md" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +version.workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, features = ["std"] } -environmental = "1.1.3" +environmental.workspace = true thiserror.workspace = true log = { workspace = true, features = ["std"] } -wasmer = { version = "2.2", features = ["singlepass"] } -wasmer-types = "2.2" +sandbox-wasmer.workspace = true +sandbox-wasmer-types.workspace = true wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", features = ["virtual_memory"] } sp-allocator = { workspace = true, features = ["std"] } sp-wasm-interface = { workspace = true, features = ["std"] } gear-sandbox-env = { workspace = true, features = ["std"] } -wasmer-cache = { version = "2.2.1", optional = true } -once_cell = "1.17.1" -tempfile = "3.5.0" +wasmer-cache = { workspace = true, optional = true } +tempfile.workspace = true [features] default = ["wasmer-cache"] diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 734a26996d1..96f1ea51b8f 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -181,7 +181,7 @@ enum BackendInstance { Wasmi(wasmi::ModuleRef), /// Wasmer module instance - Wasmer(wasmer::Instance), + Wasmer(sandbox_wasmer::Instance), } /// Sandboxed instance of a wasm module. diff --git a/sandbox/host/src/sandbox/wasmer_backend.rs b/sandbox/host/src/sandbox/wasmer_backend.rs index b200f4d2a14..35806cf9378 100644 --- a/sandbox/host/src/sandbox/wasmer_backend.rs +++ b/sandbox/host/src/sandbox/wasmer_backend.rs @@ -20,8 +20,8 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use wasmer::{Exportable, RuntimeError}; -use wasmer_types::TrapCode; +use sandbox_wasmer::{Exportable, RuntimeError}; +use sandbox_wasmer_types::TrapCode; use codec::{Decode, Encode}; use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue, GLOBAL_NAME_GAS}; @@ -46,33 +46,33 @@ enum CachedModuleErr { #[cfg(feature = "wasmer-cache")] use { - once_cell::sync::OnceCell, + sandbox_wasmer::Module, + std::sync::OnceLock, tempfile::TempDir, - wasmer::Module, wasmer_cache::{Cache, FileSystemCache, Hash}, CachedModuleErr::*, }; #[cfg(feature = "wasmer-cache")] -static CACHE_DIR: OnceCell = OnceCell::new(); +static CACHE_DIR: OnceLock = OnceLock::new(); /// Wasmer specific context pub struct Backend { - store: wasmer::Store, + store: sandbox_wasmer::Store, } impl Backend { pub fn new() -> Self { - let compiler = wasmer::Singlepass::default(); + let compiler = sandbox_wasmer::Singlepass::default(); Backend { - store: wasmer::Store::new(&wasmer::Universal::new(compiler).engine()), + store: sandbox_wasmer::Store::new(&sandbox_wasmer::Universal::new(compiler).engine()), } } } #[derive(Default)] pub struct Env { - gas: Option, + gas: Option, } // WARNING: intentionally to avoid cyclic refs @@ -88,12 +88,13 @@ impl Clone for Env { } } -impl wasmer::WasmerEnv for Env { +impl sandbox_wasmer::WasmerEnv for Env { fn init_with_instance( &mut self, - instance: &wasmer::Instance, - ) -> std::result::Result<(), wasmer::HostEnvInitError> { - let gas: wasmer::Global = instance.exports.get_with_generics_weak(GLOBAL_NAME_GAS)?; + instance: &sandbox_wasmer::Instance, + ) -> std::result::Result<(), sandbox_wasmer::HostEnvInitError> { + let gas: sandbox_wasmer::Global = + instance.exports.get_with_generics_weak(GLOBAL_NAME_GAS)?; self.gas = Some(gas); Ok(()) @@ -102,7 +103,7 @@ impl wasmer::WasmerEnv for Env { /// Invoke a function within a sandboxed module pub fn invoke( - instance: &wasmer::Instance, + instance: &sandbox_wasmer::Instance, export_name: &str, args: &[Value], sandbox_context: &mut dyn SandboxContext, @@ -112,7 +113,7 @@ pub fn invoke( .get_function(export_name) .map_err(|error| Error::Sandbox(error.to_string()))?; - let args: Vec = args.iter().map(into_wasmer_val).collect(); + let args: Vec = args.iter().map(into_wasmer_val).collect(); let wasmer_result = SandboxContextStore::using(sandbox_context, || { function @@ -150,7 +151,7 @@ pub fn invoke( #[cfg(feature = "wasmer-cache")] fn get_cached_module( wasm: &[u8], - store: &wasmer::Store, + store: &sandbox_wasmer::Store, ) -> core::result::Result { let cache_path = CACHE_DIR .get_or_init(|| { @@ -190,7 +191,7 @@ pub fn instantiate( } Err(err) => { log::trace!("Cache for contract has not been found, so compile it now"); - let module = wasmer::Module::new(&context.store, wasm) + let module = sandbox_wasmer::Module::new(&context.store, wasm) .map_err(|_| InstantiationError::ModuleDecoding)?; match err { CachedModuleErr::FileSystemErr => log::error!("Cannot open fs cache"), @@ -203,18 +204,18 @@ pub fn instantiate( }; #[cfg(not(feature = "wasmer-cache"))] - let module = wasmer::Module::new(&context.store, wasm) + let module = sandbox_wasmer::Module::new(&context.store, wasm) .map_err(|_| InstantiationError::ModuleDecoding)?; - type Exports = HashMap; + type Exports = HashMap; let mut exports_map = Exports::new(); for import in module.imports() { match import.ty() { // Nothing to do here - wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), + sandbox_wasmer::ExternType::Global(_) | sandbox_wasmer::ExternType::Table(_) => (), - wasmer::ExternType::Memory(_) => { + sandbox_wasmer::ExternType::Memory(_) => { let exports = exports_map.entry(import.module().to_string()).or_default(); let memory = guest_env @@ -243,10 +244,10 @@ pub fn instantiate( .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)? .clone(); - exports.insert(import.name(), wasmer::Extern::Memory(wasmer_memory)); + exports.insert(import.name(), sandbox_wasmer::Extern::Memory(wasmer_memory)); } - wasmer::ExternType::Function(func_ty) => { + sandbox_wasmer::ExternType::Function(func_ty) => { let guest_func_index = guest_env .imports .func_by_name(import.module(), import.name()); @@ -274,27 +275,27 @@ pub fn instantiate( let exports = exports_map.entry(import.module().to_string()).or_default(); - exports.insert(import.name(), wasmer::Extern::Function(function)); + exports.insert(import.name(), sandbox_wasmer::Extern::Function(function)); } } } - let mut import_object = wasmer::ImportObject::new(); + let mut import_object = sandbox_wasmer::ImportObject::new(); for (module_name, exports) in exports_map.into_iter() { import_object.register(module_name, exports); } let instance = SandboxContextStore::using(sandbox_context, || { - wasmer::Instance::new(&module, &import_object).map_err(|error| { - log::trace!("Failed to call wasmer::Instance::new: {error:?}"); + sandbox_wasmer::Instance::new(&module, &import_object).map_err(|error| { + log::trace!("Failed to call sandbox_wasmer::Instance::new: {error:?}"); match error { - wasmer::InstantiationError::Link(_) => InstantiationError::Instantiation, - wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, - wasmer::InstantiationError::HostEnvInitialization(_) => { + sandbox_wasmer::InstantiationError::Link(_) => InstantiationError::Instantiation, + sandbox_wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, + sandbox_wasmer::InstantiationError::HostEnvInitialization(_) => { InstantiationError::EnvironmentDefinitionCorrupted } - wasmer::InstantiationError::CpuFeature(_) => InstantiationError::CpuFeature, + sandbox_wasmer::InstantiationError::CpuFeature(_) => InstantiationError::CpuFeature, } }) })?; @@ -372,38 +373,38 @@ fn dispatch_common( serialized_result_val } -fn into_wasmer_val(value: &Value) -> wasmer::Val { +fn into_wasmer_val(value: &Value) -> sandbox_wasmer::Val { match value { - Value::I32(val) => wasmer::Val::I32(*val), - Value::I64(val) => wasmer::Val::I64(*val), - Value::F32(val) => wasmer::Val::F32(f32::from_bits(*val)), - Value::F64(val) => wasmer::Val::F64(f64::from_bits(*val)), + Value::I32(val) => sandbox_wasmer::Val::I32(*val), + Value::I64(val) => sandbox_wasmer::Val::I64(*val), + Value::F32(val) => sandbox_wasmer::Val::F32(f32::from_bits(*val)), + Value::F64(val) => sandbox_wasmer::Val::F64(f64::from_bits(*val)), } } -fn into_wasmer_result(value: ReturnValue) -> Vec { +fn into_wasmer_result(value: ReturnValue) -> Vec { match value { ReturnValue::Value(v) => vec![into_wasmer_val(&v)], ReturnValue::Unit => vec![], } } -fn into_value(value: &wasmer::Val) -> Option { +fn into_value(value: &sandbox_wasmer::Val) -> Option { match value { - wasmer::Val::I32(val) => Some(Value::I32(*val)), - wasmer::Val::I64(val) => Some(Value::I64(*val)), - wasmer::Val::F32(val) => Some(Value::F32(f32::to_bits(*val))), - wasmer::Val::F64(val) => Some(Value::F64(f64::to_bits(*val))), + sandbox_wasmer::Val::I32(val) => Some(Value::I32(*val)), + sandbox_wasmer::Val::I64(val) => Some(Value::I64(*val)), + sandbox_wasmer::Val::F32(val) => Some(Value::F32(f32::to_bits(*val))), + sandbox_wasmer::Val::F64(val) => Some(Value::F64(f64::to_bits(*val))), _ => None, } } fn dispatch_function( supervisor_func_index: SupervisorFuncIndex, - store: &wasmer::Store, - func_ty: &wasmer::FunctionType, -) -> wasmer::Function { - wasmer::Function::new(store, func_ty, move |params| { + store: &sandbox_wasmer::Store, + func_ty: &sandbox_wasmer::FunctionType, +) -> sandbox_wasmer::Function { + sandbox_wasmer::Function::new(store, func_ty, move |params| { SandboxContextStore::with(|sandbox_context| { // Serialize arguments into a byte vector. let invoke_args_data = params @@ -433,10 +434,10 @@ fn dispatch_function( fn dispatch_function_v2( supervisor_func_index: SupervisorFuncIndex, - store: &wasmer::Store, - func_ty: &wasmer::FunctionType, -) -> wasmer::Function { - wasmer::Function::new_with_env(store, func_ty, Env::default(), move |env, params| { + store: &sandbox_wasmer::Store, + func_ty: &sandbox_wasmer::FunctionType, +) -> sandbox_wasmer::Function { + sandbox_wasmer::Function::new_with_env(store, func_ty, Env::default(), move |env, params| { SandboxContextStore::with(|sandbox_context| { let gas = env .gas @@ -464,7 +465,7 @@ fn dispatch_function_v2( .map_err(|_| RuntimeError::new("Decoding Result failed!"))? .map_err(|_| RuntimeError::new("Supervisor function returned sandbox::HostError"))?; - gas.set(wasmer::Val::I64(deserialized_result.gas))?; + gas.set(sandbox_wasmer::Val::I64(deserialized_result.gas))?; Ok(into_wasmer_result(deserialized_result.inner)) }) @@ -478,9 +479,10 @@ pub fn new_memory( initial: u32, maximum: Option, ) -> crate::error::Result { - let ty = wasmer::MemoryType::new(initial, maximum, false); + let ty = sandbox_wasmer::MemoryType::new(initial, maximum, false); let memory = Memory::Wasmer(MemoryWrapper::new( - wasmer::Memory::new(&context.store, ty).map_err(|_| Error::InvalidMemoryReference)?, + sandbox_wasmer::Memory::new(&context.store, ty) + .map_err(|_| Error::InvalidMemoryReference)?, )); Ok(memory) @@ -490,12 +492,12 @@ pub fn new_memory( /// we wrap it with `RefCell` and encapsulate all memory operations. #[derive(Debug, Clone)] pub struct MemoryWrapper { - buffer: Rc>, + buffer: Rc>, } impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object - pub fn new(memory: wasmer::Memory) -> Self { + pub fn new(memory: sandbox_wasmer::Memory) -> Self { Self { buffer: Rc::new(RefCell::new(memory)), } @@ -508,7 +510,7 @@ impl MemoryWrapper { /// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since /// growing, we cannot guarantee the lifetime of the returned slice reference. - unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] { + unsafe fn memory_as_slice(memory: &sandbox_wasmer::Memory) -> &[u8] { let ptr = memory.data_ptr() as *const _; let len: usize = memory.data_size().try_into().expect( @@ -531,7 +533,8 @@ impl MemoryWrapper { /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is /// returned it must be ensured that only one mutable and no shared references to memory /// exists at the same time. - unsafe fn memory_as_slice_mut(memory: &mut wasmer::Memory) -> &mut [u8] { + #[allow(clippy::needless_pass_by_ref_mut)] + unsafe fn memory_as_slice_mut(memory: &mut sandbox_wasmer::Memory) -> &mut [u8] { let ptr = memory.data_ptr(); let len: usize = memory.data_size().try_into().expect( @@ -623,7 +626,7 @@ impl MemoryTransfer for MemoryWrapper { } /// Get global value by name -pub fn get_global(instance: &wasmer::Instance, name: &str) -> Option { +pub fn get_global(instance: &sandbox_wasmer::Instance, name: &str) -> Option { let global = instance.exports.get_global(name).ok()?; into_value(&global.get()) @@ -631,7 +634,7 @@ pub fn get_global(instance: &wasmer::Instance, name: &str) -> Option { /// Set global value by name pub fn set_global( - instance: &wasmer::Instance, + instance: &sandbox_wasmer::Instance, name: &str, value: Value, ) -> core::result::Result, crate::error::Error> { diff --git a/scripts/cargo-xwin.sh b/scripts/cargo-xwin.sh new file mode 100755 index 00000000000..5e7fe7dbf23 --- /dev/null +++ b/scripts/cargo-xwin.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +XWIN_ARCH="x86_64" CARGO_BUILD_TARGET=x86_64-pc-windows-msvc CARGO_TARGET_DIR="target-xwin" cargo xwin $@ diff --git a/scripts/check-fuzzer.sh b/scripts/check-fuzzer.sh index addd2e98bc3..ab624d796df 100755 --- a/scripts/check-fuzzer.sh +++ b/scripts/check-fuzzer.sh @@ -7,7 +7,7 @@ SCRIPTS="$(cd "$(dirname "$SELF")"/ && pwd)" main() { echo " >> Getting random bytes from /dev/urandom" - # Fuzzer expects a minimal input size of 25 MiB. Without providing a corpus of the same or larger + # Fuzzer expects a minimal input size of 350 KiB. Without providing a corpus of the same or larger # size fuzzer will stuck for a long time with trying to test the target using 0..100 bytes. mkdir -p utils/runtime-fuzzer/fuzz/corpus/main dd if=/dev/urandom of=utils/runtime-fuzzer/fuzz/corpus/main/check-fuzzer-bytes bs=1 count="$INITIAL_INPUT_SIZE" diff --git a/scripts/gear.sh b/scripts/gear.sh index 05161f5c741..1a62efb4852 100755 --- a/scripts/gear.sh +++ b/scripts/gear.sh @@ -301,9 +301,9 @@ case "$COMMAND" in header "Running fuzzer for runtime panic checks" run_fuzzer "$ROOT_DIR" "$1" "$2"; ;; - fuzz-repr) - header "Running fuzzer reproduction" - test_fuzzer_reproduction ;; + fuzzer-tests) + header "Running runtime-fuzzer crate tests" + run_fuzzer_tests ;; syscalls) header "Running syscalls integrity test of pallet-gear 'benchmarking' module on WASMI executor" diff --git a/scripts/src/test.sh b/scripts/src/test.sh index b2550519a29..d3282ce6dc0 100755 --- a/scripts/src/test.sh +++ b/scripts/src/test.sh @@ -95,8 +95,9 @@ run_fuzzer() { RUST_LOG="$LOG_TARGETS" cargo fuzz run --release --sanitizer=none main $CORPUS_DIR -- -rss_limit_mb=$RSS_LIMIT_MB -max_len=$MAX_LEN -len_control=0 } -test_fuzzer_reproduction() { - cargo nextest run -p runtime-fuzzer -E 'test(=tests::test_fuzzer_reproduction)' +run_fuzzer_tests() { + # This includes property tests for runtime-fuzzer. + cargo nextest run -p runtime-fuzzer } # TODO this is likely to be merged with `pallet_test` or `workspace_test` in #1802 diff --git a/utils/actor-system-error/Cargo.toml b/utils/actor-system-error/Cargo.toml index 503a0e8bbb2..9e90a629252 100644 --- a/utils/actor-system-error/Cargo.toml +++ b/utils/actor-system-error/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "actor-system-error" -version = "0.1.0" -edition = "2021" +description = "Helper crate for implementation of errors on gear backend" +edition.workspace = true +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] derive_more.workspace = true diff --git a/utils/calc-stack-height/Cargo.toml b/utils/calc-stack-height/Cargo.toml new file mode 100644 index 00000000000..8036b476d53 --- /dev/null +++ b/utils/calc-stack-height/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "calc-stack-height" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gear-core.workspace = true +vara-runtime = { workspace = true, features = ["std", "dev"] } +sandbox-wasmer.workspace = true +sandbox-wasmer-types.workspace = true +log.workspace = true +env_logger.workspace = true +wabt.workspace = true +anyhow.workspace = true diff --git a/utils/calc-stack-height/src/main.rs b/utils/calc-stack-height/src/main.rs new file mode 100644 index 00000000000..065dbed771a --- /dev/null +++ b/utils/calc-stack-height/src/main.rs @@ -0,0 +1,111 @@ +use gear_core::code::{Code, TryNewCodeConfig}; +use sandbox_wasmer::{ + Exports, Extern, Function, ImportObject, Instance, Memory, MemoryType, Module, Singlepass, + Store, Universal, +}; +use sandbox_wasmer_types::TrapCode; +use std::{env, fs}; + +fn main() -> anyhow::Result<()> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + + let schedule = vara_runtime::Schedule::get(); + let inf_recursion = fs::read("examples/wat/spec/inf_recursion.wat")?; + let inf_recursion = wabt::Wat2Wasm::new().convert(inf_recursion)?; + + let code = Code::try_new_mock_with_rules( + inf_recursion.as_ref().to_vec(), + |module| schedule.rules(module), + TryNewCodeConfig { + version: schedule.instruction_weights.version, + stack_height: Some(u32::MAX), + export_stack_height: true, + ..Default::default() + }, + ) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + let compiler = Singlepass::default(); + let store = Store::new(&Universal::new(compiler).engine()); + let module = Module::new(&store, code.code())?; + + let mut imports = ImportObject::new(); + let mut env = Exports::new(); + + let memory = Memory::new(&store, MemoryType::new(0, None, false))?; + env.insert("memory", Extern::Memory(memory)); + + let func = Function::new_native(&store, || {}); + env.insert("gr_out_of_gas", func); + + imports.register("env", env); + + let instance = Instance::new(&module, &imports)?; + let init = instance.exports.get_function("init")?; + let err = init.call(&[]).unwrap_err(); + assert_eq!(err.to_trap(), Some(TrapCode::StackOverflow)); + + let stack_height = instance + .exports + .get_global("__gear_stack_height")? + .get() + .i32() + .expect("Unexpected global type") as u32; + log::info!("Stack has overflowed at {} height", stack_height); + + log::info!("Binary search for maximum possible stack height"); + + let mut low = 0; + let mut high = stack_height - 1; + + let mut stack_height = 0; + + while low <= high { + let mid = (low + high) / 2; + + let code = Code::try_new( + inf_recursion.as_ref().to_vec(), + schedule.instruction_weights.version, + |module| schedule.rules(module), + Some(mid), + ) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + let module = Module::new(&store, code.code())?; + let instance = Instance::new(&module, &imports)?; + let init = instance.exports.get_function("init")?; + let err = init.call(&[]).unwrap_err(); + + match err.to_trap() { + Some(TrapCode::UnreachableCodeReached) => { + low = mid + 1; + + stack_height = mid; + + log::info!("Unreachable at {} height", mid); + } + Some(TrapCode::StackOverflow) => { + high = mid - 1; + + log::info!("Overflow at {} height", mid); + } + code => panic!("unexpected trap code: {:?}", code), + } + } + + println!( + "Stack height is {} for {}-{}", + stack_height, + env::consts::OS, + env::consts::ARCH + ); + + if let Some(schedule_stack_height) = schedule.limits.stack_height { + anyhow::ensure!( + schedule_stack_height <= stack_height, + "Stack height in runtime schedule must be decreased" + ); + } + + Ok(()) +} diff --git a/utils/crates-io/Cargo.toml b/utils/crates-io/Cargo.toml new file mode 100644 index 00000000000..834d1e80a86 --- /dev/null +++ b/utils/crates-io/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "crates-io-manager" +version.workspace = true +edition.workspace = true + +[[bin]] +name = "publish" +path = "publish.rs" + +[dependencies] +anyhow.workspace = true +cargo_metadata.workspace = true +cargo_toml.workspace = true +crates-io.workspace = true +curl.workspace = true +toml.workspace = true diff --git a/utils/crates-io/publish.rs b/utils/crates-io/publish.rs new file mode 100644 index 00000000000..f8e121abd08 --- /dev/null +++ b/utils/crates-io/publish.rs @@ -0,0 +1,171 @@ +//! mini-program for publishing packages to crates.io. +use anyhow::Result; +use cargo_metadata::MetadataCommand; +use cargo_toml::{Dependency, Manifest, Value}; +use crates_io::Registry; +use curl::easy::Easy; +use std::{ + collections::{BTreeMap, HashMap}, + env, fs, + path::PathBuf, + process::{Command, ExitStatus}, + thread, + time::Duration, +}; + +/// Packages need to be published. +const PACKAGES: [&str; 14] = [ + // Packages without local dependencies. + "actor-system-error", + "gear-common-codegen", + "gear-core-errors", + "gear-wasm-instrument", + "gmeta-codegen", + "gsdk-codegen", + "gsys", + // The packages below have local dependencies, + // and should be published in order. + "gmeta", + "gear-core", + "gear-utils", + "gear-common", + "gsdk", + "gcli", + "gclient", +]; + +/// Packages need to be patched in dependencies. +const PATCHED_PACKAGES: [&str; 1] = ["sp-arithmetic"]; + +struct CratesIo { + registry: Registry, +} + +impl CratesIo { + /// Create a new instance of `CratesIo`. + pub fn new() -> Result { + let mut handle = Easy::new(); + handle.useragent("gear-crates-io-manager")?; + + Ok(Self { + registry: Registry::new_handle("https://crates.io".into(), None, handle, false), + }) + } + + /// Verify if the package is published to crates.io. + pub fn verify(&mut self, package: &str, version: &str) -> Result { + // Here using limit = 1 since we are searching explicit + // packages here. + let (crates, _total) = self.registry.search(package, 1)?; + if crates.len() != 1 { + return Ok(false); + } + + Ok(crates[0].max_version == version) + } +} + +fn main() -> Result<()> { + let mut validator = CratesIo::new()?; + let metadata = MetadataCommand::new().no_deps().exec()?; + let mut graph = BTreeMap::new(); + let index = HashMap::::from_iter( + PACKAGES.into_iter().enumerate().map(|(i, p)| (p.into(), i)), + ); + + let workspace_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../Cargo.toml") + .canonicalize()?; + let workspace = Manifest::from_path(&workspace_path)?; + + for p in metadata.packages.into_iter() { + if !index.contains_key(&p.name) { + continue; + } + + let version = p.version.to_string(); + if validator.verify(&p.name, &version)? { + println!("Package {}@{} already published.", &p.name, &version); + continue; + } + + let path = p.manifest_path.into_std_path_buf(); + let mut manifest = Manifest::::from_slice_with_metadata(&fs::read(&path)?)?; + manifest.complete_from_path_and_workspace(&path, Some((&workspace, &workspace_path)))?; + + // NOTE: This is a bug inside of crate cargo_toml, it should + // not append crate-type = ["rlib"] to proc-macro crates, fixing + // it by hacking it now. + if p.name.ends_with("-codegen") { + if let Some(mut product) = manifest.lib { + product.crate_type = vec![]; + manifest.lib = Some(product); + } + } + + for (name, dep) in manifest.dependencies.iter_mut() { + // No need to update dependencies for packages without + // local dependencies. + if !index.contains_key(name) && !PATCHED_PACKAGES.contains(&name.as_str()) { + continue; + } + + let mut detail = if let Dependency::Detailed(detail) = &dep { + detail.clone() + } else { + continue; + }; + + match name.as_ref() { + // NOTE: the required version of sp-arithmetic is 6.0.0 in + // git repo, but 7.0.0 in crates.io, so we need to fix it. + "sp-arithmetic" => { + detail.version = Some("7.0.0".into()); + detail.branch = None; + detail.git = None; + } + _ => detail.version = Some(version.to_string()), + } + + *dep = Dependency::Detailed(detail); + } + + graph.insert(index.get(&p.name), (path, manifest)); + } + + for (path, manifest) in graph.values() { + println!("Publishing {:?}", path); + fs::write(path, toml::to_string_pretty(manifest)?)?; + + let path = path.to_string_lossy(); + let status = publish(&path)?; + if !status.success() { + println!( + "Failed to publish package {}...\nRetry after 11 mins...", + &path + ); + // The most likely reason for failure is that + // we have reached the rate limit of crates.io. + // + // Need to wait for 10 mins and try again. here + // we use 11 mins to be safe. + // + // Only retry for once, if it still fails, we + // will just give up. + thread::sleep(Duration::from_secs(660)); + publish(&path)?; + } + } + + Ok(()) +} + +fn publish(manifest: &str) -> Result { + Command::new("cargo") + .arg("publish") + .arg("--manifest-path") + .arg(manifest) + .arg("--allow-dirty") + .status() + .map_err(Into::into) +} diff --git a/utils/node-loader/Cargo.toml b/utils/node-loader/Cargo.toml index 9eebd6bbcee..fa7795ad9f4 100644 --- a/utils/node-loader/Cargo.toml +++ b/utils/node-loader/Cargo.toml @@ -24,7 +24,6 @@ clap = { workspace = true, features = ["derive"] } futures.workspace = true futures-timer.workspace = true names = "0.14.0" -once_cell.workspace = true parking_lot.workspace = true primitive-types = { workspace = true, features = ["scale-info"] } rand = { workspace = true, features = ["small_rng"] } diff --git a/utils/node-loader/src/batch_pool.rs b/utils/node-loader/src/batch_pool.rs index 16484fa1cab..d38aa50c8ab 100644 --- a/utils/node-loader/src/batch_pool.rs +++ b/utils/node-loader/src/batch_pool.rs @@ -306,7 +306,7 @@ async fn process_events( })??; } - let mut mailbox_from_events = utils::capture_mailbox_messages(&api, &mut v) + let mut mailbox_from_events = utils::capture_mailbox_messages(&api, &v) .await .expect("always valid by definition"); mailbox_added.append(&mut mailbox_from_events); diff --git a/utils/node-loader/src/batch_pool/api/nonce.rs b/utils/node-loader/src/batch_pool/api/nonce.rs index 44a0e4fdd66..70c622596cd 100644 --- a/utils/node-loader/src/batch_pool/api/nonce.rs +++ b/utils/node-loader/src/batch_pool/api/nonce.rs @@ -1,16 +1,18 @@ use crate::utils; use anyhow::{anyhow, Result}; use gclient::{Error as GClientError, Result as GClientResult}; -use once_cell::sync::OnceCell; use parking_lot::{Mutex, MutexGuard}; use std::{ cmp::Reverse, collections::BinaryHeap, - sync::atomic::{AtomicU64, Ordering}, + sync::{ + atomic::{AtomicU64, Ordering}, + OnceLock, + }, }; -pub static AVAILABLE_NONCE: OnceCell = OnceCell::new(); -pub static MISSED_NONCES: OnceCell> = OnceCell::new(); +pub static AVAILABLE_NONCE: OnceLock = OnceLock::new(); +pub static MISSED_NONCES: OnceLock> = OnceLock::new(); pub type MinHeap = BinaryHeap>; type MissedNoncesGuard<'a> = MutexGuard<'a, MinHeap>; diff --git a/utils/node-loader/src/utils.rs b/utils/node-loader/src/utils.rs index e6474508a81..c155c61a1b4 100644 --- a/utils/node-loader/src/utils.rs +++ b/utils/node-loader/src/utils.rs @@ -7,8 +7,8 @@ use gear_core::ids::{MessageId, ProgramId}; use gear_core_errors::ReplyCode; use gear_utils::NonEmpty; use gear_wasm_gen::{ - EntryPointsSet, InvocableSysCall, ParamType, StandardGearWasmConfigsBundle, SysCallName, - SysCallsInjectionTypes, SysCallsParamsConfig, + EntryPointsSet, InvocableSyscall, ParamType, StandardGearWasmConfigsBundle, SyscallName, + SyscallsInjectionTypes, SyscallsParamsConfig, }; use gsdk::metadata::runtime_types::{ gear_common::event::DispatchStatus as GenDispatchStatus, @@ -116,7 +116,7 @@ pub async fn stop_node(monitor_url: String) -> Result<()> { pub async fn capture_mailbox_messages( api: &GearApi, - event_source: &mut [gsdk::metadata::Event], + event_source: &[gsdk::metadata::Event], ) -> Result> { let to = ProgramId::from(api.account_id().as_ref()); // Mailbox message expiration threshold block number: current(last) block number + 20. @@ -213,23 +213,23 @@ pub fn get_wasm_gen_config( existing_programs: impl Iterator, ) -> StandardGearWasmConfigsBundle { let initial_pages = 2; - let mut injection_types = SysCallsInjectionTypes::all_once(); + let mut injection_types = SyscallsInjectionTypes::all_once(); injection_types.set_multiple( [ - (SysCallName::Leave, 0..=0), - (SysCallName::Panic, 0..=0), - (SysCallName::OomPanic, 0..=0), - (SysCallName::EnvVars, 0..=0), - (SysCallName::Send, 10..=15), - (SysCallName::Exit, 0..=1), - (SysCallName::Alloc, 3..=6), - (SysCallName::Free, 3..=6), + (SyscallName::Leave, 0..=0), + (SyscallName::Panic, 0..=0), + (SyscallName::OomPanic, 0..=0), + (SyscallName::EnvVars, 0..=0), + (SyscallName::Send, 10..=15), + (SyscallName::Exit, 0..=1), + (SyscallName::Alloc, 3..=6), + (SyscallName::Free, 3..=6), ] - .map(|(syscall, range)| (InvocableSysCall::Loose(syscall), range)) + .map(|(syscall, range)| (InvocableSyscall::Loose(syscall), range)) .into_iter(), ); - let mut params_config = SysCallsParamsConfig::default(); + let mut params_config = SyscallsParamsConfig::default(); params_config.add_rule(ParamType::Alloc, (1..=10).into()); params_config.add_rule(ParamType::Free, (initial_pages..=initial_pages + 50).into()); diff --git a/utils/runtime-fuzzer/README.md b/utils/runtime-fuzzer/README.md index 694bcac9fd9..114d5e270ef 100644 --- a/utils/runtime-fuzzer/README.md +++ b/utils/runtime-fuzzer/README.md @@ -16,14 +16,14 @@ Running fuzzer on the local machine: ```bash cd utils/runtime-fuzzer -# Fuzzer expects a minimal input size of 25 MiB. Without providing a corpus of the same or larger +# Fuzzer expects a minimal input size of 350 KiB. Without providing a corpus of the same or larger # size fuzzer will stuck for a long time with trying to test the target using 0..100 bytes. mkdir -p fuzz/corpus/main dd if=/dev/urandom of=fuzz/corpus/main/fuzzer-seed-corpus bs=1 count=350000 # Run fuzzer for at least 20 minutes and then press Ctrl-C to stop fuzzing. # You can also remove RUST_LOG to avoid printing tons of logs on terminal. -RUST_LOG=debug,syscalls,gear_wasm_gen=trace,runtime_fuzzer=trace,gear_core_backend=trace \ +RUST_LOG=debug,syscalls,runtime::sandbox=trace,gear_wasm_gen=trace,runtime_fuzzer=trace,gear_core_backend=trace \ cargo fuzz run \ --release \ --sanitizer=none \ diff --git a/utils/runtime-fuzzer/fuzz_corpus/c6e2a597aebabecc9bbb11eefdaa4dd8a6770188 b/utils/runtime-fuzzer/fuzz_corpus/c6e2a597aebabecc9bbb11eefdaa4dd8a6770188 new file mode 100644 index 00000000000..43bef7aa0bb Binary files /dev/null and b/utils/runtime-fuzzer/fuzz_corpus/c6e2a597aebabecc9bbb11eefdaa4dd8a6770188 differ diff --git a/utils/runtime-fuzzer/src/gear_calls.rs b/utils/runtime-fuzzer/src/gear_calls.rs index a75f122bcc6..cf241af7040 100644 --- a/utils/runtime-fuzzer/src/gear_calls.rs +++ b/utils/runtime-fuzzer/src/gear_calls.rs @@ -35,8 +35,8 @@ use gear_call_gen::{ClaimValueArgs, SendReplyArgs}; use gear_core::ids::{CodeId, MessageId, ProgramId}; use gear_utils::NonEmpty; use gear_wasm_gen::{ - EntryPointsSet, InvocableSysCall, ParamType, StandardGearWasmConfigsBundle, SysCallName, - SysCallsInjectionTypes, SysCallsParamsConfig, + EntryPointsSet, InvocableSyscall, ParamType, StandardGearWasmConfigsBundle, SyscallName, + SyscallsInjectionTypes, SyscallsParamsConfig, }; use std::mem; @@ -56,9 +56,10 @@ static_assertions::const_assert!(MAX_SALT_SIZE <= gear_core::message::MAX_PAYLOA const ID_SIZE: usize = mem::size_of::(); const GAS_AND_VALUE_SIZE: usize = mem::size_of::<(u64, u128)>(); -// Used to make sure that generators will not exceed `Unstructured` size as it's used not only -// to generate things like wasm code or message payload but also to generate some auxiliary -// data, for example index in some vec. + +/// Used to make sure that generators will not exceed `Unstructured` size as it's used not only +/// to generate things like wasm code or message payload but also to generate some auxiliary +/// data, for example index in some vec. const AUXILIARY_SIZE: usize = 512; /// This trait provides ability for [`ExtrinsicGenerator`]s to fetch messages @@ -407,23 +408,23 @@ fn config( log_info: Option, ) -> StandardGearWasmConfigsBundle { let initial_pages = 2; - let mut injection_types = SysCallsInjectionTypes::all_once(); + let mut injection_types = SyscallsInjectionTypes::all_once(); injection_types.set_multiple( [ - (SysCallName::Leave, 0..=0), - (SysCallName::Panic, 0..=0), - (SysCallName::OomPanic, 0..=0), - (SysCallName::EnvVars, 0..=0), - (SysCallName::Send, 10..=15), - (SysCallName::Exit, 0..=1), - (SysCallName::Alloc, 3..=6), - (SysCallName::Free, 3..=6), + (SyscallName::Leave, 0..=0), + (SyscallName::Panic, 0..=0), + (SyscallName::OomPanic, 0..=0), + (SyscallName::EnvVars, 0..=0), + (SyscallName::Send, 10..=15), + (SyscallName::Exit, 0..=1), + (SyscallName::Alloc, 3..=6), + (SyscallName::Free, 3..=6), ] - .map(|(syscall, range)| (InvocableSysCall::Loose(syscall), range)) + .map(|(syscall, range)| (InvocableSyscall::Loose(syscall), range)) .into_iter(), ); - let mut params_config = SysCallsParamsConfig::default(); + let mut params_config = SyscallsParamsConfig::default(); params_config.add_rule(ParamType::Alloc, (10..=20).into()); params_config.add_rule(ParamType::Free, (initial_pages..=initial_pages + 35).into()); diff --git a/utils/runtime-fuzzer/src/tests.rs b/utils/runtime-fuzzer/src/tests.rs index 9ccfef36a7c..c4acf49bc43 100644 --- a/utils/runtime-fuzzer/src/tests.rs +++ b/utils/runtime-fuzzer/src/tests.rs @@ -19,8 +19,8 @@ use crate::*; use proptest::prelude::*; -const MAX_GEAR_CALLS_BYTES: usize = 30_000_000; -const MIN_GEAR_CALLS_BYTES: usize = 25_000_000; +const MIN_GEAR_CALLS_BYTES: usize = 350_000; +const MAX_GEAR_CALLS_BYTES: usize = 450_000; #[test] fn proptest_input_validity() { @@ -28,6 +28,13 @@ fn proptest_input_validity() { assert!(MIN_GEAR_CALLS_BYTES <= MAX_GEAR_CALLS_BYTES); } +// This is a crashing input before c85f4563ce35d822958a23a92d85f798252c8466 commit to master. +#[test] +fn test_corpus_c6e2a597aebabecc9bbb11eefdaa4dd8a6770188() { + let input = include_bytes!("../fuzz_corpus/c6e2a597aebabecc9bbb11eefdaa4dd8a6770188"); + assert!(run_impl(input).is_ok()); +} + proptest! { #![proptest_config(ProptestConfig::with_cases(10))] #[test] diff --git a/utils/utils/Cargo.toml b/utils/utils/Cargo.toml index 779b44942ad..f91cf7d64d6 100644 --- a/utils/utils/Cargo.toml +++ b/utils/utils/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "gear-utils" -version = "0.1.0" +description = "Utils of gear network" +version.workspace = true +authors.workspace = true edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true [dependencies] nonempty.workspace = true diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index c876488ce9e..79bad58298c 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -30,7 +30,7 @@ regex.workspace = true [dev-dependencies] wabt.workspace = true -wasmi = {workspace = true, features = ["std"]} +wasmi = { workspace = true, features = ["std"] } [features] metawasm = ["gmeta/codegen"] diff --git a/utils/wasm-builder/src/cargo_toolchain.rs b/utils/wasm-builder/src/cargo_toolchain.rs index a0cdad14398..40a985593c7 100644 --- a/utils/wasm-builder/src/cargo_toolchain.rs +++ b/utils/wasm-builder/src/cargo_toolchain.rs @@ -59,6 +59,7 @@ impl Toolchain { .and_then(|s| std::str::from_utf8(s).ok()) .expect("unexpected `rustup` output"); + // TODO #3499: replace it with `std::sync::LazyLock` when it becomes stable static TOOLCHAIN_CHANNEL_RE: Lazy = Lazy::new(|| { // This regex is borrowed from the rustup code and modified (added non-capturing groups) let pattern = format!( diff --git a/utils/wasm-builder/src/lib.rs b/utils/wasm-builder/src/lib.rs index 46843995653..9e7e2e21c91 100644 --- a/utils/wasm-builder/src/lib.rs +++ b/utils/wasm-builder/src/lib.rs @@ -16,12 +16,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#![cfg_attr(feature = "strict", deny(warnings))] +#![doc(html_logo_url = "https://docs.gear.rs/logo.svg")] +#![doc(html_favicon_url = "https://gear-tech.io/favicons/favicon.ico")] + use crate::{cargo_command::CargoCommand, cargo_toolchain::Toolchain, wasm_project::WasmProject}; use anyhow::{Context, Result}; use gmeta::{Metadata, MetadataRepr}; use regex::Regex; use std::{env, path::PathBuf, process}; use wasm_project::ProjectType; +pub use wasm_project::{PreProcessOutput, PreProcessor}; mod builder_error; mod cargo_command; @@ -71,6 +76,12 @@ impl WasmBuilder { self } + /// Add pre-processor for wasm file + pub fn with_pre_processor(mut self, pre_processor: Box) -> Self { + self.wasm_project.add_preprocessor(pre_processor); + self + } + /// Build the program and produce an output WASM binary. pub fn build(self) { if env::var("__GEAR_WASM_BUILDER_NO_BUILD").is_ok() { diff --git a/utils/wasm-builder/src/optimize.rs b/utils/wasm-builder/src/optimize.rs index 263eb95e6d7..dcff479eb3f 100644 --- a/utils/wasm-builder/src/optimize.rs +++ b/utils/wasm-builder/src/optimize.rs @@ -49,7 +49,7 @@ pub struct Optimizer { impl Optimizer { pub fn new(file: PathBuf) -> Result { - let contents = fs::read(&file)?; + let contents = fs::read(&file).context("Failed to read file by optimizer")?; let module = parity_wasm::deserialize_buffer(&contents) .with_context(|| format!("File path: {file:?}"))?; Ok(Self { module, file }) diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index da77a64b9e0..9fb4a339358 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -64,6 +64,20 @@ pub struct WasmProject { profile: String, project_type: ProjectType, features: Option>, + pre_processors: Vec>, +} + +/// Preprocessor output +pub struct PreProcessOutput { + /// Location of the artifact generated by pre-processor + pub path: PathBuf, + /// Content of the artifact generated by pre-processor + pub content: Vec, +} + +/// Pre-processor hook for wasm generation +pub trait PreProcessor { + fn pre_process(&self, original: PathBuf) -> Result; } impl WasmProject { @@ -91,12 +105,12 @@ impl WasmProject { .to_string_lossy() .into(); - let mut target_dir = out_dir.clone(); - while target_dir.pop() { - if target_dir.ends_with("target") { - break; - } - } + let mut target_dir = out_dir + .ancestors() + .find(|path| path.ends_with(&profile)) + .and_then(|path| path.parent()) + .map(|p| p.to_owned()) + .expect("Could not find target directory"); let mut wasm_target_dir = target_dir.clone(); wasm_target_dir.push("wasm32-unknown-unknown"); @@ -114,9 +128,14 @@ impl WasmProject { profile, project_type, features: None, + pre_processors: vec![], } } + pub fn add_preprocessor(&mut self, pre_processor: Box) { + self.pre_processors.push(pre_processor) + } + /// Return the path to the temporary generated `Cargo.toml`. pub fn manifest_path(&self) -> PathBuf { self.out_dir.join("Cargo.toml") @@ -205,16 +224,22 @@ impl WasmProject { cargo_toml.insert("features".into(), features.into()); cargo_toml.insert("workspace".into(), Table::new().into()); - smart_fs::write(self.manifest_path(), toml::to_string_pretty(&cargo_toml)?)?; + smart_fs::write(self.manifest_path(), toml::to_string_pretty(&cargo_toml)?) + .context("Failed to write generated manifest path")?; // Copy original `Cargo.lock` if any let from_lock = self.original_dir.join("Cargo.lock"); let to_lock = self.out_dir.join("Cargo.lock"); - let _ = fs::copy(from_lock, to_lock); + drop(fs::copy(from_lock, to_lock)); let mut source_code = "#![no_std] pub use orig_project::*;\n".to_owned(); - fs::create_dir_all(&self.wasm_target_dir)?; + fs::create_dir_all(&self.wasm_target_dir).with_context(|| { + format!( + "Failed to create WASM target directory: {}", + self.wasm_target_dir.display() + ) + })?; // Write metadata if let Some(metadata) = &self.project_type.metadata() { @@ -263,7 +288,7 @@ extern "C" fn metahash() {{ } let src_dir = self.out_dir.join("src"); - fs::create_dir_all(&src_dir)?; + fs::create_dir_all(&src_dir).context("Failed to create `src` directory")?; smart_fs::write(src_dir.join("lib.rs"), source_code)?; Ok(()) @@ -342,11 +367,19 @@ extern "C" fn metahash() {{ .insert_stack_end_export() .unwrap_or_else(|err| log::info!("Cannot insert stack end export: {}", err)); optimizer.strip_custom_sections(); - fs::write(opt_wasm_path.clone(), optimizer.optimize(OptType::Opt)?)?; + fs::write(opt_wasm_path.clone(), optimizer.optimize(OptType::Opt)?) + .context("Failed to write optimized WASM binary")?; } // Create path string in `.binpath` file. let relative_path_to_wasm = pathdiff::diff_paths(&self.wasm_target_dir, &self.original_dir) + .with_context(|| { + format!( + "wasm_target_dir={}; original_dir={}", + self.wasm_target_dir.display(), + self.original_dir.display() + ) + }) .expect("Unable to calculate relative path") .join(file_base_name); smart_fs::write( @@ -398,7 +431,16 @@ extern "C" fn metahash() {{ .join(format!("wasm32-unknown-unknown/{}", self.profile)) .join(format!("{}.wasm", &file_base_name)); - fs::create_dir_all(&self.target_dir)?; + fs::create_dir_all(&self.target_dir).context("Failed to create target directory")?; + + for pre_processor in &self.pre_processors { + let output = pre_processor.pre_process(original_wasm_path.clone())?; + + let mut pre_processed_path = self.wasm_target_dir.clone(); + pre_processed_path.push(output.path); + + fs::write(&pre_processed_path, &output.content)?; + } if self.project_type.is_metawasm() { self.postprocess_meta(&original_wasm_path, file_base_name)?; @@ -438,7 +480,8 @@ extern "C" fn metahash() {{ // It is needed because feature set or toolchain can change. fn force_rerun_on_next_run(&self, wasm_file_path: &Path) -> Result<()> { let stamp_file_path = wasm_file_path.with_extension("stamp"); - fs::write(&stamp_file_path, ChronoLocal::now().to_rfc3339())?; + fs::write(&stamp_file_path, ChronoLocal::now().to_rfc3339()) + .context("Failed to write stamp file")?; println!("cargo:rerun-if-changed={}", stamp_file_path.display()); Ok(()) } diff --git a/utils/wasm-gen/src/config.rs b/utils/wasm-gen/src/config.rs index 3b797ed5d15..7042322963b 100644 --- a/utils/wasm-gen/src/config.rs +++ b/utils/wasm-gen/src/config.rs @@ -54,7 +54,7 @@ //! stack_end_page: Some(64), //! }; //! let entry_points_set = EntryPointsSet::InitHandle; -//! let syscalls_config = SysCallsConfigBuilder::new(SysCallsInjectionTypes::all_once()) +//! let syscalls_config = SyscallsConfigBuilder::new(SyscallsInjectionTypes::all_once()) //! .with_source_msg_dest() //! .with_log_info("I'm from wasm-gen".into()) //! .build(); @@ -62,7 +62,8 @@ //! let wasm_gen_config = GearWasmGeneratorConfig { //! memory_config: memory_pages_config, //! entry_points_config: entry_points_set, -//! remove_recursions: true, +//! remove_recursions: false, +//! critical_gas_limit: Some(1_000_000), //! syscalls_config, //! }; //! ``` @@ -140,8 +141,15 @@ pub struct StandardGearWasmConfigsBundle { pub existing_addresses: Option>, /// Flag which signals whether recursions must be removed. pub remove_recursion: bool, + /// If the limit is set to `Some(_)`, programs will try to stop execution + /// after reaching a critical gas limit, which can be useful to exit from + /// heavy loops and recursions that waste all gas. + /// + /// The `gr_gas_available` syscall is called at the beginning of each + /// function and for each control instruction (blocks, loops, conditions). + pub critical_gas_limit: Option, /// Injection type for each syscall. - pub injection_types: SysCallsInjectionTypes, + pub injection_types: SyscallsInjectionTypes, /// Config of gear wasm call entry-points (exports). pub entry_points_set: EntryPointsSet, /// Initial wasm memory pages. @@ -149,7 +157,7 @@ pub struct StandardGearWasmConfigsBundle { /// Optional stack end pages. pub stack_end_page: Option, /// Syscalls params config - pub params_config: SysCallsParamsConfig, + pub params_config: SyscallsParamsConfig, } impl Default for StandardGearWasmConfigsBundle { @@ -158,11 +166,12 @@ impl Default for StandardGearWasmConfigsBundle { log_info: Some("StandardGearWasmConfigsBundle".into()), existing_addresses: None, remove_recursion: false, - injection_types: SysCallsInjectionTypes::all_once(), + critical_gas_limit: Some(1_000_000), + injection_types: SyscallsInjectionTypes::all_once(), entry_points_set: Default::default(), initial_pages: DEFAULT_INITIAL_SIZE, stack_end_page: None, - params_config: SysCallsParamsConfig::default(), + params_config: SyscallsParamsConfig::default(), } } } @@ -173,6 +182,7 @@ impl> ConfigsBundle for StandardGearWasmConfigsBundle { log_info, existing_addresses, remove_recursion, + critical_gas_limit, injection_types, entry_points_set, initial_pages, @@ -182,12 +192,12 @@ impl> ConfigsBundle for StandardGearWasmConfigsBundle { let selectable_params = SelectableParams::default(); - let mut syscalls_config_builder = SysCallsConfigBuilder::new(injection_types); + let mut syscalls_config_builder = SyscallsConfigBuilder::new(injection_types); if let Some(log_info) = log_info { syscalls_config_builder = syscalls_config_builder.with_log_info(log_info); } if let Some(addresses) = existing_addresses { - syscalls_config_builder = syscalls_config_builder.with_data_offset_msg_dest(addresses); + syscalls_config_builder = syscalls_config_builder.with_addresses_msg_dest(addresses); } else { syscalls_config_builder = syscalls_config_builder.with_source_msg_dest(); } @@ -199,6 +209,7 @@ impl> ConfigsBundle for StandardGearWasmConfigsBundle { upper_limit: None, }; let gear_wasm_generator_config = GearWasmGeneratorConfigBuilder::new() + .with_critical_gas_limit(critical_gas_limit) .with_recursions_removed(remove_recursion) .with_syscalls_config(syscalls_config_builder.build()) .with_entry_points_config(entry_points_set) diff --git a/utils/wasm-gen/src/config/generator.rs b/utils/wasm-gen/src/config/generator.rs index 2c1da46750f..2cea6dd74fd 100644 --- a/utils/wasm-gen/src/config/generator.rs +++ b/utils/wasm-gen/src/config/generator.rs @@ -18,7 +18,7 @@ //! Configs related to instantiation of gear wasm module generators. -use crate::SysCallsConfig; +use crate::SyscallsConfig; pub(crate) const DEFAULT_INITIAL_SIZE: u32 = 16; @@ -47,7 +47,7 @@ impl GearWasmGeneratorConfigBuilder { } /// Defines syscalls config for the gear wasm generator. - pub fn with_syscalls_config(mut self, syscalls_config: SysCallsConfig) -> Self { + pub fn with_syscalls_config(mut self, syscalls_config: SyscallsConfig) -> Self { self.0.syscalls_config = syscalls_config; self @@ -60,6 +60,13 @@ impl GearWasmGeneratorConfigBuilder { self } + /// Defines whether programs should have a critical gas limit. + pub fn with_critical_gas_limit(mut self, critical_gas_limit: Option) -> Self { + self.0.critical_gas_limit = critical_gas_limit; + + self + } + /// Build the gear wasm generator. pub fn build(self) -> GearWasmGeneratorConfig { self.0 @@ -77,10 +84,13 @@ pub struct GearWasmGeneratorConfig { /// Entry points config. pub entry_points_config: EntryPointsSet, /// Syscalls generator module config. - pub syscalls_config: SysCallsConfig, + pub syscalls_config: SyscallsConfig, /// Flag, signalizing whether recursions /// should be removed from resulting module. pub remove_recursions: bool, + /// The critical gas limit after which the program + /// will attempt to terminate successfully. + pub critical_gas_limit: Option, } /// Memory pages config used by [`crate::MemoryGenerator`]. diff --git a/utils/wasm-gen/src/config/syscalls.rs b/utils/wasm-gen/src/config/syscalls.rs index 143f0ec826f..ede17d5def5 100644 --- a/utils/wasm-gen/src/config/syscalls.rs +++ b/utils/wasm-gen/src/config/syscalls.rs @@ -24,7 +24,7 @@ mod param; mod precise; use gear_utils::NonEmpty; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; use gsys::{Hash, HashWithValue}; use std::collections::HashSet; @@ -32,27 +32,27 @@ pub use injection::*; pub use param::*; pub use precise::*; -use crate::InvocableSysCall; +use crate::InvocableSyscall; -/// Builder for [`SysCallsConfig`]. +/// Builder for [`SyscallsConfig`]. #[derive(Debug, Clone)] -pub struct SysCallsConfigBuilder(SysCallsConfig); +pub struct SyscallsConfigBuilder(SyscallsConfig); -impl SysCallsConfigBuilder { +impl SyscallsConfigBuilder { /// Create a new builder with defined injection amounts for all syscalls. - pub fn new(injection_types: SysCallsInjectionTypes) -> Self { - Self(SysCallsConfig { + pub fn new(injection_types: SyscallsInjectionTypes) -> Self { + Self(SyscallsConfig { injection_types, - params_config: SysCallsParamsConfig::default(), - precise_syscalls_config: PreciseSysCallsConfig::default(), - syscall_destination: SysCallDestination::default(), + params_config: SyscallsParamsConfig::default(), + precise_syscalls_config: PreciseSyscallsConfig::default(), + syscall_destination: SyscallDestination::default(), error_processing_config: ErrorProcessingConfig::None, log_info: None, }) } /// Set config for syscalls params. - pub fn with_params_config(mut self, params_config: SysCallsParamsConfig) -> Self { + pub fn with_params_config(mut self, params_config: SyscallsParamsConfig) -> Self { self.0.params_config = params_config; self @@ -61,32 +61,32 @@ impl SysCallsConfigBuilder { /// Set config for precise syscalls. pub fn with_precise_syscalls_config( mut self, - precise_syscalls_config: PreciseSysCallsConfig, + precise_syscalls_config: PreciseSyscallsConfig, ) -> Self { self.0.precise_syscalls_config = precise_syscalls_config; self } - /// Set whether `gr_send*` and `gr_exit` syscalls must use `gr_source` result for syscall destination. + /// Set whether syscalls with destination param (like `gr_*send*` or `gr_exit`) must use `gr_source` syscall result for a destination param. pub fn with_source_msg_dest(mut self) -> Self { - self.0.syscall_destination = SysCallDestination::Source; + self.0.syscall_destination = SyscallDestination::Source; self.0 .injection_types - .enable_syscall_import(InvocableSysCall::Loose(SysCallName::Source)); + .enable_syscall_import(InvocableSyscall::Loose(SyscallName::Source)); self } - /// Set whether `gr_send*` and `gr_exit` syscalls must use some address from `addresses` collection - /// as a syscall destination. - pub fn with_data_offset_msg_dest>(mut self, addresses: NonEmpty) -> Self { + /// Set whether syscalls with destination param (like `gr_*send*` or `gr_exit`) must use addresses from `addresses` collection + /// for a destination param. + pub fn with_addresses_msg_dest>(mut self, addresses: NonEmpty) -> Self { let addresses = NonEmpty::collect(addresses.into_iter().map(|pid| HashWithValue { hash: pid.into(), value: 0, })) .expect("collected from non empty"); - self.0.syscall_destination = SysCallDestination::ExistingAddresses(addresses); + self.0.syscall_destination = SyscallDestination::ExistingAddresses(addresses); self } @@ -99,7 +99,7 @@ impl SysCallsConfigBuilder { self.0.log_info = Some(log); self.0 .injection_types - .enable_syscall_import(InvocableSysCall::Loose(SysCallName::Debug)); + .enable_syscall_import(InvocableSyscall::Loose(SyscallName::Debug)); self } @@ -111,8 +111,8 @@ impl SysCallsConfigBuilder { self } - /// Build the [`SysCallsConfig`]. - pub fn build(self) -> SysCallsConfig { + /// Build the [`SyscallsConfig`]. + pub fn build(self) -> SyscallsConfig { self.0 } } @@ -122,16 +122,16 @@ pub enum ErrorProcessingConfig { /// Process errors on all the fallible syscalls. All, /// Process only errors on provided syscalls. - Whitelist(HashSet), + Whitelist(HashSet), /// Process errors on all the syscalls excluding provided. - Blacklist(HashSet), + Blacklist(HashSet), /// Don't process syscall errors at all. #[default] None, } impl ErrorProcessingConfig { - pub fn error_should_be_processed(&self, syscall: &InvocableSysCall) -> bool { + pub fn error_should_be_processed(&self, syscall: &InvocableSyscall) -> bool { match self { Self::All => true, Self::Whitelist(wl) => wl.contains(syscall), @@ -143,42 +143,42 @@ impl ErrorProcessingConfig { /// United config for all entities in syscalls generator module. #[derive(Debug, Clone, Default)] -pub struct SysCallsConfig { - injection_types: SysCallsInjectionTypes, - params_config: SysCallsParamsConfig, - precise_syscalls_config: PreciseSysCallsConfig, - syscall_destination: SysCallDestination, +pub struct SyscallsConfig { + injection_types: SyscallsInjectionTypes, + params_config: SyscallsParamsConfig, + precise_syscalls_config: PreciseSyscallsConfig, + syscall_destination: SyscallDestination, error_processing_config: ErrorProcessingConfig, log_info: Option, } -impl SysCallsConfig { +impl SyscallsConfig { /// Get possible number of times (range) the syscall can be injected in the wasm. - pub fn injection_types(&self, name: InvocableSysCall) -> SysCallInjectionType { + pub fn injection_types(&self, name: InvocableSyscall) -> SyscallInjectionType { self.injection_types.get(name) } /// Get defined syscall destination for `gr_send*` and `gr_exit` syscalls. /// - /// For more info, read [`SysCallDestination`]. - pub fn syscall_destination(&self) -> &SysCallDestination { + /// For more info, read [`SyscallDestination`]. + pub fn syscall_destination(&self) -> &SyscallDestination { &self.syscall_destination } /// Get defined log info. /// - /// For more info, read [`SysCallsConfigBuilder::with_log_info`]. + /// For more info, read [`SyscallsConfigBuilder::with_log_info`]. pub fn log_info(&self) -> Option<&String> { self.log_info.as_ref() } /// Get syscalls params config. - pub fn params_config(&self) -> &SysCallsParamsConfig { + pub fn params_config(&self) -> &SyscallsParamsConfig { &self.params_config } /// Get precise syscalls config. - pub fn precise_syscalls_config(&self) -> &PreciseSysCallsConfig { + pub fn precise_syscalls_config(&self) -> &PreciseSyscallsConfig { &self.precise_syscalls_config } @@ -195,26 +195,26 @@ impl SysCallsConfig { /// It's either to the message source, to some existing known address, /// or to some random, most probably non-existing, address. #[derive(Debug, Clone, Default)] -pub enum SysCallDestination { +pub enum SyscallDestination { Source, ExistingAddresses(NonEmpty), #[default] Random, } -impl SysCallDestination { +impl SyscallDestination { /// Check whether syscall destination is a result of `gr_source`. pub fn is_source(&self) -> bool { - matches!(&self, SysCallDestination::Source) + matches!(&self, SyscallDestination::Source) } /// Check whether syscall destination is defined randomly. pub fn is_random(&self) -> bool { - matches!(&self, SysCallDestination::Random) + matches!(&self, SyscallDestination::Random) } /// Check whether syscall destination is defined from a collection of existing addresses. pub fn is_existing_addresses(&self) -> bool { - matches!(&self, SysCallDestination::ExistingAddresses(_)) + matches!(&self, SyscallDestination::ExistingAddresses(_)) } } diff --git a/utils/wasm-gen/src/config/syscalls/injection.rs b/utils/wasm-gen/src/config/syscalls/injection.rs index 0c843443c19..681c77496d2 100644 --- a/utils/wasm-gen/src/config/syscalls/injection.rs +++ b/utils/wasm-gen/src/config/syscalls/injection.rs @@ -20,16 +20,16 @@ //! These entities allows to configure which syscalls to insert into //! code section of wasm module and which ones to simply import. //! -//! Types here are used to create [`crate::SysCallsConfig`]. +//! Types here are used to create [`crate::SyscallsConfig`]. -use crate::InvocableSysCall; +use crate::InvocableSyscall; -use gear_wasm_instrument::syscalls::SysCallName; +use gear_wasm_instrument::syscalls::SyscallName; use std::{collections::HashMap, ops::RangeInclusive}; /// This enum defines how the syscall should be injected into wasm module. #[derive(Debug, Clone)] -pub enum SysCallInjectionType { +pub enum SyscallInjectionType { /// Don't modify wasm module at all. None, /// Syscall import will be injected into import section of wasm module, @@ -45,7 +45,7 @@ pub enum SysCallInjectionType { /// It wraps syscall amount range `RangeInclusive` - the range from which /// amount of the syscall invocations will be generated. /// - /// Setting range to `(0..=n)`, where `n >= 0` can imitate `SysCallInjectionType::Import`, + /// Setting range to `(0..=n)`, where `n >= 0` can imitate `SyscallInjectionType::Import`, /// as in case if syscall amount range is zero, then syscall import will be injected, but /// no invocations will be generated, which is pretty similar to the other variant. Function(RangeInclusive), @@ -53,37 +53,37 @@ pub enum SysCallInjectionType { /// Possible injection types for each syscall. #[derive(Debug, Clone)] -pub struct SysCallsInjectionTypes(HashMap); +pub struct SyscallsInjectionTypes(HashMap); -impl SysCallsInjectionTypes { +impl SyscallsInjectionTypes { /// Instantiate a syscalls map, where each gear syscall is injected into wasm-module only once. pub fn all_once() -> Self { - Self::new_with_injection_type(SysCallInjectionType::Function(1..=1)) + Self::new_with_injection_type(SyscallInjectionType::Function(1..=1)) } /// Instantiate a syscalls map, where no gear syscall is ever injected into wasm-module. pub fn all_never() -> Self { - Self::new_with_injection_type(SysCallInjectionType::None) + Self::new_with_injection_type(SyscallInjectionType::None) } /// Instantiate a syscalls map with given injection type. - fn new_with_injection_type(injection_type: SysCallInjectionType) -> Self { - let syscalls = SysCallName::instrumentable(); + fn new_with_injection_type(injection_type: SyscallInjectionType) -> Self { + let syscalls = SyscallName::instrumentable(); Self( syscalls .iter() .cloned() - .map(|name| (InvocableSysCall::Loose(name), injection_type.clone())) + .map(|name| (InvocableSyscall::Loose(name), injection_type.clone())) .chain(syscalls.iter().cloned().filter_map(|name| { - InvocableSysCall::has_precise_variant(name) - .then_some((InvocableSysCall::Precise(name), injection_type.clone())) + InvocableSyscall::has_precise_variant(name) + .then_some((InvocableSyscall::Precise(name), injection_type.clone())) })) .collect(), ) } /// Gets injection type for given syscall. - pub fn get(&self, name: InvocableSysCall) -> SysCallInjectionType { + pub fn get(&self, name: InvocableSyscall) -> SyscallInjectionType { self.0 .get(&name) .cloned() @@ -92,34 +92,34 @@ impl SysCallsInjectionTypes { /// Sets possible amount range for the the syscall. /// - /// Sets injection type for `name` syscall to `SysCallInjectionType::Function`. - pub fn set(&mut self, name: InvocableSysCall, min: u32, max: u32) { + /// Sets injection type for `name` syscall to `SyscallInjectionType::Function`. + pub fn set(&mut self, name: InvocableSyscall, min: u32, max: u32) { self.0 - .insert(name, SysCallInjectionType::Function(min..=max)); + .insert(name, SyscallInjectionType::Function(min..=max)); - if let InvocableSysCall::Precise(syscall) = name { - let Some(required_imports) = InvocableSysCall::required_imports_for_syscall(syscall) + if let InvocableSyscall::Precise(syscall) = name { + let Some(required_imports) = InvocableSyscall::required_imports_for_syscall(syscall) else { return; }; for &syscall_import in required_imports { - self.enable_syscall_import(InvocableSysCall::Loose(syscall_import)); + self.enable_syscall_import(InvocableSyscall::Loose(syscall_import)); } } } /// Imports the given syscall, if necessary. - pub(crate) fn enable_syscall_import(&mut self, name: InvocableSysCall) { - if let Some(injection_type @ SysCallInjectionType::None) = self.0.get_mut(&name) { - *injection_type = SysCallInjectionType::Import; + pub(crate) fn enable_syscall_import(&mut self, name: InvocableSyscall) { + if let Some(injection_type @ SyscallInjectionType::None) = self.0.get_mut(&name) { + *injection_type = SyscallInjectionType::Import; } } - /// Same as [`SysCallsInjectionTypes::set`], but sets amount ranges for multiple syscalls. + /// Same as [`SyscallsInjectionTypes::set`], but sets amount ranges for multiple syscalls. pub fn set_multiple( &mut self, - syscalls_freqs: impl Iterator)>, + syscalls_freqs: impl Iterator)>, ) { for (name, range) in syscalls_freqs { let (min, max) = range.into_inner(); @@ -128,7 +128,7 @@ impl SysCallsInjectionTypes { } } -impl Default for SysCallsInjectionTypes { +impl Default for SyscallsInjectionTypes { fn default() -> Self { Self::all_once() } diff --git a/utils/wasm-gen/src/config/syscalls/param.rs b/utils/wasm-gen/src/config/syscalls/param.rs index 72782de0a86..f1bf90f1955 100644 --- a/utils/wasm-gen/src/config/syscalls/param.rs +++ b/utils/wasm-gen/src/config/syscalls/param.rs @@ -18,7 +18,7 @@ //! Entities describing syscall param, more precisely, it's allowed values. //! -//! Types here are used to create [`crate::SysCallsConfig`]. +//! Types here are used to create [`crate::SyscallsConfig`]. use crate::DEFAULT_INITIAL_SIZE; use arbitrary::{Result, Unstructured}; @@ -41,23 +41,23 @@ pub use gear_wasm_instrument::syscalls::ParamType; /// - [`ParamType::Size`] will be ignored when it means length of some in-memory /// array. #[derive(Debug, Clone)] -pub struct SysCallsParamsConfig(HashMap); +pub struct SyscallsParamsConfig(HashMap); -impl SysCallsParamsConfig { +impl SyscallsParamsConfig { pub fn empty() -> Self { Self(HashMap::new()) } - /// New [`SysCallsParamsConfig`] with all rules set to produce one constant value. + /// New [`SyscallsParamsConfig`] with all rules set to produce one constant value. pub fn all_constant_value(value: i64) -> Self { - let allowed_values: SysCallParamAllowedValues = (value..=value).into(); + let allowed_values: SyscallParamAllowedValues = (value..=value).into(); Self( [ - ParamType::Size, + ParamType::Length, ParamType::Gas, - ParamType::MessagePosition, - ParamType::Duration, - ParamType::Delay, + ParamType::Offset, + ParamType::DurationBlockNumber, + ParamType::DelayBlockNumber, ParamType::Handler, ParamType::Free, ParamType::Version, @@ -69,32 +69,32 @@ impl SysCallsParamsConfig { } /// Get allowed values for the `param`. - pub fn get_rule(&self, param: &ParamType) -> Option { + pub fn get_rule(&self, param: &ParamType) -> Option { self.0.get(param).cloned() } /// Set allowed values for the `param`. - pub fn add_rule(&mut self, param: ParamType, allowed_values: SysCallParamAllowedValues) { + pub fn add_rule(&mut self, param: ParamType, allowed_values: SyscallParamAllowedValues) { matches!(param, ParamType::Ptr(..)) - .then(|| panic!("ParamType::Ptr(..) isn't supported in SysCallsParamsConfig")); + .then(|| panic!("ParamType::Ptr(..) isn't supported in SyscallsParamsConfig")); self.0.insert(param, allowed_values); } } -impl Default for SysCallsParamsConfig { +impl Default for SyscallsParamsConfig { fn default() -> Self { let free_start = DEFAULT_INITIAL_SIZE as i64; let free_end = free_start + 5; Self( [ - (ParamType::Size, (0..=0x10000).into()), + (ParamType::Length, (0..=0x10000).into()), // There are no rules for memory arrays and pointers as they are chosen // in accordance to memory pages config. (ParamType::Gas, (0..=250_000_000_000).into()), - (ParamType::MessagePosition, (0..=10).into()), - (ParamType::Duration, (1..=8).into()), - (ParamType::Delay, (0..=4).into()), + (ParamType::Offset, (0..=10).into()), + (ParamType::DurationBlockNumber, (1..=8).into()), + (ParamType::DelayBlockNumber, (0..=4).into()), (ParamType::Handler, (0..=100).into()), (ParamType::Free, (free_start..=free_end).into()), (ParamType::Version, (1..=1).into()), @@ -107,15 +107,15 @@ impl Default for SysCallsParamsConfig { /// Range of allowed values for the syscall param. #[derive(Debug, Clone)] -pub struct SysCallParamAllowedValues(RangeInclusive); +pub struct SyscallParamAllowedValues(RangeInclusive); -impl From> for SysCallParamAllowedValues { +impl From> for SyscallParamAllowedValues { fn from(range: RangeInclusive) -> Self { Self(range) } } -impl SysCallParamAllowedValues { +impl SyscallParamAllowedValues { /// Zero param value. /// /// That means that for particular param `0` will be always @@ -133,13 +133,13 @@ impl SysCallParamAllowedValues { } } -impl Default for SysCallParamAllowedValues { +impl Default for SyscallParamAllowedValues { fn default() -> Self { Self::zero() } } -impl SysCallParamAllowedValues { +impl SyscallParamAllowedValues { /// Get i32 value for the param from it's allowed range. pub fn get_i32(&self, unstructured: &mut Unstructured) -> Result { let current_range_start = *self.0.start(); diff --git a/utils/wasm-gen/src/config/syscalls/precise.rs b/utils/wasm-gen/src/config/syscalls/precise.rs index a207af80f03..914e8a1e7ed 100644 --- a/utils/wasm-gen/src/config/syscalls/precise.rs +++ b/utils/wasm-gen/src/config/syscalls/precise.rs @@ -23,15 +23,20 @@ use std::ops::RangeInclusive; /// Represents the configuration for building some parts of precise syscalls. /// Can be used to write unit tests so you don't have to rely on randomness. #[derive(Debug, Clone)] -pub struct PreciseSysCallsConfig { +pub struct PreciseSyscallsConfig { range_of_send_push_calls: RangeInclusive, + range_of_send_input_calls: RangeInclusive, } -impl PreciseSysCallsConfig { +impl PreciseSyscallsConfig { /// Creates a new configuration for precise syscalls, filled with the given values. - pub fn new(range_of_send_push_calls: RangeInclusive) -> Self { + pub fn new( + range_of_send_push_calls: RangeInclusive, + range_of_send_input_calls: RangeInclusive, + ) -> Self { Self { range_of_send_push_calls, + range_of_send_input_calls, } } @@ -39,10 +44,15 @@ impl PreciseSysCallsConfig { pub fn range_of_send_push_calls(&self) -> RangeInclusive { self.range_of_send_push_calls.clone() } + + /// Get the range of `send_input*` syscalls. + pub fn range_of_send_input_calls(&self) -> RangeInclusive { + self.range_of_send_input_calls.clone() + } } -impl Default for PreciseSysCallsConfig { +impl Default for PreciseSyscallsConfig { fn default() -> Self { - Self::new(0..=3) + Self::new(0..=3, 1..=1) } } diff --git a/utils/wasm-gen/src/generator.rs b/utils/wasm-gen/src/generator.rs index 5277a5fb22e..dda2eb3b285 100644 --- a/utils/wasm-gen/src/generator.rs +++ b/utils/wasm-gen/src/generator.rs @@ -139,6 +139,16 @@ impl<'a, 'b> GearWasmGenerator<'a, 'b> { .into_wasm_module() .into_inner(); + let module = if let Some(critical_gas_limit) = config.critical_gas_limit { + log::trace!("Injecting critical gas limit"); + utils::inject_critical_gas_limit(module, critical_gas_limit) + } else { + log::trace!("Critical gas limit is not set"); + module + }; + + let module = utils::inject_stack_limiter(module); + Ok(if config.remove_recursions { log::trace!("Removing recursions"); utils::remove_recursion(module) @@ -183,16 +193,16 @@ impl<'a, 'b> GearWasmGenerator<'a, 'b> { self, mem_import_gen_proof: MemoryImportGenerationProof, ep_gen_proof: GearEntryPointGenerationProof, - ) -> Result<(DisabledSysCallsInvocator, FrozenGearWasmGenerator<'a, 'b>)> { + ) -> Result<(DisabledSyscallsInvocator, FrozenGearWasmGenerator<'a, 'b>)> { let syscalls_imports_gen_instantiator = - SysCallsImportsGeneratorInstantiator::from((self, mem_import_gen_proof, ep_gen_proof)); + SyscallsImportsGeneratorInstantiator::from((self, mem_import_gen_proof, ep_gen_proof)); let (syscalls_imports_gen, frozen_gear_wasm_gen) = syscalls_imports_gen_instantiator.into(); let syscalls_imports_gen_res = syscalls_imports_gen.generate()?; let ad_injector = AdditionalDataInjector::from(syscalls_imports_gen_res); let data_injection_res = ad_injector.inject(); - let syscalls_invocator = SysCallsInvocator::from(data_injection_res); + let syscalls_invocator = SyscallsInvocator::from(data_injection_res); let disabled_syscalls_invocator = syscalls_invocator.insert_invokes()?; Ok((disabled_syscalls_invocator, frozen_gear_wasm_gen)) @@ -217,7 +227,7 @@ struct CallIndexes { /// These are indexes of functions which aren't generated from /// `wasm-smith` but from the current crate generators. All gear /// entry points ([`EntryPointsGenerator`]) and custom precise syscalls - /// (generated in [`SysCallsImportsGenerator`]) are considered to be + /// (generated in [`SyscallsImportsGenerator`]) are considered to be /// "custom" functions. /// /// Separating "pre-defined" functions from newly generated ones is important diff --git a/utils/wasm-gen/src/generator/syscalls.rs b/utils/wasm-gen/src/generator/syscalls.rs index ac66b948a63..d6cf7979352 100644 --- a/utils/wasm-gen/src/generator/syscalls.rs +++ b/utils/wasm-gen/src/generator/syscalls.rs @@ -21,15 +21,15 @@ //! Generators from this module form a state machine: //! ```text //! # Zero syscalls generators nesting level. -//! SysCallsImport--->DisabledSysCallsImport--->ModuleWithCallIndexes--->WasmModule +//! SyscallsImport--->DisabledSyscallsImport--->ModuleWithCallIndexes--->WasmModule //! //! # First syscalls generators nesting level. -//! SysCallsImport--->DisabledSysCallsImport--(SysCallsImportsGenerationProof)-->AdditionalDataInjector---\ +//! SyscallsImport--->DisabledSyscallsImport--(SyscallsImportsGenerationProof)-->AdditionalDataInjector---\ //! |--->DisabledAdditionalDataInjector--->ModuleWithCallIndexes--->WasmModule //! //! # Third syscalls generators nesting level -//! SysCallsImport--->DisabledSysCallsImport--(SysCallsImportsGenerationProof)-->AdditionalDataInjector---\ -//! |--->DisabledAdditionalDataInjector--(AddressesInjectionOutcome)-->SysCallsInvocator--->DisabledSysCallsInvocator--->ModuleWithCallIndexes--->WasmModule +//! SyscallsImport--->DisabledSyscallsImport--(SyscallsImportsGenerationProof)-->AdditionalDataInjector---\ +//! |--->DisabledAdditionalDataInjector--(AddressesInjectionOutcome)-->SyscallsInvocator--->DisabledSyscallsInvocator--->ModuleWithCallIndexes--->WasmModule //! ``` //! Entities in curly brackets are those, which are required for the next transition. //! Also all transitions require previous entity to be disabled. @@ -43,7 +43,7 @@ pub use imports::*; pub use invocator::*; use gear_wasm_instrument::syscalls::{ - HashType, ParamType, PtrInfo, PtrType, SysCallName, SysCallSignature, + HashType, ParamType, PtrInfo, PtrType, SyscallName, SyscallSignature, }; /// Type of invocable syscall. @@ -57,30 +57,31 @@ use gear_wasm_instrument::syscalls::{ /// is created from scratch - first `gr_reserve_gas` is called and then it's result /// is used for the further `gr_reservation_send` call. Those are `Precise` syscalls. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum InvocableSysCall { - Loose(SysCallName), - Precise(SysCallName), +pub enum InvocableSyscall { + Loose(SyscallName), + Precise(SyscallName), } -impl InvocableSysCall { +impl InvocableSyscall { pub(crate) fn to_str(self) -> &'static str { match self { - InvocableSysCall::Loose(syscall) => syscall.to_str(), - InvocableSysCall::Precise(syscall) => match syscall { - SysCallName::ReservationSend => "precise_gr_reservation_send", - SysCallName::ReservationReply => "precise_gr_reservation_reply", - SysCallName::SendCommit => "precise_gr_send_commit", - SysCallName::SendCommitWGas => "precise_gr_send_commit_wgas", + InvocableSyscall::Loose(syscall) => syscall.to_str(), + InvocableSyscall::Precise(syscall) => match syscall { + SyscallName::ReservationSend => "precise_gr_reservation_send", + SyscallName::ReservationReply => "precise_gr_reservation_reply", + SyscallName::SendCommit => "precise_gr_send_commit", + SyscallName::SendCommitWGas => "precise_gr_send_commit_wgas", + SyscallName::ReplyDeposit => "precise_gr_reply_deposit", _ => unimplemented!(), }, } } - fn into_signature(self) -> SysCallSignature { + fn into_signature(self) -> SyscallSignature { match self { - InvocableSysCall::Loose(name) => name.signature(), - InvocableSysCall::Precise(name) => match name { - SysCallName::ReservationSend => SysCallSignature::gr([ + InvocableSyscall::Loose(name) => name.signature(), + InvocableSyscall::Precise(name) => match name { + SyscallName::ReservationSend => SyscallSignature::gr([ // Address of recipient and value (HashWithValue struct) ParamType::Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, @@ -89,38 +90,38 @@ impl InvocableSysCall { ParamType::Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - // Size of the payload - ParamType::Size, + // Length of the payload + ParamType::Length, // Number of blocks to delay the sending for - ParamType::Delay, + ParamType::DelayBlockNumber, // Amount of gas to reserve ParamType::Gas, // Duration of the reservation - ParamType::Duration, + ParamType::DurationBlockNumber, // Address of error returned ParamType::Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - SysCallName::ReservationReply => SysCallSignature::gr([ + SyscallName::ReservationReply => SyscallSignature::gr([ // Address of value ParamType::Ptr(PtrInfo::new_immutable(PtrType::Value)), // Pointer to payload ParamType::Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - // Size of the payload - ParamType::Size, + // Length of the payload + ParamType::Length, // Amount of gas to reserve ParamType::Gas, // Duration of the reservation - ParamType::Duration, + ParamType::DurationBlockNumber, // Address of error returned ParamType::Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - SysCallName::SendCommit => SysCallSignature::gr([ + SyscallName::SendCommit => SyscallSignature::gr([ // Address of recipient and value (HashWithValue struct) ParamType::Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, @@ -129,37 +130,58 @@ impl InvocableSysCall { ParamType::Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - // Size of the payload - ParamType::Size, + // Length of the payload + ParamType::Length, // Number of blocks to delay the sending for - ParamType::Delay, + ParamType::DelayBlockNumber, // Address of error returned, `ErrorCode` here because underlying syscalls have different error types ParamType::Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), - SysCallName::SendCommitWGas => SysCallSignature::gr([ + SyscallName::SendCommitWGas => SyscallSignature::gr([ // Address of recipient and value (HashWithValue struct) ParamType::Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), // Number of blocks to delay the sending for - ParamType::Delay, + ParamType::DelayBlockNumber, // Amount of gas to reserve ParamType::Gas, // Address of error returned, `ErrorCode` here because underlying syscalls have different error types ParamType::Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), + SyscallName::ReplyDeposit => SyscallSignature::gr([ + // Address of recipient and value (HashWithValue struct). That's needed + // because first `gr_send_input` is invoked and resulting message id is + // used as an input to `gr_reply_deposit`. + ParamType::Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( + HashType::ActorId, + ))), + // An offset defining starting index in the received payload (related to `gr_send_input`). + ParamType::Offset, + // Length of the slice of the received message payload (related to `gr_send_input`). + ParamType::Length, + // Delay (related to `gr_send_input`). + ParamType::DelayBlockNumber, + // Amount of gas deposited for a message id got from `gr_send_input`. + // That's an actual input for `gr_reply_deposit` + ParamType::Gas, + // Error pointer + ParamType::Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( + HashType::MessageId, + ))), + ]), _ => unimplemented!(), }, } } /// Checks whether given syscall has the precise variant. - pub(crate) fn has_precise_variant(syscall: SysCallName) -> bool { + pub(crate) fn has_precise_variant(syscall: SyscallName) -> bool { Self::required_imports_for_syscall(syscall).is_some() } /// Returns the required imports to build precise syscall, but of a fixed size. - fn required_imports(syscall: SysCallName) -> &'static [SysCallName; N] { + fn required_imports(syscall: SyscallName) -> &'static [SyscallName; N] { Self::required_imports_for_syscall(syscall) .expect("failed to find required imports for syscall") .try_into() @@ -168,109 +190,58 @@ impl InvocableSysCall { /// Returns the required imports to build precise syscall. pub(crate) fn required_imports_for_syscall( - syscall: SysCallName, - ) -> Option<&'static [SysCallName]> { + syscall: SyscallName, + ) -> Option<&'static [SyscallName]> { // NOTE: the last syscall must be pattern itself Some(match syscall { - SysCallName::ReservationSend => { - &[SysCallName::ReserveGas, SysCallName::ReservationSend] + SyscallName::ReservationSend => { + &[SyscallName::ReserveGas, SyscallName::ReservationSend] } - SysCallName::ReservationReply => { - &[SysCallName::ReserveGas, SysCallName::ReservationReply] + SyscallName::ReservationReply => { + &[SyscallName::ReserveGas, SyscallName::ReservationReply] } - SysCallName::SendCommit => &[ - SysCallName::SendInit, - SysCallName::SendPush, - SysCallName::SendCommit, + SyscallName::SendCommit => &[ + SyscallName::SendInit, + SyscallName::SendPush, + SyscallName::SendCommit, ], - SysCallName::SendCommitWGas => &[ - SysCallName::Size, - SysCallName::SendInit, - SysCallName::SendPushInput, - SysCallName::SendCommitWGas, + SyscallName::SendCommitWGas => &[ + SyscallName::Size, + SyscallName::SendInit, + SyscallName::SendPushInput, + SyscallName::SendCommitWGas, ], + SyscallName::ReplyDeposit => &[SyscallName::SendInput, SyscallName::ReplyDeposit], _ => return None, }) } - /// Returns the index of the destination param. - fn has_destination_param(&self) -> Option { - use InvocableSysCall::*; - use SysCallName::*; + /// Returns the index of the destination param if a syscall has it. + fn destination_param_idx(&self) -> Option { + use InvocableSyscall::*; + use SyscallName::*; match *self { Loose(Send | SendWGas | SendInput | SendInputWGas | Exit) - | Precise(ReservationSend | SendCommit | SendCommitWGas) => Some(0), + | Precise(ReservationSend | SendCommit | SendCommitWGas | ReplyDeposit) => Some(0), Loose(SendCommit | SendCommitWGas) => Some(1), _ => None, } } + /// Returns `true` for every syscall which has a destination param idx and that is not `gr_exit` syscall, + /// as it only has destination param. + fn has_destination_param_with_value(&self) -> bool { + self.destination_param_idx().is_some() + && !matches!(self, InvocableSyscall::Loose(SyscallName::Exit)) + } + // If syscall changes from fallible into infallible or vice versa in future, // we'll see it by analyzing code coverage stats produced by fuzzer. pub(crate) fn is_fallible(&self) -> bool { - let underlying_syscall = match *self { - Self::Loose(sc) => sc, - Self::Precise(sc) => sc, - }; - - match underlying_syscall { - SysCallName::EnvVars - | SysCallName::BlockHeight - | SysCallName::BlockTimestamp - | SysCallName::Debug - | SysCallName::Panic - | SysCallName::OomPanic - | SysCallName::Exit - | SysCallName::GasAvailable - | SysCallName::Leave - | SysCallName::MessageId - | SysCallName::ProgramId - | SysCallName::Random - | SysCallName::Size - | SysCallName::Source - | SysCallName::ValueAvailable - | SysCallName::Value - | SysCallName::WaitFor - | SysCallName::WaitUpTo - | SysCallName::Wait - | SysCallName::Alloc - | SysCallName::Free - | SysCallName::OutOfGas => false, - SysCallName::CreateProgramWGas - | SysCallName::CreateProgram - | SysCallName::ReplyDeposit - | SysCallName::ReplyCode - | SysCallName::SignalCode - | SysCallName::PayProgramRent - | SysCallName::Read - | SysCallName::ReplyCommitWGas - | SysCallName::ReplyCommit - | SysCallName::ReplyPush - | SysCallName::ReplyPushInput - | SysCallName::ReplyTo - | SysCallName::SignalFrom - | SysCallName::ReplyInputWGas - | SysCallName::ReplyWGas - | SysCallName::Reply - | SysCallName::ReplyInput - | SysCallName::ReservationReplyCommit - | SysCallName::ReservationReply - | SysCallName::ReservationSendCommit - | SysCallName::ReservationSend - | SysCallName::ReserveGas - | SysCallName::SendCommitWGas - | SysCallName::SendCommit - | SysCallName::SendInit - | SysCallName::SendPush - | SysCallName::SendPushInput - | SysCallName::SendInputWGas - | SysCallName::SendWGas - | SysCallName::Send - | SysCallName::SendInput - | SysCallName::SystemReserveGas - | SysCallName::UnreserveGas - | SysCallName::Wake => true, + match self { + InvocableSyscall::Loose(syscall) => syscall.is_fallible(), + InvocableSyscall::Precise(syscall) => syscall.is_fallible(), } } } diff --git a/utils/wasm-gen/src/generator/syscalls/additional_data.rs b/utils/wasm-gen/src/generator/syscalls/additional_data.rs index 05779481ebc..3c26bda8d5f 100644 --- a/utils/wasm-gen/src/generator/syscalls/additional_data.rs +++ b/utils/wasm-gen/src/generator/syscalls/additional_data.rs @@ -20,16 +20,16 @@ use crate::{ generator::{ - CallIndexes, CallIndexesHandle, DisabledSysCallsImportsGenerator, ModuleWithCallIndexes, - SysCallsImportsGenerationProof, + CallIndexes, CallIndexesHandle, DisabledSyscallsImportsGenerator, ModuleWithCallIndexes, + SyscallsImportsGenerationProof, }, - utils, EntryPointName, InvocableSysCall, SysCallDestination, SysCallsConfig, WasmModule, + utils, EntryPointName, InvocableSyscall, SyscallDestination, SyscallsConfig, WasmModule, }; use arbitrary::Unstructured; use gear_core::ids::ProgramId; use gear_wasm_instrument::{ parity_wasm::{builder, elements::Instruction}, - syscalls::SysCallName, + syscalls::SyscallName, }; use std::{collections::BTreeMap, iter::Cycle, num::NonZeroU32, vec::IntoIter}; @@ -40,7 +40,7 @@ use std::{collections::BTreeMap, iter::Cycle, num::NonZeroU32, vec::IntoIter}; /// /// By implementation this type is not instantiated, when no /// data is set to the wasm module. More precisely, if no -/// additional data was set to [`SysCallsConfig`]. +/// additional data was set to [`SyscallsConfig`]. pub struct AddressesOffsets(Cycle>); impl AddressesOffsets { @@ -55,30 +55,30 @@ impl AddressesOffsets { /// Additional data injector. /// /// Injects some additional data from provided config to wasm module data section. -/// The config, which contains additional data types and values is received from [`DisabledSysCallsImportsGenerator`]. +/// The config, which contains additional data types and values is received from [`DisabledSyscallsImportsGenerator`]. /// -/// The generator is instantiated only with having [`SysCallsImportsGenerationProof`], which gives a guarantee. that +/// The generator is instantiated only with having [`SyscallsImportsGenerationProof`], which gives a guarantee. that /// if log info should be injected, than `gr_debug` syscall import is generated. pub struct AdditionalDataInjector<'a, 'b> { unstructured: &'b mut Unstructured<'a>, call_indexes: CallIndexes, - config: SysCallsConfig, + config: SyscallsConfig, last_offset: u32, module: WasmModule, addresses_offsets: Vec, - syscalls_imports: BTreeMap, CallIndexesHandle)>, + syscalls_imports: BTreeMap, CallIndexesHandle)>, } impl<'a, 'b> From<( - DisabledSysCallsImportsGenerator<'a, 'b>, - SysCallsImportsGenerationProof, + DisabledSyscallsImportsGenerator<'a, 'b>, + SyscallsImportsGenerationProof, )> for AdditionalDataInjector<'a, 'b> { fn from( (disabled_gen, _syscalls_gen_proof): ( - DisabledSysCallsImportsGenerator<'a, 'b>, - SysCallsImportsGenerationProof, + DisabledSyscallsImportsGenerator<'a, 'b>, + SyscallsImportsGenerationProof, ), ) -> Self { let data_offset = disabled_gen @@ -140,7 +140,7 @@ impl<'a, 'b> AdditionalDataInjector<'a, 'b> { )); } - let SysCallDestination::ExistingAddresses(existing_addresses) = + let SyscallDestination::ExistingAddresses(existing_addresses) = self.config.syscall_destination() else { return None; @@ -210,13 +210,13 @@ impl<'a, 'b> AdditionalDataInjector<'a, 'b> { log::trace!("Info will be logged in handle_reply"); self.module.gear_entry_point(EntryPointName::HandleReply) }) - // This generator is instantiated from SysCallsImportsGenerator, which can only be + // This generator is instantiated from SyscallsImportsGenerator, which can only be // generated if entry points and memory import were generated. .expect("impossible to have no gear export"); let debug_call_indexes_handle = self .syscalls_imports - .get(&InvocableSysCall::Loose(SysCallName::Debug)) + .get(&InvocableSyscall::Loose(SyscallName::Debug)) .map(|&(_, handle)| handle as u32) .expect("impossible by configs generation to have log info printing without debug syscall generated"); @@ -260,7 +260,7 @@ impl<'a, 'b> AdditionalDataInjector<'a, 'b> { /// Basically this type just carries inserted into data section /// addresses offsets. /// -/// There's design point of having this type, which is described in [`super::SysCallsInvocator`] docs. +/// There's design point of having this type, which is described in [`super::SyscallsInvocator`] docs. pub struct AddressesInjectionOutcome { pub(super) offsets: Option, } @@ -274,8 +274,8 @@ pub struct DisabledAdditionalDataInjector<'a, 'b> { pub(super) module: WasmModule, pub(super) call_indexes: CallIndexes, pub(super) syscalls_imports: - BTreeMap, CallIndexesHandle)>, - pub(super) config: SysCallsConfig, + BTreeMap, CallIndexesHandle)>, + pub(super) config: SyscallsConfig, } impl<'a, 'b> From> for ModuleWithCallIndexes { diff --git a/utils/wasm-gen/src/generator/syscalls/imports.rs b/utils/wasm-gen/src/generator/syscalls/imports.rs index 2f0cf38b8b3..db2afe973b8 100644 --- a/utils/wasm-gen/src/generator/syscalls/imports.rs +++ b/utils/wasm-gen/src/generator/syscalls/imports.rs @@ -24,7 +24,7 @@ use crate::{ GearWasmGenerator, MemoryImportGenerationProof, ModuleWithCallIndexes, }, wasm::{PageCount as WasmPageCount, WasmModule}, - InvocableSysCall, SysCallInjectionType, SysCallsConfig, + InvocableSyscall, SyscallInjectionType, SyscallsConfig, }; use arbitrary::{Error as ArbitraryError, Result, Unstructured}; use gear_wasm_instrument::{ @@ -32,24 +32,24 @@ use gear_wasm_instrument::{ builder, elements::{BlockType, Instruction, Instructions}, }, - syscalls::SysCallName, + syscalls::SyscallName, }; use gsys::{Handle, Hash, Length}; use std::{collections::BTreeMap, mem, num::NonZeroU32}; /// Gear syscalls imports generator. -pub struct SysCallsImportsGenerator<'a, 'b> { +pub struct SyscallsImportsGenerator<'a, 'b> { unstructured: &'b mut Unstructured<'a>, call_indexes: CallIndexes, module: WasmModule, - config: SysCallsConfig, - syscalls_imports: BTreeMap, CallIndexesHandle)>, + config: SyscallsConfig, + syscalls_imports: BTreeMap, CallIndexesHandle)>, } /// Syscalls imports generator instantiator. /// /// Serves as a new type in order to create the generator from gear wasm generator and proofs. -pub struct SysCallsImportsGeneratorInstantiator<'a, 'b>( +pub struct SyscallsImportsGeneratorInstantiator<'a, 'b>( ( GearWasmGenerator<'a, 'b>, MemoryImportGenerationProof, @@ -60,13 +60,13 @@ pub struct SysCallsImportsGeneratorInstantiator<'a, 'b>( /// The set of syscalls that need to be imported to create precise syscall. #[derive(thiserror::Error, Debug)] #[error("The following syscalls must be imported: {0:?}")] -pub struct RequiredSysCalls(&'static [SysCallName]); +pub struct RequiredSyscalls(&'static [SyscallName]); /// An error that occurs when generating precise syscall. #[derive(thiserror::Error, Debug)] -pub enum PreciseSysCallError { +pub enum PreciseSyscallError { #[error("{0}")] - RequiredImports(#[from] RequiredSysCalls), + RequiredImports(#[from] RequiredSyscalls), #[error("{0}")] Arbitrary(#[from] ArbitraryError), } @@ -76,7 +76,7 @@ impl<'a, 'b> GearWasmGenerator<'a, 'b>, MemoryImportGenerationProof, GearEntryPointGenerationProof, - )> for SysCallsImportsGeneratorInstantiator<'a, 'b> + )> for SyscallsImportsGeneratorInstantiator<'a, 'b> { fn from( inner: ( @@ -89,19 +89,19 @@ impl<'a, 'b> } } -impl<'a, 'b> From> +impl<'a, 'b> From> for ( - SysCallsImportsGenerator<'a, 'b>, + SyscallsImportsGenerator<'a, 'b>, FrozenGearWasmGenerator<'a, 'b>, ) { - fn from(instantiator: SysCallsImportsGeneratorInstantiator<'a, 'b>) -> Self { - let SysCallsImportsGeneratorInstantiator(( + fn from(instantiator: SyscallsImportsGeneratorInstantiator<'a, 'b>) -> Self { + let SyscallsImportsGeneratorInstantiator(( generator, _mem_import_gen_proof, _gen_ep_gen_proof, )) = instantiator; - let syscall_gen = SysCallsImportsGenerator { + let syscall_gen = SyscallsImportsGenerator { unstructured: generator.unstructured, call_indexes: generator.call_indexes, module: generator.module, @@ -118,14 +118,14 @@ impl<'a, 'b> From> } } -impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { +impl<'a, 'b> SyscallsImportsGenerator<'a, 'b> { /// Instantiate a new gear syscalls imports generator. /// /// The generator instantiations requires having type-level proof that the wasm module has memory import in it. /// This proof could be gotten from memory generator. pub fn new( module_with_indexes: ModuleWithCallIndexes, - config: SysCallsConfig, + config: SyscallsConfig, unstructured: &'b mut Unstructured<'a>, _mem_import_gen_proof: MemoryImportGenerationProof, _gen_ep_gen_proof: GearEntryPointGenerationProof, @@ -145,12 +145,12 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { } /// Disable current generator. - pub fn disable(self) -> DisabledSysCallsImportsGenerator<'a, 'b> { + pub fn disable(self) -> DisabledSyscallsImportsGenerator<'a, 'b> { log::trace!( "Random data when disabling syscalls imports generator - {}", self.unstructured.len() ); - DisabledSysCallsImportsGenerator { + DisabledSyscallsImportsGenerator { unstructured: self.unstructured, call_indexes: self.call_indexes, module: self.module, @@ -166,8 +166,8 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { pub fn generate( mut self, ) -> Result<( - DisabledSysCallsImportsGenerator<'a, 'b>, - SysCallsImportsGenerationProof, + DisabledSyscallsImportsGenerator<'a, 'b>, + SyscallsImportsGenerationProof, )> { log::trace!("Generating syscalls imports"); @@ -178,48 +178,49 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { } /// Generates syscalls imports from config, used to instantiate the generator. - pub fn generate_syscalls_imports(&mut self) -> Result { + pub fn generate_syscalls_imports(&mut self) -> Result { log::trace!( "Random data before syscalls imports - {}", self.unstructured.len() ); - for syscall in SysCallName::instrumentable() { + for syscall in SyscallName::instrumentable() { let syscall_generation_data = self.generate_syscall_import(syscall)?; if let Some(syscall_generation_data) = syscall_generation_data { self.syscalls_imports - .insert(InvocableSysCall::Loose(syscall), syscall_generation_data); + .insert(InvocableSyscall::Loose(syscall), syscall_generation_data); } } - Ok(SysCallsImportsGenerationProof(())) + Ok(SyscallsImportsGenerationProof(())) } /// Generates precise syscalls and handles errors if any occurred during generation. fn generate_precise_syscalls(&mut self) -> Result<()> { - use SysCallName::*; + use SyscallName::*; #[allow(clippy::type_complexity)] let precise_syscalls: [( - SysCallName, - fn(&mut Self, SysCallName) -> Result, - ); 4] = [ + SyscallName, + fn(&mut Self, SyscallName) -> Result, + ); 5] = [ (ReservationSend, Self::generate_send_from_reservation), (ReservationReply, Self::generate_reply_from_reservation), (SendCommit, Self::generate_send_commit), (SendCommitWGas, Self::generate_send_commit_with_gas), + (ReplyDeposit, Self::generate_reply_deposit), ]; for (precise_syscall, generate_method) in precise_syscalls { let syscall_injection_type = self .config - .injection_types(InvocableSysCall::Precise(precise_syscall)); - if let SysCallInjectionType::Function(syscall_amount_range) = syscall_injection_type { + .injection_types(InvocableSyscall::Precise(precise_syscall)); + if let SyscallInjectionType::Function(syscall_amount_range) = syscall_injection_type { let precise_syscall_amount = NonZeroU32::new(self.unstructured.int_in_range(syscall_amount_range)?); log::trace!( "Constructing `{name}` syscall...", - name = InvocableSysCall::Precise(precise_syscall).to_str() + name = InvocableSyscall::Precise(precise_syscall).to_str() ); if precise_syscall_amount.is_none() { @@ -230,11 +231,11 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { match generate_method(self, precise_syscall) { Ok(call_indexes_handle) => { self.syscalls_imports.insert( - InvocableSysCall::Precise(precise_syscall), + InvocableSyscall::Precise(precise_syscall), (precise_syscall_amount, call_indexes_handle), ); } - Err(PreciseSysCallError::RequiredImports(err)) => { + Err(PreciseSyscallError::RequiredImports(err)) => { // By syscalls injection types config all required syscalls for // precise syscalls are set. // By generator's implementation, precise calls are generated after @@ -243,7 +244,7 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { "Invalid generators configuration or implementation: required syscalls aren't set: {err}" ) } - Err(PreciseSysCallError::Arbitrary(err)) => return Err(err), + Err(PreciseSyscallError::Arbitrary(err)) => return Err(err), } } } @@ -260,22 +261,22 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { /// If no import is required, `None` is returned. fn generate_syscall_import( &mut self, - syscall: SysCallName, + syscall: SyscallName, ) -> Result, CallIndexesHandle)>> { let syscall_injection_type = self .config - .injection_types(InvocableSysCall::Loose(syscall)); + .injection_types(InvocableSyscall::Loose(syscall)); let syscall_amount = match syscall_injection_type { - SysCallInjectionType::Import => 0, - SysCallInjectionType::Function(syscall_amount_range) => { + SyscallInjectionType::Import => 0, + SyscallInjectionType::Function(syscall_amount_range) => { self.unstructured.int_in_range(syscall_amount_range)? } _ => return Ok(None), }; - // Insert import either for case of `SysCallInjectionType::Import`, or - // if `SysCallInjectionType::Function(syscall_amount_range)` yielded zero. + // Insert import either for case of `SyscallInjectionType::Import`, or + // if `SyscallInjectionType::Function(syscall_amount_range)` yielded zero. let call_indexes_handle = self.insert_syscall_import(syscall); log::trace!( " -- Syscall `{}` will be invoked {} times", @@ -287,14 +288,14 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { } /// Inserts gear syscall defined by the `syscall` param. - fn insert_syscall_import(&mut self, syscall: SysCallName) -> CallIndexesHandle { + fn insert_syscall_import(&mut self, syscall: SyscallName) -> CallIndexesHandle { let syscall_import_idx = self.module.count_import_funcs(); // Insert syscall import to the module self.module.with(|module| { let mut module_builder = builder::from_module(module); - // Build signature applicable for the parity-wasm for the sys call + // Build signature applicable for the parity-wasm for the syscall let syscall_signature = syscall.signature().func_type(); let signature_idx = module_builder.push_signature( builder::signature() @@ -323,17 +324,17 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { } } -impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { +impl<'a, 'b> SyscallsImportsGenerator<'a, 'b> { /// The amount of memory used to create a precise syscall. - const PRECISE_SYS_CALL_MEMORY_SIZE: u32 = 100; + const PRECISE_SYSCALL_MEMORY_SIZE: u32 = 100; /// Generates a function which calls "properly" the `gr_reservation_send`. fn generate_send_from_reservation( &mut self, - syscall: SysCallName, - ) -> Result { + syscall: SyscallName, + ) -> Result { let [reserve_gas_idx, reservation_send_idx] = - self.invocable_syscalls_indexes(InvocableSysCall::required_imports(syscall))?; + self.invocable_syscalls_indexes(InvocableSyscall::required_imports(syscall))?; // subtract to be sure we are in memory boundaries. let rid_pid_value_ptr = self.reserve_memory(); @@ -421,10 +422,10 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { /// Generates a function which calls "properly" the `gr_reservation_reply`. fn generate_reply_from_reservation( &mut self, - syscall: SysCallName, - ) -> Result { + syscall: SyscallName, + ) -> Result { let [reserve_gas_idx, reservation_reply_idx] = - self.invocable_syscalls_indexes(InvocableSysCall::required_imports(syscall))?; + self.invocable_syscalls_indexes(InvocableSyscall::required_imports(syscall))?; // subtract to be sure we are in memory boundaries. let rid_value_ptr = self.reserve_memory(); @@ -493,10 +494,10 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { /// Generates a function which calls "properly" the `gr_send_commit`. fn generate_send_commit( &mut self, - syscall: SysCallName, - ) -> Result { + syscall: SyscallName, + ) -> Result { let [send_init_idx, send_push_idx, send_commit_idx] = - self.invocable_syscalls_indexes(InvocableSysCall::required_imports(syscall))?; + self.invocable_syscalls_indexes(InvocableSyscall::required_imports(syscall))?; // subtract to be sure we are in memory boundaries. let handle_ptr = self.reserve_memory(); @@ -592,10 +593,10 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { /// Generates a function which calls "properly" the `gr_send_commit_wgas`. fn generate_send_commit_with_gas( &mut self, - syscall: SysCallName, - ) -> Result { + syscall: SyscallName, + ) -> Result { let [size_idx, send_init_idx, send_push_input_idx, send_commit_wgas_idx] = - self.invocable_syscalls_indexes(InvocableSysCall::required_imports(syscall))?; + self.invocable_syscalls_indexes(InvocableSyscall::required_imports(syscall))?; // subtract to be sure we are in memory boundaries. let handle_ptr = self.reserve_memory(); @@ -695,19 +696,106 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { Ok(call_indexes_handle) } + fn generate_reply_deposit( + &mut self, + syscall: SyscallName, + ) -> Result { + let [send_input_idx, reply_deposit_idx] = + self.invocable_syscalls_indexes(InvocableSyscall::required_imports(syscall))?; + + let mid_ptr = self.reserve_memory(); + + let precise_reply_deposit_invocation = [ + // Pointer to pid_value argument of HashWithValue type. + Instruction::GetLocal(0), + // Offset value defining starting index in the received message payload. + Instruction::GetLocal(1), + // Length of the slice of the received message payload. + Instruction::GetLocal(2), + // Delay. + Instruction::GetLocal(3), + // Pointer to the result of the `gr_send_input`, which is of type ErrorWithHash. + Instruction::GetLocal(5), + // Invocation of the `gr_send_input`. + Instruction::Call(send_input_idx as u32), + // Load ErrorWithHash. + Instruction::GetLocal(5), + // Take first 4 bytes from the data of ErrorWithHash type, which is error code, i.e. + // ErrorWithHash.error. + Instruction::I32Load(2, 0), + // Check if ErrorWithHash.error == 0. + Instruction::I32Eqz, + // If ErrorWithHash.error == 0. + Instruction::If(BlockType::NoResult), + // Copy Hash struct (32 bytes) containing message id. + // Push on stack ptr to address where message id will be defined. + Instruction::I32Const(mid_ptr), + // Get the ErrorWithHash result of the `gr_send_input` call + Instruction::GetLocal(5), + // Load 8 bytes from the ErrorWithHash skipping first 4 bytes, + // which are bytes of i32 error_code value. + Instruction::I64Load(3, 4), + // Store these 8 bytes in the `mid_ptr` starting from the byte 0. + Instruction::I64Store(3, 0), + // Perform same procedure 3 times more to complete + // 32 bytes message id value under `mid_ptr`. + Instruction::I32Const(mid_ptr), + Instruction::GetLocal(5), + Instruction::I64Load(3, 12), + Instruction::I64Store(3, 8), + Instruction::I32Const(mid_ptr), + Instruction::GetLocal(5), + Instruction::I64Load(3, 20), + Instruction::I64Store(3, 16), + Instruction::I32Const(mid_ptr), + Instruction::GetLocal(5), + Instruction::I64Load(3, 28), + Instruction::I64Store(3, 24), + // Pointer to message id. + Instruction::I32Const(mid_ptr), + // Pointer to gas value for `gr_reply_deposit`. + Instruction::GetLocal(4), + // Pointer to the result of the `gr_reply_deposit`. + Instruction::GetLocal(5), + // Invocation of `gr_reply_deposit`. + Instruction::Call(reply_deposit_idx as u32), + Instruction::End, + ]; + + let invocations_amount = self.unstructured.int_in_range( + self.config + .precise_syscalls_config() + .range_of_send_input_calls(), + )?; + + // The capacity is amount of times `gr_reply_deposit` is invoked precisely + 1 for `End` instruction. + let mut func_instructions = + Vec::with_capacity(precise_reply_deposit_invocation.len() * invocations_amount + 1); + for _ in 0..invocations_amount { + func_instructions.extend_from_slice(&precise_reply_deposit_invocation); + } + func_instructions.push(Instruction::End); + + let func_instructions = Instructions::new(func_instructions); + let call_indexes_handle = + self.generate_proper_syscall_invocation(syscall, func_instructions); + + Ok(call_indexes_handle) + } + /// Returns the indexes of invocable syscalls. fn invocable_syscalls_indexes( &mut self, - syscalls: &'static [SysCallName; N], - ) -> Result<[usize; N], RequiredSysCalls> { + syscalls: &'static [SyscallName; N], + ) -> Result<[usize; N], RequiredSyscalls> { let mut indexes = [0; N]; for (index, &syscall) in indexes.iter_mut().zip(syscalls.iter()) { *index = self .syscalls_imports - .get(&InvocableSysCall::Loose(syscall)) + .get(&InvocableSyscall::Loose(syscall)) .map(|&(_, call_indexes_handle)| call_indexes_handle) - .ok_or_else(|| RequiredSysCalls(&syscalls[..]))?; + .ok_or_else(|| RequiredSyscalls(&syscalls[..]))?; } Ok(indexes) @@ -716,7 +804,7 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { /// Reserves enough memory build precise syscall. fn reserve_memory(&self) -> i32 { self.memory_size_in_bytes() - .saturating_sub(Self::PRECISE_SYS_CALL_MEMORY_SIZE) as i32 + .saturating_sub(Self::PRECISE_SYSCALL_MEMORY_SIZE) as i32 } /// Returns the size of the memory in bytes that can be used to build precise syscall. @@ -732,10 +820,10 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { /// Generates a function which calls "properly" the given syscall. fn generate_proper_syscall_invocation( &mut self, - syscall: SysCallName, + syscall: SyscallName, func_instructions: Instructions, ) -> CallIndexesHandle { - let invocable_syscall = InvocableSysCall::Precise(syscall); + let invocable_syscall = InvocableSyscall::Precise(syscall); let signature = invocable_syscall.into_signature(); let func_ty = signature.func_type(); @@ -770,24 +858,24 @@ impl<'a, 'b> SysCallsImportsGenerator<'a, 'b> { } } -/// Proof that there was an instance of syscalls imports generator and `SysCallsImportsGenerator::generate_syscalls_imports` was called. -pub struct SysCallsImportsGenerationProof(()); +/// Proof that there was an instance of syscalls imports generator and `SyscallsImportsGenerator::generate_syscalls_imports` was called. +pub struct SyscallsImportsGenerationProof(()); /// Disabled gear wasm syscalls generator. /// /// Instance of this types signals that there was once active syscalls generator, /// but it ended up it's work. -pub struct DisabledSysCallsImportsGenerator<'a, 'b> { +pub struct DisabledSyscallsImportsGenerator<'a, 'b> { pub(super) unstructured: &'b mut Unstructured<'a>, pub(super) call_indexes: CallIndexes, pub(super) module: WasmModule, - pub(super) config: SysCallsConfig, + pub(super) config: SyscallsConfig, pub(super) syscalls_imports: - BTreeMap, CallIndexesHandle)>, + BTreeMap, CallIndexesHandle)>, } -impl<'a, 'b> From> for ModuleWithCallIndexes { - fn from(disabled_syscall_gen: DisabledSysCallsImportsGenerator<'a, 'b>) -> Self { +impl<'a, 'b> From> for ModuleWithCallIndexes { + fn from(disabled_syscall_gen: DisabledSyscallsImportsGenerator<'a, 'b>) -> Self { ModuleWithCallIndexes { module: disabled_syscall_gen.module, call_indexes: disabled_syscall_gen.call_indexes, diff --git a/utils/wasm-gen/src/generator/syscalls/invocator.rs b/utils/wasm-gen/src/generator/syscalls/invocator.rs index 38a5f9d6628..06d0cb1bc4f 100644 --- a/utils/wasm-gen/src/generator/syscalls/invocator.rs +++ b/utils/wasm-gen/src/generator/syscalls/invocator.rs @@ -24,27 +24,28 @@ use crate::{ DisabledAdditionalDataInjector, FunctionIndex, ModuleWithCallIndexes, }, wasm::{PageCount as WasmPageCount, WasmModule}, - InvocableSysCall, SysCallParamAllowedValues, SysCallsConfig, SysCallsParamsConfig, + InvocableSyscall, SyscallParamAllowedValues, SyscallsConfig, SyscallsParamsConfig, }; use arbitrary::{Result, Unstructured}; use gear_wasm_instrument::{ parity_wasm::elements::{BlockType, Instruction, Internal, ValueType}, - syscalls::{ParamType, PtrInfo, PtrType, SysCallName, SysCallSignature}, + syscalls::{ParamType, PtrInfo, PtrType, SyscallName, SyscallSignature}, }; +use gsys::Hash; use std::{ collections::{btree_map::Entry, BTreeMap, BinaryHeap, HashSet}, - iter, + iter, mem, num::NonZeroU32, }; #[derive(Debug)] -pub(crate) enum ProcessedSysCallParams { +pub(crate) enum ProcessedSyscallParams { Alloc { - allowed_values: Option, + allowed_values: Option, }, Value { value_type: ValueType, - allowed_values: Option, + allowed_values: Option, }, MemoryArraySize, MemoryArrayPtr, @@ -53,8 +54,8 @@ pub(crate) enum ProcessedSysCallParams { pub(crate) fn process_syscall_params( params: &[ParamType], - params_config: &SysCallsParamsConfig, -) -> Vec { + params_config: &SyscallsParamsConfig, +) -> Vec { let length_param_indexes = params .iter() .filter_map(|¶m| match param { @@ -69,22 +70,22 @@ pub(crate) fn process_syscall_params( let mut res = Vec::with_capacity(params.len()); for (param_idx, ¶m) in params.iter().enumerate() { let processed_param = match param { - ParamType::Alloc => ProcessedSysCallParams::Alloc { + ParamType::Alloc => ProcessedSyscallParams::Alloc { allowed_values: params_config.get_rule(¶m), }, - ParamType::Size if length_param_indexes.contains(¶m_idx) => { + ParamType::Length if length_param_indexes.contains(¶m_idx) => { // Due to match guard `ParamType::Size` can be processed in two ways: - // 1. The function will return `ProcessedSysCallParams::MemoryArraySize` + // 1. The function will return `ProcessedSyscallParams::MemoryArraySize` // if this parameter is associated with PtrType::BufferStart { .. }`. - // 2. Otherwise, `ProcessedSysCallParams::Value` will be returned from the function. - ProcessedSysCallParams::MemoryArraySize + // 2. Otherwise, `ProcessedSyscallParams::Value` will be returned from the function. + ProcessedSyscallParams::MemoryArraySize } ParamType::Ptr(PtrInfo { ty: PtrType::SizedBufferStart { .. }, .. - }) => ProcessedSysCallParams::MemoryArrayPtr, - ParamType::Ptr(_) => ProcessedSysCallParams::MemoryPtrValue, - _ => ProcessedSysCallParams::Value { + }) => ProcessedSyscallParams::MemoryArrayPtr, + ParamType::Ptr(_) => ProcessedSyscallParams::MemoryPtrValue, + _ => ProcessedSyscallParams::Value { value_type: param.into(), allowed_values: params_config.get_rule(¶m), }, @@ -109,20 +110,20 @@ pub(crate) fn process_syscall_params( /// additional data injector was disabled, before injecting addresses from the config. As a result, /// invocator would set un-intended by config values as messages destination. To avoid such /// inconsistency the [`AddressesInjectionOutcome`] gives additional required guarantees. -pub struct SysCallsInvocator<'a, 'b> { +pub struct SyscallsInvocator<'a, 'b> { unstructured: &'b mut Unstructured<'a>, call_indexes: CallIndexes, module: WasmModule, - config: SysCallsConfig, + config: SyscallsConfig, offsets: Option, - syscalls_imports: BTreeMap, CallIndexesHandle)>, + syscalls_imports: BTreeMap, CallIndexesHandle)>, } impl<'a, 'b> From<( DisabledAdditionalDataInjector<'a, 'b>, AddressesInjectionOutcome, - )> for SysCallsInvocator<'a, 'b> + )> for SyscallsInvocator<'a, 'b> { fn from( (disabled_gen, outcome): ( @@ -175,14 +176,14 @@ impl ParamSetter { } } -pub type SysCallInvokeInstructions = Vec; +pub type SyscallInvokeInstructions = Vec; -impl<'a, 'b> SysCallsInvocator<'a, 'b> { +impl<'a, 'b> SyscallsInvocator<'a, 'b> { /// Insert syscalls invokes. /// /// The method builds instructions, which describe how each syscall is called, and then /// insert these instructions into any random function. In the end, all call indexes are resolved. - pub fn insert_invokes(mut self) -> Result { + pub fn insert_invokes(mut self) -> Result { log::trace!( "Random data before inserting all syscalls invocations - {}", self.unstructured.len() @@ -197,7 +198,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { self.resolves_calls_indexes(); - Ok(DisabledSysCallsInvocator { + Ok(DisabledSyscallsInvocator { module: self.module, call_indexes: self.call_indexes, }) @@ -217,7 +218,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { /// Returns mapping `func_id` <-> `syscalls which should be inserted into func_id`. fn build_syscalls_insertion_mapping( &mut self, - ) -> Result>> { + ) -> Result>> { let insert_into_funcs = self.call_indexes.predefined_funcs_indexes(); let syscalls = self .syscalls_imports @@ -247,7 +248,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { fn insert_syscalls_into_fn( &mut self, insert_into_fn: usize, - syscalls: Vec, + syscalls: Vec, ) -> Result<()> { log::trace!( "Random data before inserting syscalls invoke instructions into function with index {insert_into_fn} - {}", @@ -311,17 +312,17 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { fn build_syscall_invoke_instructions( &mut self, - invocable: InvocableSysCall, - signature: SysCallSignature, + invocable: InvocableSyscall, + signature: SyscallSignature, call_indexes_handle: CallIndexesHandle, - ) -> Result { + ) -> Result { log::trace!( "Random data before building `{}` syscall invoke instructions - {}", invocable.to_str(), self.unstructured.len() ); - if let Some(argument_index) = invocable.has_destination_param() { + if let Some(argument_index) = invocable.destination_param_idx() { log::trace!( " -- Building call instructions for a `{}` syscall with destination", invocable.to_str() @@ -345,8 +346,8 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { fn build_call_with_destination( &mut self, - invocable: InvocableSysCall, - signature: SysCallSignature, + invocable: InvocableSyscall, + signature: SyscallSignature, call_indexes_handle: CallIndexesHandle, destination_arg_idx: usize, ) -> Result> { @@ -360,7 +361,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { let gr_source_call_indexes_handle = self .syscalls_imports - .get(&InvocableSysCall::Loose(SysCallName::Source)) + .get(&InvocableSyscall::Loose(SyscallName::Source)) .map(|&(_, call_indexes_handle)| call_indexes_handle as u32) .expect("by config if destination is source, then `gr_source` is generated"); @@ -368,7 +369,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { .module .initial_mem_size() .map(Into::::into) - // To instantiate this generator, we must instantiate SysCallImportsGenerator, which can be + // To instantiate this generator, we must instantiate SyscallImportsGenerator, which can be // instantiated only with memory import generation proof. .expect("generator is instantiated with a memory import generation proof") .memory_size(); @@ -376,13 +377,30 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { let upper_limit = mem_size.saturating_sub(100); let offset = self.unstructured.int_in_range(0..=upper_limit)?; - vec![ - // call `gsys::gr_source` with a memory offset + // 3 instructions for invoking `gsys::gr_source` and possibly 3 more + // for defining value param so HashWithValue will be constructed. + let mut ret = Vec::with_capacity(6); + ret.extend_from_slice(&[ + // call `gsys::gr_source` storing actor id and some `offset` pointer. Instruction::I32Const(offset as i32), Instruction::Call(gr_source_call_indexes_handle), - // pass the offset as the first argument to the send-call Instruction::I32Const(offset as i32), - ] + ]); + + if invocable.has_destination_param_with_value() { + // We have to skip actor id bytes to define the following value param. + let skip_bytes = mem::size_of::(); + ret.extend_from_slice(&[ + // Define 0 value for HashWithValue + Instruction::I32Const(0), + // Store value on the offset + skip_bytes. That will form HashWithValue. + Instruction::I32Store(2, skip_bytes as u32), + // Pass the offset as the first argument to the syscall with destination. + Instruction::I32Const(offset as i32), + ]); + } + + ret } else { let address_offset = match self.offsets.as_mut() { Some(offsets) => { @@ -412,8 +430,8 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { fn build_call( &mut self, - invocable: InvocableSysCall, - signature: SysCallSignature, + invocable: InvocableSyscall, + signature: SyscallSignature, call_indexes_handle: CallIndexesHandle, ) -> Result> { let param_setters = self.build_param_setters(&signature.params)?; @@ -451,7 +469,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { let mem_size_pages = self .module .initial_mem_size() - // To instantiate this generator, we must instantiate SysCallImportsGenerator, which can be + // To instantiate this generator, we must instantiate SyscallImportsGenerator, which can be // instantiated only with memory import generation proof. .expect("generator is instantiated with a memory import generation proof"); let mem_size = Into::::into(mem_size_pages).memory_size(); @@ -461,7 +479,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { for processed_param in process_syscall_params(params, self.config.params_config()) { match processed_param { - ProcessedSysCallParams::Alloc { allowed_values } => { + ProcessedSyscallParams::Alloc { allowed_values } => { let pages_to_alloc = if let Some(allowed_values) = allowed_values { allowed_values.get_i32(self.unstructured)? } else { @@ -473,7 +491,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { setters.push(ParamSetter::new_i32(pages_to_alloc)); } - ProcessedSysCallParams::Value { + ProcessedSyscallParams::Value { value_type, allowed_values, } => { @@ -500,7 +518,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { setters.push(setter); } - ProcessedSysCallParams::MemoryArraySize => { + ProcessedSyscallParams::MemoryArraySize => { let length; let upper_limit = mem_size.saturating_sub(1) as i32; @@ -518,7 +536,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { log::trace!(" ---- Memory array length - {length}"); setters.push(ParamSetter::new_i32(length)); } - ProcessedSysCallParams::MemoryArrayPtr => { + ProcessedSyscallParams::MemoryArrayPtr => { let offset; let upper_limit = mem_size.saturating_sub(1) as i32; @@ -533,7 +551,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { log::trace!(" ---- Memory array offset - {offset}"); setters.push(ParamSetter::new_i32(offset)); } - ProcessedSysCallParams::MemoryPtrValue => { + ProcessedSyscallParams::MemoryPtrValue => { // Subtract a bit more so entities from `gsys` fit. let upper_limit = mem_size.saturating_sub(100); let offset = self.unstructured.int_in_range(0..=upper_limit)? as i32; @@ -556,14 +574,14 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { Ok(setters) } - fn build_result_processing_ignored(signature: SysCallSignature) -> Vec { + fn build_result_processing_ignored(signature: SyscallSignature) -> Vec { iter::repeat(Instruction::Drop) .take(signature.results.len()) .collect() } fn build_result_processing_fallible( - signature: SysCallSignature, + signature: SyscallSignature, param_setters: &[ParamSetter], ) -> Vec { // TODO: #3129. @@ -602,7 +620,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { } } - fn build_result_processing_infallible(signature: SysCallSignature) -> Vec { + fn build_result_processing_infallible(signature: SyscallSignature) -> Vec { // TODO: #3129 // For now we don't check anywhere that `alloc` and `free` return // error codes as described here. Also we don't assert that only `alloc` and `free` @@ -680,7 +698,7 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { let export_funcs_call_indexes_handles = module .export_section_mut() - // This generator is instantiated from SysCallsImportsGenerator, which can only be + // This generator is instantiated from SyscallsImportsGenerator, which can only be // generated if entry points and memory import were generated. .expect("has at least 1 export") .entries_mut() @@ -719,13 +737,13 @@ impl<'a, 'b> SysCallsInvocator<'a, 'b> { /// /// This type signals that syscalls imports generation, additional data injection and /// syscalls invocation (with further call indexes resolution) is done. -pub struct DisabledSysCallsInvocator { +pub struct DisabledSyscallsInvocator { module: WasmModule, call_indexes: CallIndexes, } -impl From for ModuleWithCallIndexes { - fn from(disabled_syscalls_invocator: DisabledSysCallsInvocator) -> Self { +impl From for ModuleWithCallIndexes { + fn from(disabled_syscalls_invocator: DisabledSyscallsInvocator) -> Self { ModuleWithCallIndexes { module: disabled_syscalls_invocator.module, call_indexes: disabled_syscalls_invocator.call_indexes, diff --git a/utils/wasm-gen/src/lib.rs b/utils/wasm-gen/src/lib.rs index 64bd9e4bfc3..cf317c0e8de 100644 --- a/utils/wasm-gen/src/lib.rs +++ b/utils/wasm-gen/src/lib.rs @@ -36,7 +36,7 @@ mod utils; mod wasm; pub use config::*; -pub use gear_wasm_instrument::syscalls::SysCallName; +pub use gear_wasm_instrument::syscalls::SyscallName; pub use generator::*; pub use wasm::WasmModule; pub use wasm_gen_arbitrary::*; diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index 1d4ff88eccf..8db5c816e40 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -47,6 +47,40 @@ use std::{mem, num::NonZeroUsize}; const UNSTRUCTURED_SIZE: usize = 1_000_000; +#[test] +fn inject_critical_gas_limit_works() { + let wat1 = r#" + (module + (memory $memory0 (import "env" "memory") 16) + (export "handle" (func $handle)) + (func $handle + call $f + drop + ) + (func $f (result i64) + call $f + ) + (func $g + (loop $my_loop + br $my_loop + ) + ) + )"#; + + let wasm_bytes = wat::parse_str(wat1).expect("invalid wat"); + let module = + parity_wasm::deserialize_buffer::(&wasm_bytes).expect("invalid wasm bytes"); + let module_with_critical_gas_limit = utils::inject_critical_gas_limit(module, 1_000_000); + + let wasm_bytes = module_with_critical_gas_limit + .into_bytes() + .expect("invalid pw module"); + assert!(wasmparser::validate(&wasm_bytes).is_ok()); + + let wat = wasmprinter::print_bytes(&wasm_bytes).expect("failed printing bytes"); + println!("wat = {wat}"); +} + #[test] fn remove_trivial_recursions() { let wat1 = r#" @@ -122,8 +156,8 @@ fn injecting_addresses_works() { stack_end_page: Some(stack_end_page), }) .with_syscalls_config( - SysCallsConfigBuilder::new(Default::default()) - .with_data_offset_msg_dest(addresses) + SyscallsConfigBuilder::new(Default::default()) + .with_addresses_msg_dest(addresses) .build(), ) .build(); @@ -185,10 +219,10 @@ fn error_processing_works_for_fallible_syscalls() { let mut unstructured = Unstructured::new(&buf); let mut unstructured2 = Unstructured::new(&buf); - let fallible_syscalls = SysCallName::instrumentable() + let fallible_syscalls = SyscallName::instrumentable() .into_iter() .filter_map(|syscall| { - let invocable_syscall = InvocableSysCall::Loose(syscall); + let invocable_syscall = InvocableSyscall::Loose(syscall); invocable_syscall.is_fallible().then_some(invocable_syscall) }); @@ -198,11 +232,11 @@ fn error_processing_works_for_fallible_syscalls() { const INJECTED_SYSCALLS: u32 = 8; - let mut injection_types = SysCallsInjectionTypes::all_never(); + let mut injection_types = SyscallsInjectionTypes::all_never(); injection_types.set(syscall, INJECTED_SYSCALLS, INJECTED_SYSCALLS); let syscalls_config_builder = - SysCallsConfigBuilder::new(injection_types).with_params_config(params_config); + SyscallsConfigBuilder::new(injection_types).with_params_config(params_config); // Assert that syscalls results will be processed. let termination_reason = execute_wasm_with_custom_configs( @@ -253,29 +287,29 @@ fn precise_syscalls_works() { rng.fill_bytes(&mut buf); let mut unstructured = Unstructured::new(&buf); - let precise_syscalls = SysCallName::instrumentable() + let precise_syscalls = SyscallName::instrumentable() .into_iter() .filter_map(|syscall| { - InvocableSysCall::has_precise_variant(syscall) - .then_some(InvocableSysCall::Precise(syscall)) + InvocableSyscall::has_precise_variant(syscall) + .then_some(InvocableSyscall::Precise(syscall)) }); for syscall in precise_syscalls { // Prepare syscalls config & context settings for test case. const INJECTED_SYSCALLS: u32 = 1; - let mut injection_types = SysCallsInjectionTypes::all_never(); + let mut injection_types = SyscallsInjectionTypes::all_never(); injection_types.set(syscall, INJECTED_SYSCALLS, INJECTED_SYSCALLS); - let mut param_config = SysCallsParamsConfig::default(); + let mut param_config = SyscallsParamsConfig::default(); param_config.add_rule(ParamType::Gas, (0..=0).into()); // Assert that syscalls results will be processed. let termination_reason = execute_wasm_with_custom_configs( &mut unstructured, - SysCallsConfigBuilder::new(injection_types) + SyscallsConfigBuilder::new(injection_types) .with_params_config(param_config) - .with_precise_syscalls_config(PreciseSysCallsConfig::new(3..=3)) + .with_precise_syscalls_config(PreciseSyscallsConfig::new(3..=3, 3..=3)) .with_source_msg_dest() .set_error_processing_config(ErrorProcessingConfig::All) .build(), @@ -300,14 +334,14 @@ struct MemoryWrite { } fn get_params_for_syscall_to_fail( - syscall: InvocableSysCall, -) -> (SysCallsParamsConfig, Option) { + syscall: InvocableSyscall, +) -> (SyscallsParamsConfig, Option) { let syscall_name = match syscall { - InvocableSysCall::Loose(name) => name, - InvocableSysCall::Precise(name) => name, + InvocableSyscall::Loose(name) => name, + InvocableSyscall::Precise(name) => name, }; let memory_write = match syscall_name { - SysCallName::PayProgramRent => Some(MemoryWrite { + SyscallName::PayProgramRent => Some(MemoryWrite { offset: 0, content: vec![255; WASM_PAGE_SIZE], }), @@ -315,14 +349,14 @@ fn get_params_for_syscall_to_fail( }; ( - SysCallsParamsConfig::all_constant_value(i32::MAX as i64), + SyscallsParamsConfig::all_constant_value(i32::MAX as i64), memory_write, ) } fn execute_wasm_with_custom_configs( unstructured: &mut Unstructured, - syscalls_config: SysCallsConfig, + syscalls_config: SyscallsConfig, initial_memory_write: Option, outgoing_limit: u32, imitate_reply: bool, diff --git a/utils/wasm-gen/src/utils.rs b/utils/wasm-gen/src/utils.rs index c89440967a6..826a316e775 100644 --- a/utils/wasm-gen/src/utils.rs +++ b/utils/wasm-gen/src/utils.rs @@ -16,9 +16,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use gear_wasm_instrument::parity_wasm::{ - builder, - elements::{self, FuncBody, ImportCountType, Instruction, Module, Type, ValueType}, +use crate::wasm::PageCount as WasmPageCount; +use gear_wasm_instrument::{ + parity_wasm::{ + builder, + elements::{ + BlockType, External, FuncBody, ImportCountType, Instruction, Instructions, Internal, + Module, Section, Type, ValueType, + }, + }, + syscalls::SyscallName, + wasm_instrument::{self, InjectionConfig}, }; use gsys::HashWithValue; use std::{ @@ -95,7 +103,7 @@ pub fn remove_recursion(module: Module) -> Module { .with_results(signature.results().to_vec()) .build() .body() - .with_instructions(elements::Instructions::new(body)) + .with_instructions(Instructions::new(body)) .build() .build(), ) @@ -215,6 +223,247 @@ fn find_recursion_impl( path.pop(); } +pub fn inject_stack_limiter(module: Module) -> Module { + wasm_instrument::inject_stack_limiter_with_config( + module, + InjectionConfig { + stack_limit: 30_003, + injection_fn: |signature| { + let results = signature.results(); + let mut body = Vec::with_capacity(results.len() + 1); + + for result in results { + let instruction = match result { + ValueType::I32 => Instruction::I32Const(u32::MAX as i32), + ValueType::I64 => Instruction::I64Const(u64::MAX as i64), + ValueType::F32 | ValueType::F64 => { + unreachable!("f32/64 types are not supported") + } + }; + + body.push(instruction); + } + + body.push(Instruction::Return); + + body + }, + stack_height_export_name: None, + }, + ) + .expect("Failed to inject stack height limits") +} + +/// Injects a critical gas limit to a given wasm module. +/// +/// Code before injection gas limiter: +/// ```ignore +/// fn func() { +/// func(); +/// loop { } +/// } +/// ``` +/// +/// Code after injection gas limiter: +/// ```ignore +/// use gcore::exec; +/// +/// const CRITICAL_GAS_LIMIT: u64 = 1_000_000; +/// +/// fn func() { +/// // exit from recursions +/// if exec::gas_available() <= CRITICAL_GAS_LIMIT { +/// return; +/// } +/// func(); +/// loop { +/// // exit from heavy loops +/// if exec::gas_available() <= CRITICAL_GAS_LIMIT { +/// return; +/// } +/// } +/// } +/// ``` +pub fn inject_critical_gas_limit(module: Module, critical_gas_limit: u64) -> Module { + // get initial memory size of program + let Some(mem_size) = module + .import_section() + .and_then(|section| { + section + .entries() + .iter() + .find_map(|entry| match entry.external() { + External::Memory(mem_ty) => Some(mem_ty.limits().initial()), + _ => None, + }) + }) + .map(Into::::into) + .map(|page_count| page_count.memory_size()) + else { + return module; + }; + + // store available gas pointer on the last memory page + let gas_ptr = mem_size - mem::size_of::() as u32; + + // add gr_gas_available import if needed + let maybe_gr_gas_available_index = module.import_section().and_then(|section| { + section + .entries() + .iter() + .filter(|entry| matches!(entry.external(), External::Function(_))) + .enumerate() + .find_map(|(i, entry)| { + (entry.module() == "env" && entry.field() == SyscallName::GasAvailable.to_str()) + .then_some(i as u32) + }) + }); + // sections should only be rewritten if the module did not previously have gr_gas_available import + let rewrite_sections = maybe_gr_gas_available_index.is_none(); + + let (gr_gas_available_index, mut module) = match maybe_gr_gas_available_index { + Some(gr_gas_available_index) => (gr_gas_available_index, module), + None => { + let mut mbuilder = builder::from_module(module); + + // fn gr_gas_available(gas: *mut u64); + let import_sig = mbuilder + .push_signature(builder::signature().with_param(ValueType::I32).build_sig()); + + mbuilder.push_import( + builder::import() + .module("env") + .field(SyscallName::GasAvailable.to_str()) + .external() + .func(import_sig) + .build(), + ); + + // back to plain module + let module = mbuilder.build(); + + let import_count = module.import_count(ImportCountType::Function); + let gr_gas_available_index = import_count as u32 - 1; + + (gr_gas_available_index, module) + } + }; + + let (Some(type_section), Some(function_section)) = + (module.type_section(), module.function_section()) + else { + return module; + }; + + let types = type_section.types().to_vec(); + let signature_entries = function_section.entries().to_vec(); + + let Some(code_section) = module.code_section_mut() else { + return module; + }; + + for (index, func_body) in code_section.bodies_mut().iter_mut().enumerate() { + let signature_index = &signature_entries[index]; + let signature = &types[signature_index.type_ref() as usize]; + let Type::Function(signature) = signature; + let results = signature.results(); + + // create the body of the gas limiter: + let mut body = Vec::with_capacity(results.len() + 9); + body.extend_from_slice(&[ + // gr_gas_available(gas_ptr) + Instruction::I32Const(gas_ptr as i32), + Instruction::Call(gr_gas_available_index), + // gas_available = *gas_ptr + Instruction::I32Const(gas_ptr as i32), + Instruction::I64Load(3, 0), + Instruction::I64Const(critical_gas_limit as i64), + // if gas_available <= critical_gas_limit { return result; } + Instruction::I64LeU, + Instruction::If(BlockType::NoResult), + ]); + + // exit the current function with dummy results + for result in results { + let instruction = match result { + ValueType::I32 => Instruction::I32Const(u32::MAX as i32), + ValueType::I64 => Instruction::I64Const(u64::MAX as i64), + ValueType::F32 | ValueType::F64 => unreachable!("f32/64 types are not supported"), + }; + + body.push(instruction); + } + + body.extend_from_slice(&[Instruction::Return, Instruction::End]); + + let instructions = func_body.code_mut().elements_mut(); + + let original_instructions = + mem::replace(instructions, Vec::with_capacity(instructions.len())); + let new_instructions = instructions; + + // insert gas limiter at the beginning of each function to limit recursions + new_instructions.extend_from_slice(&body); + + // also insert gas limiter at the beginning of each block, loop and condition + // to limit control instructions + for instruction in original_instructions { + match instruction { + Instruction::Block(_) | Instruction::Loop(_) | Instruction::If(_) => { + new_instructions.push(instruction); + new_instructions.extend_from_slice(&body); + } + Instruction::Call(call_index) + if rewrite_sections && call_index >= gr_gas_available_index => + { + // fix function indexes if import gr_gas_available was inserted + new_instructions.push(Instruction::Call(call_index + 1)); + } + _ => { + new_instructions.push(instruction); + } + } + } + } + + // fix other sections if import gr_gas_available was inserted + if rewrite_sections { + let sections = module.sections_mut(); + sections.retain(|section| !matches!(section, Section::Custom(_))); + + for section in sections { + match section { + Section::Export(export_section) => { + for export in export_section.entries_mut() { + if let Internal::Function(func_index) = export.internal_mut() { + if *func_index >= gr_gas_available_index { + *func_index += 1 + } + } + } + } + Section::Element(elements_section) => { + for segment in elements_section.entries_mut() { + for func_index in segment.members_mut() { + if *func_index >= gr_gas_available_index { + *func_index += 1 + } + } + } + } + Section::Start(start_idx) => { + if *start_idx >= gr_gas_available_index { + *start_idx += 1; + } + } + _ => {} + } + } + } + + module +} + pub(crate) fn hash_with_value_to_vec(hash_with_value: &HashWithValue) -> Vec { let address_data_size = mem::size_of::(); let address_data_slice = unsafe { diff --git a/utils/wasm-instrument/Cargo.toml b/utils/wasm-instrument/Cargo.toml index c79f9f48507..e6a304b5a8b 100644 --- a/utils/wasm-instrument/Cargo.toml +++ b/utils/wasm-instrument/Cargo.toml @@ -17,12 +17,10 @@ enum-iterator.workspace = true [dev-dependencies] wasmparser.workspace = true wat.workspace = true -gear-core-backend = { workspace = true, features = ["mock"] } gear-core.workspace = true [features] default = ["std"] std = [ - "gear-core-backend/std", "gwasm-instrument/std", ] diff --git a/utils/wasm-instrument/src/lib.rs b/utils/wasm-instrument/src/lib.rs index fe38b421e86..d1b623a596b 100644 --- a/utils/wasm-instrument/src/lib.rs +++ b/utils/wasm-instrument/src/lib.rs @@ -24,15 +24,15 @@ extern crate alloc; use alloc::vec; use gwasm_instrument::{ - gas_metering::{self, Rules}, + gas_metering::Rules, parity_wasm::{ builder, elements::{self, BlockType, ImportCountType, Instruction, Instructions, Local, ValueType}, }, }; -use crate::syscalls::SysCallName; -pub use gwasm_instrument::{self as wasm_instrument, parity_wasm}; +pub use crate::syscalls::SyscallName; +pub use gwasm_instrument::{self as wasm_instrument, gas_metering, parity_wasm}; #[cfg(test)] mod tests; @@ -57,7 +57,7 @@ pub fn inject( .import_section() .map(|section| { section.entries().iter().any(|entry| { - entry.module() == gas_module_name && entry.field() == SysCallName::OutOfGas.to_str() + entry.module() == gas_module_name && entry.field() == SyscallName::OutOfGas.to_str() }) }) .unwrap_or(false) @@ -86,7 +86,7 @@ pub fn inject( mbuilder.push_import( builder::import() .module(gas_module_name) - .field(SysCallName::OutOfGas.to_str()) + .field(SyscallName::OutOfGas.to_str()) .external() .func(import_sig) .build(), diff --git a/utils/wasm-instrument/src/syscalls.rs b/utils/wasm-instrument/src/syscalls.rs index 7a363764867..c00cace2543 100644 --- a/utils/wasm-instrument/src/syscalls.rs +++ b/utils/wasm-instrument/src/syscalls.rs @@ -22,12 +22,12 @@ use crate::parity_wasm::elements::{FunctionType, ValueType}; use alloc::{collections::BTreeSet, vec::Vec}; use enum_iterator::{self, Sequence}; -/// All available sys calls. +/// All available syscalls. /// -/// The type is mainly used to prevent from skipping sys-call integration test for -/// a newly introduced sys-call or from typo in sys-call name. +/// The type is mainly used to prevent from skipping syscall integration test for +/// a newly introduced syscall or from typo in syscall name. #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Sequence, Hash)] -pub enum SysCallName { +pub enum SyscallName { // Message sending related // -- // Sending `handle` message @@ -105,65 +105,65 @@ pub enum SysCallName { PayProgramRent, } -impl SysCallName { +impl SyscallName { pub fn to_str(&self) -> &'static str { match self { - SysCallName::Alloc => "alloc", - SysCallName::EnvVars => "gr_env_vars", - SysCallName::BlockHeight => "gr_block_height", - SysCallName::BlockTimestamp => "gr_block_timestamp", - SysCallName::CreateProgram => "gr_create_program", - SysCallName::CreateProgramWGas => "gr_create_program_wgas", - SysCallName::ReplyDeposit => "gr_reply_deposit", - SysCallName::Debug => "gr_debug", - SysCallName::Panic => "gr_panic", - SysCallName::OomPanic => "gr_oom_panic", - SysCallName::Exit => "gr_exit", - SysCallName::Free => "free", - SysCallName::GasAvailable => "gr_gas_available", - SysCallName::Leave => "gr_leave", - SysCallName::MessageId => "gr_message_id", - SysCallName::OutOfGas => "gr_out_of_gas", - SysCallName::PayProgramRent => "gr_pay_program_rent", - SysCallName::ProgramId => "gr_program_id", - SysCallName::Random => "gr_random", - SysCallName::Read => "gr_read", - SysCallName::Reply => "gr_reply", - SysCallName::ReplyCommit => "gr_reply_commit", - SysCallName::ReplyCommitWGas => "gr_reply_commit_wgas", - SysCallName::ReplyPush => "gr_reply_push", - SysCallName::ReplyTo => "gr_reply_to", - SysCallName::SignalFrom => "gr_signal_from", - SysCallName::ReplyWGas => "gr_reply_wgas", - SysCallName::ReplyInput => "gr_reply_input", - SysCallName::ReplyPushInput => "gr_reply_push_input", - SysCallName::ReplyInputWGas => "gr_reply_input_wgas", - SysCallName::ReservationReply => "gr_reservation_reply", - SysCallName::ReservationReplyCommit => "gr_reservation_reply_commit", - SysCallName::ReservationSend => "gr_reservation_send", - SysCallName::ReservationSendCommit => "gr_reservation_send_commit", - SysCallName::ReserveGas => "gr_reserve_gas", - SysCallName::Send => "gr_send", - SysCallName::SendCommit => "gr_send_commit", - SysCallName::SendCommitWGas => "gr_send_commit_wgas", - SysCallName::SendInit => "gr_send_init", - SysCallName::SendPush => "gr_send_push", - SysCallName::SendWGas => "gr_send_wgas", - SysCallName::SendInput => "gr_send_input", - SysCallName::SendPushInput => "gr_send_push_input", - SysCallName::SendInputWGas => "gr_send_input_wgas", - SysCallName::Size => "gr_size", - SysCallName::Source => "gr_source", - SysCallName::ReplyCode => "gr_reply_code", - SysCallName::SignalCode => "gr_signal_code", - SysCallName::SystemReserveGas => "gr_system_reserve_gas", - SysCallName::UnreserveGas => "gr_unreserve_gas", - SysCallName::Value => "gr_value", - SysCallName::ValueAvailable => "gr_value_available", - SysCallName::Wait => "gr_wait", - SysCallName::WaitFor => "gr_wait_for", - SysCallName::WaitUpTo => "gr_wait_up_to", - SysCallName::Wake => "gr_wake", + SyscallName::Alloc => "alloc", + SyscallName::EnvVars => "gr_env_vars", + SyscallName::BlockHeight => "gr_block_height", + SyscallName::BlockTimestamp => "gr_block_timestamp", + SyscallName::CreateProgram => "gr_create_program", + SyscallName::CreateProgramWGas => "gr_create_program_wgas", + SyscallName::ReplyDeposit => "gr_reply_deposit", + SyscallName::Debug => "gr_debug", + SyscallName::Panic => "gr_panic", + SyscallName::OomPanic => "gr_oom_panic", + SyscallName::Exit => "gr_exit", + SyscallName::Free => "free", + SyscallName::GasAvailable => "gr_gas_available", + SyscallName::Leave => "gr_leave", + SyscallName::MessageId => "gr_message_id", + SyscallName::OutOfGas => "gr_out_of_gas", + SyscallName::PayProgramRent => "gr_pay_program_rent", + SyscallName::ProgramId => "gr_program_id", + SyscallName::Random => "gr_random", + SyscallName::Read => "gr_read", + SyscallName::Reply => "gr_reply", + SyscallName::ReplyCommit => "gr_reply_commit", + SyscallName::ReplyCommitWGas => "gr_reply_commit_wgas", + SyscallName::ReplyPush => "gr_reply_push", + SyscallName::ReplyTo => "gr_reply_to", + SyscallName::SignalFrom => "gr_signal_from", + SyscallName::ReplyWGas => "gr_reply_wgas", + SyscallName::ReplyInput => "gr_reply_input", + SyscallName::ReplyPushInput => "gr_reply_push_input", + SyscallName::ReplyInputWGas => "gr_reply_input_wgas", + SyscallName::ReservationReply => "gr_reservation_reply", + SyscallName::ReservationReplyCommit => "gr_reservation_reply_commit", + SyscallName::ReservationSend => "gr_reservation_send", + SyscallName::ReservationSendCommit => "gr_reservation_send_commit", + SyscallName::ReserveGas => "gr_reserve_gas", + SyscallName::Send => "gr_send", + SyscallName::SendCommit => "gr_send_commit", + SyscallName::SendCommitWGas => "gr_send_commit_wgas", + SyscallName::SendInit => "gr_send_init", + SyscallName::SendPush => "gr_send_push", + SyscallName::SendWGas => "gr_send_wgas", + SyscallName::SendInput => "gr_send_input", + SyscallName::SendPushInput => "gr_send_push_input", + SyscallName::SendInputWGas => "gr_send_input_wgas", + SyscallName::Size => "gr_size", + SyscallName::Source => "gr_source", + SyscallName::ReplyCode => "gr_reply_code", + SyscallName::SignalCode => "gr_signal_code", + SyscallName::SystemReserveGas => "gr_system_reserve_gas", + SyscallName::UnreserveGas => "gr_unreserve_gas", + SyscallName::Value => "gr_value", + SyscallName::ValueAvailable => "gr_value_available", + SyscallName::Wait => "gr_wait", + SyscallName::WaitFor => "gr_wait_for", + SyscallName::WaitUpTo => "gr_wait_up_to", + SyscallName::Wake => "gr_wake", } } @@ -236,140 +236,140 @@ impl SysCallName { } /// Returns signature for syscall by name. - pub fn signature(self) -> SysCallSignature { + pub fn signature(self) -> SyscallSignature { use ParamType::*; use ValueType::I32; match self { - Self::Alloc => SysCallSignature::system([Alloc], [I32]), - Self::Free => SysCallSignature::system([Free], [I32]), - Self::Debug => SysCallSignature::gr([ + Self::Alloc => SyscallSignature::system([Alloc], [I32]), + Self::Free => SyscallSignature::system([Free], [I32]), + Self::Debug => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 1, })), - Size, + Length, ]), - Self::Panic => SysCallSignature::gr([ + Self::Panic => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 1, })), - Size, + Length, ]), - Self::OomPanic => SysCallSignature::gr([]), + Self::OomPanic => SyscallSignature::gr([]), Self::BlockHeight => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::BlockNumber))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::BlockNumber))]) } Self::BlockTimestamp => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::BlockTimestamp))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::BlockTimestamp))]) } - Self::Exit => SysCallSignature::gr([Ptr(PtrInfo::new_immutable(PtrType::Hash( + Self::Exit => SyscallSignature::gr([Ptr(PtrInfo::new_immutable(PtrType::Hash( HashType::ActorId, )))]), - Self::GasAvailable => SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Gas))]), - Self::PayProgramRent => SysCallSignature::gr([ + Self::GasAvailable => SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Gas))]), + Self::PayProgramRent => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithBlockNumberAndValue)), ]), Self::ProgramId => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Hash(HashType::ActorId)))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Hash(HashType::ActorId)))]) } - Self::Leave => SysCallSignature::gr([]), + Self::Leave => SyscallSignature::gr([]), Self::ValueAvailable => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Value))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Value))]) } - Self::Wait => SysCallSignature::gr([]), - Self::WaitUpTo => SysCallSignature::gr([Duration]), - Self::WaitFor => SysCallSignature::gr([Duration]), - Self::Wake => SysCallSignature::gr([ + Self::Wait => SyscallSignature::gr([]), + Self::WaitUpTo => SyscallSignature::gr([DurationBlockNumber]), + Self::WaitFor => SyscallSignature::gr([DurationBlockNumber]), + Self::Wake => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::Hash(HashType::MessageId))), - Delay, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), Self::ReplyCode => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::ErrorWithReplyCode))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::ErrorWithReplyCode))]) } Self::SignalCode => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::ErrorWithSignalCode))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::ErrorWithSignalCode))]) } - Self::MessageId => SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Hash( + Self::MessageId => SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Hash( HashType::MessageId, )))]), Self::EnvVars => { - SysCallSignature::gr([Version, Ptr(PtrInfo::new_mutable(PtrType::BufferStart))]) + SyscallSignature::gr([Version, Ptr(PtrInfo::new_mutable(PtrType::BufferStart))]) } - Self::Read => SysCallSignature::gr([ - MessagePosition, - Size, + Self::Read => SyscallSignature::gr([ + Offset, + Length, Ptr(PtrInfo::new_mutable(PtrType::SizedBufferStart { length_param_idx: 1, })), Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), - Self::Reply => SysCallSignature::gr([ + Self::Reply => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 1, })), - Size, + Length, Ptr(PtrInfo::new_immutable(PtrType::Value)), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReplyInput => SysCallSignature::gr([ - Size, - Size, + Self::ReplyInput => SyscallSignature::gr([ + Offset, + Length, Ptr(PtrInfo::new_immutable(PtrType::Value)), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReplyWGas => SysCallSignature::gr([ + Self::ReplyWGas => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 1, })), - Size, + Length, Gas, Ptr(PtrInfo::new_immutable(PtrType::Value)), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReplyInputWGas => SysCallSignature::gr([ - Size, - Size, + Self::ReplyInputWGas => SyscallSignature::gr([ + Offset, + Length, Gas, Ptr(PtrInfo::new_immutable(PtrType::Value)), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReplyCommit => SysCallSignature::gr([ + Self::ReplyCommit => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::Value)), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReplyCommitWGas => SysCallSignature::gr([ + Self::ReplyCommitWGas => SyscallSignature::gr([ Gas, Ptr(PtrInfo::new_immutable(PtrType::Value)), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReservationReply => SysCallSignature::gr([ + Self::ReservationReply => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ReservationId, ))), Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - Size, + Length, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReservationReplyCommit => SysCallSignature::gr([ + Self::ReservationReplyCommit => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ReservationId, ))), @@ -377,111 +377,113 @@ impl SysCallName { HashType::MessageId, ))), ]), - Self::ReplyPush => SysCallSignature::gr([ + Self::ReplyPush => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 1, })), - Size, + Length, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), - Self::ReplyPushInput => { - SysCallSignature::gr([Size, Size, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode))]) - } - Self::ReplyTo => SysCallSignature::gr([Ptr(PtrInfo::new_mutable( + Self::ReplyPushInput => SyscallSignature::gr([ + Offset, + Length, + Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), + ]), + Self::ReplyTo => SyscallSignature::gr([Ptr(PtrInfo::new_mutable( PtrType::ErrorWithHash(HashType::MessageId), ))]), - Self::SignalFrom => SysCallSignature::gr([Ptr(PtrInfo::new_mutable( + Self::SignalFrom => SyscallSignature::gr([Ptr(PtrInfo::new_mutable( PtrType::ErrorWithHash(HashType::MessageId), ))]), - Self::Send => SysCallSignature::gr([ + Self::Send => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - Size, - Delay, + Length, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::SendInput => SysCallSignature::gr([ + Self::SendInput => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), - Size, - Size, - Delay, + Offset, + Length, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::SendWGas => SysCallSignature::gr([ + Self::SendWGas => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - Size, + Length, Gas, - Delay, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::SendInputWGas => SysCallSignature::gr([ + Self::SendInputWGas => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), - Size, - Size, + Offset, + Length, Gas, - Delay, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::SendCommit => SysCallSignature::gr([ + Self::SendCommit => SyscallSignature::gr([ Handler, Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), - Delay, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::SendCommitWGas => SysCallSignature::gr([ + Self::SendCommitWGas => SyscallSignature::gr([ Handler, Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::ActorId, ))), Gas, - Delay, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), Self::SendInit => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHandle))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHandle))]) } - Self::SendPush => SysCallSignature::gr([ + Self::SendPush => SyscallSignature::gr([ Handler, Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - Size, + Length, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), - Self::SendPushInput => SysCallSignature::gr([ + Self::SendPushInput => SyscallSignature::gr([ Handler, - Size, - Size, + Offset, + Length, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), - Self::ReservationSend => SysCallSignature::gr([ + Self::ReservationSend => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::TwoHashesWithValue( HashType::ReservationId, HashType::ActorId, @@ -489,87 +491,87 @@ impl SysCallName { Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - Size, - Delay, + Length, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::ReservationSendCommit => SysCallSignature::gr([ + Self::ReservationSendCommit => SyscallSignature::gr([ Handler, Ptr(PtrInfo::new_immutable(PtrType::TwoHashesWithValue( HashType::ReservationId, HashType::ActorId, ))), - Delay, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::MessageId, ))), ]), - Self::Size => SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Length))]), + Self::Size => SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Length))]), Self::Source => { - SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Hash(HashType::ActorId)))]) + SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Hash(HashType::ActorId)))]) } - Self::Value => SysCallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Value))]), - Self::CreateProgram => SysCallSignature::gr([ + Self::Value => SyscallSignature::gr([Ptr(PtrInfo::new_mutable(PtrType::Value))]), + Self::CreateProgram => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::CodeId, ))), Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - Size, + Length, Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 4, })), - Size, - Delay, + Length, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithTwoHashes( HashType::MessageId, HashType::ActorId, ))), ]), - Self::CreateProgramWGas => SysCallSignature::gr([ + Self::CreateProgramWGas => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::HashWithValue( HashType::CodeId, ))), Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 2, })), - Size, + Length, Ptr(PtrInfo::new_immutable(PtrType::SizedBufferStart { length_param_idx: 4, })), - Size, + Length, Gas, - Delay, + DelayBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithTwoHashes( HashType::MessageId, HashType::ActorId, ))), ]), - Self::ReplyDeposit => SysCallSignature::gr([ + Self::ReplyDeposit => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::Hash(HashType::MessageId))), Gas, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode)), ]), - Self::ReserveGas => SysCallSignature::gr([ + Self::ReserveGas => SyscallSignature::gr([ Gas, - Duration, + DurationBlockNumber, Ptr(PtrInfo::new_mutable(PtrType::ErrorWithHash( HashType::ReservationId, ))), ]), - Self::UnreserveGas => SysCallSignature::gr([ + Self::UnreserveGas => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::Hash( HashType::ReservationId, ))), Ptr(PtrInfo::new_mutable(PtrType::ErrorWithGas)), ]), Self::SystemReserveGas => { - SysCallSignature::gr([Gas, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode))]) + SyscallSignature::gr([Gas, Ptr(PtrInfo::new_mutable(PtrType::ErrorCode))]) } - Self::Random => SysCallSignature::gr([ + Self::Random => SyscallSignature::gr([ Ptr(PtrInfo::new_immutable(PtrType::Hash(HashType::SubjectId))), Ptr(PtrInfo::new_mutable(PtrType::BlockNumberWithHash( HashType::SubjectId, @@ -591,6 +593,15 @@ impl SysCallName { _ => return None, }) } + + /// Checks whether the syscall is fallible. + /// + /// Literally checks whether syscall contains mutable error pointer. + pub fn is_fallible(&self) -> bool { + self.signature().params.into_iter().any( + |param| matches!(param, ParamType::Ptr(PtrInfo { mutable: true, ty }) if ty.is_error()), + ) + } } /// Syscall param type. @@ -599,16 +610,16 @@ impl SysCallName { /// belongs to. See [`PtrInfo`] and [`PtrType`] for more details. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum ParamType { - Size, // i32 buffers size in memory - Ptr(PtrInfo), // i32 pointer - Gas, // i64 gas amount - MessagePosition, // i32 message position - Duration, // i32 duration in blocks - Delay, // i32 delay in blocks - Handler, // i32 handler number - Alloc, // i32 alloc pages - Free, // i32 free page - Version, // i32 version number of exec settings + Length, // i32 buffers length + Ptr(PtrInfo), // i32 pointer + Gas, // i64 gas amount + Offset, // i32 offset in the input buffer (message payload) + DurationBlockNumber, // i32 duration in blocks + DelayBlockNumber, // i32 delay in blocks + Handler, // i32 handler number + Alloc, // i32 pages to alloc + Free, // i32 page number to free + Version, // i32 version number of exec settings } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -711,12 +722,12 @@ impl From for ValueType { /// Syscall signature. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct SysCallSignature { +pub struct SyscallSignature { pub params: Vec, pub results: Vec, } -impl SysCallSignature { +impl SyscallSignature { pub fn gr(params: [ParamType; N]) -> Self { Self { params: params.to_vec(), diff --git a/utils/wasm-instrument/src/tests.rs b/utils/wasm-instrument/src/tests.rs index d85a139fcca..de674939ed2 100644 --- a/utils/wasm-instrument/src/tests.rs +++ b/utils/wasm-instrument/src/tests.rs @@ -19,7 +19,7 @@ use super::*; use crate::{ rules::CustomConstantCostRules, - syscalls::{ParamType, PtrInfo, PtrType, SysCallName}, + syscalls::{ParamType, PtrInfo, PtrType, SyscallName}, }; use alloc::format; use elements::Instruction::*; @@ -161,7 +161,7 @@ fn duplicate_import() { (global i32 (i32.const 42)) (memory 0 1) )"#, - out_of_gas = SysCallName::OutOfGas.to_str() + out_of_gas = SyscallName::OutOfGas.to_str() ); let module = parse_wat(&wat); @@ -618,66 +618,11 @@ test_gas_counter_injection! { "# } -/// Check that all sys calls are supported by backend. -#[test] -fn test_sys_calls_table() { - use gas_metering::ConstantCostRules; - use gear_core::message::DispatchKind; - use gear_core_backend::{ - env::{BackendReport, Environment}, - error::ActorTerminationReason, - mock::MockExt, - }; - use parity_wasm::builder; - - // Make module with one empty function. - let mut module = builder::module() - .function() - .signature() - .build() - .build() - .build(); - - // Insert syscalls imports. - for name in SysCallName::instrumentable() { - let sign = name.signature(); - let types = module.type_section_mut().unwrap().types_mut(); - let type_no = types.len() as u32; - types.push(parity_wasm::elements::Type::Function(sign.func_type())); - - module = builder::from_module(module) - .import() - .module("env") - .external() - .func(type_no) - .field(name.to_str()) - .build() - .build(); - } - - let module = inject(module, &ConstantCostRules::default(), "env").unwrap(); - let code = module.into_bytes().unwrap(); - - // Execute wasm and check success. - let ext = MockExt::default(); - let env = - Environment::new(ext, &code, DispatchKind::Init, Default::default(), 0.into()).unwrap(); - let report = env - .execute(|_, _, _| -> Result<(), u32> { Ok(()) }) - .unwrap(); - - let BackendReport { - termination_reason, .. - } = report; - - assert_eq!(termination_reason, ActorTerminationReason::Success.into()); -} - #[test] fn check_memory_array_pointers_definition_correctness() { - let sys_calls = SysCallName::instrumentable(); - for sys_call in sys_calls { - let signature = sys_call.signature(); + let syscalls = SyscallName::instrumentable(); + for syscall in syscalls { + let signature = syscall.signature(); let size_param_indexes = signature .params .iter() @@ -690,7 +635,24 @@ fn check_memory_array_pointers_definition_correctness() { }); for idx in size_param_indexes { - assert_eq!(signature.params.get(idx), Some(&ParamType::Size)); + assert_eq!(signature.params.get(idx), Some(&ParamType::Length)); + } + } +} + +// Basically checks that mutable error pointer is always last in every fallible syscall params set. +#[test] +fn check_syscall_err_ptr_position() { + for syscall in SyscallName::instrumentable() { + if syscall.is_fallible() { + let signature = syscall.signature(); + let err_ptr = signature + .params + .last() + .expect("fallible syscall has at least err ptr"); + assert!( + matches!(err_ptr, ParamType::Ptr(PtrInfo { mutable: true, ty }) if ty.is_error()) + ); } } }