From 95179723a8515337a90276d47374237c3e5a7784 Mon Sep 17 00:00:00 2001 From: Michael Heuer Date: Thu, 4 Jun 2026 11:52:56 +0200 Subject: [PATCH] feat: add integration tests --- .github/ISSUE_TEMPLATE/task.md | 23 - .../workflows/{bindings.yml => crates.yml} | 34 +- CONTEXT.md | 40 ++ Cargo.lock | 607 +++++++++++++----- Cargo.toml | 25 +- README.md | 17 +- contracts/README.md | 2 +- {bindings => crates/bindings}/Cargo.toml | 0 {bindings => crates/bindings}/LICENSE | 0 {bindings => crates/bindings}/README.md | 0 .../bindings}/deployments.json | 0 .../bindings}/src/addresses.rs | 0 {bindings => crates/bindings}/src/contract.rs | 0 .../bindings}/src/conversion.rs | 0 {bindings => crates/bindings}/src/error.rs | 0 .../src/generated/i_commitment_tree.rs | 0 .../src/generated/i_nullifier_set.rs | 0 .../src/generated/i_protocol_adapter.rs | 0 .../bindings}/src/generated/mod.rs | 0 .../src/generated/protocol_adapter.rs | 0 {bindings => crates/bindings}/src/helpers.rs | 0 {bindings => crates/bindings}/src/lib.rs | 0 .../bindings}/tests/contract.rs | 0 .../bindings}/tests/conversion.rs | 0 .../bindings}/tests/deployments.rs | 0 .../example-tx-generation}/Cargo.toml | 0 .../example-tx-generation}/LICENSE | 0 .../example-tx-generation}/README.md | 0 .../example-tx-generation}/src/main.rs | 0 crates/integration-test/Cargo.toml | 36 ++ .../src/deploy/mock_risc0_bindings.rs | 55 ++ crates/integration-test/src/deploy/mod.rs | 5 + crates/integration-test/src/deploy/pa.rs | 14 + crates/integration-test/src/envs.rs | 8 + .../src/envs/common/execute.rs | 224 +++++++ .../integration-test/src/envs/common/mod.rs | 4 + .../integration-test/src/envs/e2e/config.rs | 47 ++ crates/integration-test/src/envs/e2e/mod.rs | 159 +++++ crates/integration-test/src/envs/e2e/setup.rs | 94 +++ crates/integration-test/src/envs/local/mod.rs | 164 +++++ .../integration-test/src/envs/local/setup.rs | 103 +++ crates/integration-test/src/keychain.rs | 25 + crates/integration-test/src/lib.rs | 4 + crates/integration-test/src/state/actors.rs | 78 +++ crates/integration-test/src/state/chains.rs | 96 +++ crates/integration-test/src/state/keys.rs | 23 + crates/integration-test/src/state/mod.rs | 4 + crates/integration-test/src/state/pa.rs | 72 +++ crates/integration-test/tests/conversion.rs | 21 + crates/integration-test/tests/integration.rs | 108 ++++ justfile | 72 ++- 51 files changed, 1927 insertions(+), 237 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/task.md rename .github/workflows/{bindings.yml => crates.yml} (66%) create mode 100644 CONTEXT.md rename {bindings => crates/bindings}/Cargo.toml (100%) rename {bindings => crates/bindings}/LICENSE (100%) rename {bindings => crates/bindings}/README.md (100%) rename {bindings => crates/bindings}/deployments.json (100%) rename {bindings => crates/bindings}/src/addresses.rs (100%) rename {bindings => crates/bindings}/src/contract.rs (100%) rename {bindings => crates/bindings}/src/conversion.rs (100%) rename {bindings => crates/bindings}/src/error.rs (100%) rename {bindings => crates/bindings}/src/generated/i_commitment_tree.rs (100%) rename {bindings => crates/bindings}/src/generated/i_nullifier_set.rs (100%) rename {bindings => crates/bindings}/src/generated/i_protocol_adapter.rs (100%) rename {bindings => crates/bindings}/src/generated/mod.rs (100%) rename {bindings => crates/bindings}/src/generated/protocol_adapter.rs (100%) rename {bindings => crates/bindings}/src/helpers.rs (100%) rename {bindings => crates/bindings}/src/lib.rs (100%) rename {bindings => crates/bindings}/tests/contract.rs (100%) rename {bindings => crates/bindings}/tests/conversion.rs (100%) rename {bindings => crates/bindings}/tests/deployments.rs (100%) rename {example-tx-generation => crates/example-tx-generation}/Cargo.toml (100%) rename {example-tx-generation => crates/example-tx-generation}/LICENSE (100%) rename {example-tx-generation => crates/example-tx-generation}/README.md (100%) rename {example-tx-generation => crates/example-tx-generation}/src/main.rs (100%) create mode 100644 crates/integration-test/Cargo.toml create mode 100644 crates/integration-test/src/deploy/mock_risc0_bindings.rs create mode 100644 crates/integration-test/src/deploy/mod.rs create mode 100644 crates/integration-test/src/deploy/pa.rs create mode 100644 crates/integration-test/src/envs.rs create mode 100644 crates/integration-test/src/envs/common/execute.rs create mode 100644 crates/integration-test/src/envs/common/mod.rs create mode 100644 crates/integration-test/src/envs/e2e/config.rs create mode 100644 crates/integration-test/src/envs/e2e/mod.rs create mode 100644 crates/integration-test/src/envs/e2e/setup.rs create mode 100644 crates/integration-test/src/envs/local/mod.rs create mode 100644 crates/integration-test/src/envs/local/setup.rs create mode 100644 crates/integration-test/src/keychain.rs create mode 100644 crates/integration-test/src/lib.rs create mode 100644 crates/integration-test/src/state/actors.rs create mode 100644 crates/integration-test/src/state/chains.rs create mode 100644 crates/integration-test/src/state/keys.rs create mode 100644 crates/integration-test/src/state/mod.rs create mode 100644 crates/integration-test/src/state/pa.rs create mode 100644 crates/integration-test/tests/conversion.rs create mode 100644 crates/integration-test/tests/integration.rs diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md deleted file mode 100644 index 5c8d0e89..00000000 --- a/.github/ISSUE_TEMPLATE/task.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Task -about: A protocol adapter related task -title: "" -labels: "" -assignees: "" ---- - -## Context - - - -## Scope - - - -## Definition of Done - - - -## Outcome - - diff --git a/.github/workflows/bindings.yml b/.github/workflows/crates.yml similarity index 66% rename from .github/workflows/bindings.yml rename to .github/workflows/crates.yml index 126f787e..14c43762 100644 --- a/.github/workflows/bindings.yml +++ b/.github/workflows/crates.yml @@ -1,4 +1,4 @@ -name: Bindings +name: Crates on: push: @@ -8,10 +8,13 @@ on: jobs: tests: - name: Bindings Tests + name: Crates Tests runs-on: macos-latest env: ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} + QUEUE_BASE_URL: ${{ secrets.QUEUE_BASE_URL }} + QUEUE_AUTH_TOKEN: ${{ secrets.QUEUE_AUTH_TOKEN }} + RISC0_SKIP_BUILD_KERNELS: true steps: - name: Checkout repository @@ -20,6 +23,11 @@ jobs: - name: Install just uses: extractions/setup-just@v3 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: v1.7.0 + - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -28,30 +36,22 @@ jobs: - name: Show Rust version run: rustc --version - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: v1.7.0 - - - name: Show Foundry version - run: forge --version - - name: Restore dependencies cache uses: actions/cache@v4 with: path: | target - key: rust-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + key: rust-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} restore-keys: rust- - name: Run rustfmt - run: just bindings-fmt-check + run: just crates-fmt-check - - name: Build Bindings - run: just bindings-build + - name: Build + run: just crates-build --features e2e - - name: Lint Bindings - run: just bindings-lint + - name: Lint + run: just crates-lint - name: Run Tests - run: just bindings-test + run: just crates-test --features e2e diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 00000000..08a82b75 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,40 @@ +# Anoma EVM Protocol Adapter + +The protocol adapter contract and integration-test layer that settles **Anoma +Resource Machine** transactions on EVM-compatible chains. + +## Language + +**Protocol Adapter (PA)**: +The EVM contract that verifies and settles ARM transactions on-chain — checking +the compliance, logic, and delta proofs and updating the commitment tree and +nullifier set. Use "protocol adapter" (not "verifier" or "settler") for the +contract. + +**Anoma Resource Machine (ARM)**: +The state model the PA settles: state is a set of immutable **resources** that +transactions consume and create. The off-chain ARM types live in `anoma-rm-risc0`. + +**Resource**: +The unit of ARM state. Created and consumed by actions; committed to the +commitment tree and spent via a nullifier. + +**Action**: +A bundle of consumed and created resources proven together — one compliance unit +plus the per-resource logic proofs. + +**Transaction**: +A set of actions plus a delta proof, submitted to the PA's `execute` function. + +**Compliance / Logic / Delta**: +The three proof kinds the PA verifies — compliance for resource bookkeeping, logic +for each resource's application rules, delta for value balance. Aggregation folds +them into a single proof. + +**Commitment Tree / Nullifier Set**: +The PA's on-chain state — a Merkle tree of resource commitments and the set of +spent nullifiers. + +**Forwarder**: +An application contract (e.g. the ERC20 or generic-call forwarder) that the PA +drives to enact EVM side effects on behalf of an action. Each lives in its own repo. diff --git a/Cargo.lock b/Cargo.lock index d2f230de..f538d500 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -359,7 +359,7 @@ checksum = "ec0a82e56b1843bce483942d54fcadea92e676f1bde162e93c7d3b621fabc4e1" dependencies = [ "alloy-primitives", "alloy-sol-types", - "http 1.4.0", + "http 1.4.2", "serde", "serde_json", "thiserror 2.0.18", @@ -492,7 +492,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.13.3", + "reqwest 0.13.4", "serde", "serde_json", "thiserror 2.0.18", @@ -561,7 +561,7 @@ dependencies = [ "alloy-transport-ws", "futures", "pin-project", - "reqwest 0.13.3", + "reqwest 0.13.4", "serde", "serde_json", "tokio", @@ -915,7 +915,7 @@ dependencies = [ "alloy-json-rpc", "alloy-transport", "itertools 0.14.0", - "reqwest 0.13.3", + "reqwest 0.13.4", "serde_json", "tower", "tracing", @@ -951,7 +951,7 @@ dependencies = [ "alloy-pubsub", "alloy-transport", "futures", - "http 1.4.0", + "http 1.4.2", "rustls", "serde_json", "tokio", @@ -1012,6 +1012,57 @@ dependencies = [ "tokio", ] +[[package]] +name = "anoma-pa-evm-integration-test" +version = "0.1.0" +dependencies = [ + "alloy", + "alloy-chains", + "anoma-pa-evm-bindings", + "anoma-pa-testkit", + "anoma-risc0-verifier-bindings", + "anoma-rm-risc0", + "anyhow", + "bincode", + "dotenvy", + "hex", + "regex", + "risc0-zkvm", + "rstest", + "tokio", +] + +[[package]] +name = "anoma-pa-testkit" +version = "0.2.0" +source = "git+https://github.com/anoma/pa-testkit?rev=74cf6ad7cd488021128c34662e68171c7ae28e6d#74cf6ad7cd488021128c34662e68171c7ae28e6d" +dependencies = [ + "anoma-rm-risc0", + "anoma-rm-risc0-gadgets", + "anyhow", + "bincode", + "futures", + "heliax-ap-orchestrator-sdk", + "hex", + "k256", + "mockall", + "regex", + "risc0-zkvm", + "serde", + "sha2 0.11.0", + "tokio", +] + +[[package]] +name = "anoma-risc0-verifier-bindings" +version = "1.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9de2aea49f20548795e42423d7dd0ea32f3ec1c6737902aba0623e9033b2b5" +dependencies = [ + "alloy", + "serde", +] + [[package]] name = "anoma-rm-risc0" version = "1.1.1" @@ -1075,6 +1126,12 @@ dependencies = [ "serde", ] +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + [[package]] name = "anyhow" version = "1.0.102" @@ -1090,6 +1147,15 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "ark-bn254" version = "0.5.0" @@ -1535,15 +1601,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.8.17" +version = "1.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517aa062d8bd9015ee23d6daa5e1c1372328412fdae4e6c4c1be9b69c6ad37a2" +checksum = "e33f815b73a3899c03b380d543532e5865f230dce9678d108dc10732a8682275" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1558,7 +1624,7 @@ dependencies = [ "aws-types", "bytes", "fastrand", - "http 1.4.0", + "http 1.4.2", "time", "tokio", "tracing", @@ -1616,7 +1682,7 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "percent-encoding", "pin-project-lite", @@ -1626,10 +1692,11 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.107.0" +version = "1.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff0bb3e6249447a5ecfeec4616de4f04498a4337302b6ffe221b4a8745905c4" +checksum = "217276b3d948ec05b2726d66b2c9013567ca42751052c287ec45800ee81799c9" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -1643,17 +1710,18 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.104.0" +version = "1.106.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa6622798e19e6a76b690562085dd4771c736cd48343464a53ab4ae2f2c9f84" +checksum = "4c80de7bb7d03e9ca8c9fd7b489f20f3948d3f3be91a7953591347d238115408" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -1668,16 +1736,16 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.4.4" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7083fb918b38474ac65ffbf8a69fc8792d36879f4ac5f1667b43aec61efe9a5" +checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -1688,7 +1756,7 @@ dependencies = [ "hex", "hmac 0.13.0", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "percent-encoding", "sha2 0.11.0", "time", @@ -1718,7 +1786,7 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "percent-encoding", @@ -1729,9 +1797,9 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.6" +version = "0.62.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5" +checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-schema", @@ -1772,7 +1840,7 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -1784,16 +1852,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32" +checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "pin-project-lite", "tokio", "tracing", @@ -1819,20 +1887,20 @@ checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", - "http 1.4.0", + "http 1.4.2", ] [[package]] name = "aws-smithy-types" -version = "1.4.8" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b" +checksum = "32b42fcf341259d85ca10fac9a2f6448a8ec691c6955a18e45bc3b71a85fab85" dependencies = [ "base64-simd", "bytes", "bytes-utils", "http 0.2.12", - "http 1.4.0", + "http 1.4.2", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -1878,7 +1946,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "itoa", @@ -1902,7 +1970,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "mime", @@ -1972,15 +2040,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.4" +version = "0.1.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" +checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" [[package]] name = "bitcoin_hashes" -version = "0.14.1" +version = "0.14.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" dependencies = [ "bitcoin-io", "hex-conservative", @@ -1994,9 +2062,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitvec" @@ -2036,9 +2104,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -2103,9 +2171,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byte-slice-cast" @@ -2207,9 +2275,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.62" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "jobserver", @@ -2231,13 +2299,15 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -2286,9 +2356,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "cobs" @@ -2350,9 +2420,9 @@ checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "const-hex" -version = "1.19.0" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -2684,7 +2754,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "powerfmt", "serde_core", ] @@ -2791,7 +2860,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", @@ -2829,9 +2898,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -2859,6 +2928,12 @@ dependencies = [ "clap", ] +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "downcast-rs" version = "1.2.1" @@ -3207,6 +3282,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -3290,6 +3374,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.32" @@ -3326,9 +3416,9 @@ dependencies = [ "hyper", "jsonwebtoken", "once_cell", - "prost 0.14.3", - "prost-types 0.14.3", - "reqwest 0.13.3", + "prost 0.14.4", + "prost-types 0.14.4", + "reqwest 0.13.4", "secret-vault-value", "serde", "serde_json", @@ -3347,7 +3437,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bafc7e33650ab9f05dcc16325f05d56b8d10393114e31a19a353b86fa60cfe7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "cfg-if", "log", "managed", @@ -3465,7 +3555,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.4.2", "indexmap 2.14.0", "slab", "tokio", @@ -3558,6 +3648,40 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "heliax-ap-orchestrator-sdk" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56eef6564772c8be23bc7ebe41ae1ea1cab830a2873f30e27335fe57bf3916d5" +dependencies = [ + "heliax-ap-orchestrator-shared", + "reqwest 0.12.28", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "heliax-ap-orchestrator-shared" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbdf274755868c87e16da5ec59e36c9bc037813cdc4844d8cc2f61be942825c" +dependencies = [ + "aes-gcm", + "base64", + "chrono", + "hex", + "pem", + "rand 0.8.6", + "ring", + "serde", + "serde_json", + "thiserror 2.0.18", + "utoipa", + "uuid", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -3637,9 +3761,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -3663,7 +3787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.2", ] [[package]] @@ -3674,7 +3798,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "pin-project-lite", ] @@ -3702,16 +3826,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "httparse", "httpdate", @@ -3728,7 +3852,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", + "http 1.4.2", "hyper", "hyper-util", "rustls", @@ -3761,7 +3885,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "hyper", "ipnet", @@ -4113,13 +4237,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -4152,6 +4275,7 @@ dependencies = [ "once_cell", "serdect", "sha2 0.10.9", + "signature", ] [[package]] @@ -4175,9 +4299,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1766b89733097006f3a1388a02849865d6bc98c89273cb622e29fdd209922183" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -4270,9 +4394,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "libc", ] @@ -4312,9 +4436,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "lru" @@ -4444,9 +4568,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -4484,7 +4608,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "block", "core-graphics-types", "foreign-types", @@ -4521,15 +4645,41 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "mockall" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58d964098a5f9c6b63d0798e5372fd04708193510a7af313c22e9f29b7b620b" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca41ce716dda6a9be188b385aa78ee5260fc25cd3802cb2a8afdc6afbe6b6dbf" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "ndarray" version = "0.16.1" @@ -5011,6 +5161,32 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -5047,7 +5223,31 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.11+spec-1.1.0", + "toml_edit 0.25.12+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", ] [[package]] @@ -5101,7 +5301,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.1", + "bitflags 2.13.0", "num-traits", "rand 0.9.4", "rand_chacha 0.9.0", @@ -5134,12 +5334,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", - "prost-derive 0.14.3", + "prost-derive 0.14.4", ] [[package]] @@ -5170,9 +5370,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools 0.14.0", @@ -5192,11 +5392,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ - "prost 0.14.3", + "prost 0.14.4", ] [[package]] @@ -5420,7 +5620,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", ] [[package]] @@ -5456,9 +5656,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -5485,9 +5685,15 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" @@ -5501,7 +5707,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "hyper", @@ -5534,16 +5740,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "futures-core", "futures-util", "h2", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "hyper", @@ -5983,6 +6189,35 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-ident", +] + [[package]] name = "ruint" version = "1.18.0" @@ -6070,7 +6305,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -6095,9 +6330,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -6336,7 +6571,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6453,9 +6688,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" dependencies = [ "base64", "bs58", @@ -6473,9 +6708,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.20.0" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ "darling 0.23.0", "proc-macro2", @@ -6548,9 +6783,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3f15d4e239ebe08413eed880e0f9b5af4b40ee0472543320efa91d488e96a7" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" dependencies = [ "cc", "cfg-if", @@ -6558,9 +6793,19 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] [[package]] name = "signature" @@ -6614,18 +6859,18 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -6793,6 +7038,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "textwrap" version = "0.16.2" @@ -6850,12 +7101,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "fc1aa89044e7786ffb2ec017acb22cb7de5b0be46d0f21aea2b224b8561e5db2" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -6865,15 +7115,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "9d3bfe86347f0cc659f586f01e26303ccd32418f26f30c7b0309b3ca3a07d695" dependencies = [ "num-conv", "time-core", @@ -6913,7 +7163,9 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", @@ -7027,9 +7279,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", @@ -7063,7 +7315,7 @@ dependencies = [ "base64", "bytes", "h2", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "hyper", @@ -7090,7 +7342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", - "prost 0.14.3", + "prost 0.14.4", "tonic", ] @@ -7120,11 +7372,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", - "bitflags 2.11.1", + "bitflags 2.13.0", "bytes", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body 1.0.1", "http-body-util", "pin-project-lite", @@ -7204,7 +7456,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http 1.4.2", "httparse", "log", "rand 0.9.4", @@ -7273,9 +7525,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "typetag" @@ -7339,9 +7591,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -7396,13 +7648,40 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", + "uuid", +] + [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ + "getrandom 0.4.2", "js-sys", + "serde_core", "wasm-bindgen", ] @@ -7466,9 +7745,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen 0.57.1", ] @@ -7484,9 +7763,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" dependencies = [ "cfg-if", "once_cell", @@ -7497,9 +7776,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ "js-sys", "wasm-bindgen", @@ -7507,9 +7786,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7517,9 +7796,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ "bumpalo", "proc-macro2", @@ -7530,9 +7809,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] @@ -7591,7 +7870,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.13.0", "hashbrown 0.15.5", "indexmap 2.14.0", "semver 1.0.28", @@ -7613,9 +7892,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" dependencies = [ "js-sys", "wasm-bindgen", @@ -8002,7 +8281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.13.0", "indexmap 2.14.0", "log", "serde", @@ -8085,9 +8364,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8108,18 +8387,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -8149,18 +8428,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e3d1f118..bc559032 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,14 @@ [workspace] resolver = "3" -members = ["bindings", "example-tx-generation"] +members = [ + "crates/bindings", + "crates/example-tx-generation", + "crates/integration-test", +] +# example-tx-generation links the risc0 prover (real aggregated proofs); keep it +# out of default builds (CI `crates-*` recipes) — build it explicitly with +# `cargo build -p example-tx-generation` when generating test vectors. +default-members = ["crates/bindings", "crates/integration-test"] [workspace.package] keywords = ["anoma", "evm", "protocol-adapter"] @@ -15,10 +23,23 @@ alloy = { version = "2.0.5", features = ["full", "eip712", "node-bindings"] } alloy-chains = "0.2.33" anoma-rm-risc0 = { version = "1.1.1", features = ["bonsai", "aggregation"] } anoma-rm-risc0-test-app = { version = "1.1.1", features = ["bonsai"] } +anyhow = "1.0" +bincode = "1.3.3" dotenvy = { version = "0.15.7", features = ["cli"] } +hex = "0.4" +regex = "1.12.3" +risc0-zkvm = "3.0.3" +rstest = "0.26.1" serde = { version = "1.0.228", default-features = false } serde_json = "1.0" thiserror = "2.0.18" tokio = { version = "1.52", features = ["rt-multi-thread"] } -anoma-pa-evm-bindings = { path = "bindings" } +anoma-pa-evm-bindings = { path = "crates/bindings" } +anoma-pa-evm-integration-test = { path = "crates/integration-test" } + +# Backend-agnostic risc0 testkit, pinned by git rev (see anoma-pa-testkit +# ADR-0002). For cross-repo development, override locally with a `[patch]` to a +# path checkout. +anoma-pa-testkit = { git = "https://github.com/anoma/pa-testkit", rev = "74cf6ad7cd488021128c34662e68171c7ae28e6d" } +anoma-risc0-verifier-bindings = "1.0.0-rc.0" diff --git a/README.md b/README.md index d555160d..5cf26834 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Contracts Tests](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml/badge.svg)](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml) [![soldeer.xyz](https://img.shields.io/badge/soldeer.xyz-anoma--pa--evm-blue?logo=ethereum)](https://soldeer.xyz/project/anoma-pa-evm) [![License](https://img.shields.io/badge/license-MIT-blue)](https://raw.githubusercontent.com/anoma/pa-evm/refs/heads/main/bindings/LICENSE) +[![Contracts Tests](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml/badge.svg)](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml) [![soldeer.xyz](https://img.shields.io/badge/soldeer.xyz-anoma--pa--evm-blue?logo=ethereum)](https://soldeer.xyz/project/anoma-pa-evm) [![License](https://img.shields.io/badge/license-MIT-blue)](https://raw.githubusercontent.com/anoma/pa-evm/refs/heads/main/contracts/LICENSE) -[![Bindings Tests](https://github.com/anoma/pa-evm/actions/workflows/bindings.yml/badge.svg)](https://github.com/anoma/pa-evm/actions/workflows/bindings.yml) [![crates.io](https://img.shields.io/badge/crates.io-anoma--pa--evm--bindings-blue?logo=rust)](https://crates.io/crates/anoma-pa-evm-bindings) [![License](https://img.shields.io/badge/license-MIT-blue)](https://raw.githubusercontent.com/anoma/pa-evm/refs/heads/main/bindings/LICENSE) +[![Crates Tests](https://github.com/anoma/pa-evm/actions/workflows/crates.yml/badge.svg)](https://github.com/anoma/pa-evm/actions/workflows/crates.yml) [![crates.io](https://img.shields.io/badge/crates.io-anoma--pa--evm--bindings-blue?logo=rust)](https://crates.io/crates/anoma-pa-evm-bindings) [![License](https://img.shields.io/badge/license-MIT-blue)](https://raw.githubusercontent.com/anoma/pa-evm/refs/heads/main/crates/bindings/LICENSE) # Anoma EVM Protocol Adapter @@ -14,9 +14,11 @@ This monorepo is structured as follows: ``` . ├── audits -├── bindings ├── contracts -├── example-tx-generation +├── crates +│ ├── bindings +│ ├── example-tx-generation +│ └── integration-test ├── Cargo.lock ├── Cargo.toml ├── README.md @@ -25,10 +27,11 @@ This monorepo is structured as follows: The [contracts](./contracts/) folder contains the contracts written in [Solidity](https://soliditylang.org/) as well as [Foundry forge](https://book.getfoundry.sh/forge/) tests and deploy scripts. -The [bindings](./bindings/) folder provides [Rust](https://www.rust-lang.org/) bindings for the conversion of Rust and [RISC Zero](https://risczero.com/) types into [EVM types](https://docs.soliditylang.org/en/latest/types.html) and exposes the deployment addresses on the different supported networks using the [alloy-rs](https://github.com/alloy-rs) -library. +The [crates](./crates/) folder contains the Rust workspace: -The [example-tx-generation](./example-tx-generation) folder contains a binary to generate example transactions with aggregated and non-aggregated proofs as `.bin` files for testing purposes, which can be used for testing purposes, e.g., in [./contracts/test/examples/transactions/](./contracts/test/examples/transactions/). +- [bindings](./crates/bindings/) provides [Rust](https://www.rust-lang.org/) bindings for the conversion of Rust and [RISC Zero](https://risczero.com/) types into [EVM types](https://docs.soliditylang.org/en/latest/types.html) and exposes the deployment addresses on the different supported networks using the [alloy-rs](https://github.com/alloy-rs) library. +- [example-tx-generation](./crates/example-tx-generation/) contains a binary to generate example transactions with aggregated and non-aggregated proofs as `.bin` files for testing purposes, e.g., in [./contracts/test/examples/transactions/](./contracts/test/examples/transactions/). +- [integration-test](./crates/integration-test/) contains the Rust integration and e2e test harness that deploys the protocol adapter to a local or forked chain and exercises it with risc0-proven transactions. ## Audits diff --git a/contracts/README.md b/contracts/README.md index 9d3a7071..039eb272 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -1,4 +1,4 @@ -[![Contracts Tests](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml/badge.svg)](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml) [![soldeer.xyz](https://img.shields.io/badge/soldeer.xyz-anoma--pa--evm-blue?logo=ethereum)](https://soldeer.xyz/project/anoma-pa-evm) [![License](https://img.shields.io/badge/license-MIT-blue)](https://raw.githubusercontent.com/anoma/pa-evm/refs/heads/main/bindings/LICENSE) +[![Contracts Tests](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml/badge.svg)](https://github.com/anoma/pa-evm/actions/workflows/contracts.yml) [![soldeer.xyz](https://img.shields.io/badge/soldeer.xyz-anoma--pa--evm-blue?logo=ethereum)](https://soldeer.xyz/project/anoma-pa-evm) [![License](https://img.shields.io/badge/license-MIT-blue)](https://raw.githubusercontent.com/anoma/pa-evm/refs/heads/main/contracts/LICENSE) # Anoma EVM Protocol Adapter Contract diff --git a/bindings/Cargo.toml b/crates/bindings/Cargo.toml similarity index 100% rename from bindings/Cargo.toml rename to crates/bindings/Cargo.toml diff --git a/bindings/LICENSE b/crates/bindings/LICENSE similarity index 100% rename from bindings/LICENSE rename to crates/bindings/LICENSE diff --git a/bindings/README.md b/crates/bindings/README.md similarity index 100% rename from bindings/README.md rename to crates/bindings/README.md diff --git a/bindings/deployments.json b/crates/bindings/deployments.json similarity index 100% rename from bindings/deployments.json rename to crates/bindings/deployments.json diff --git a/bindings/src/addresses.rs b/crates/bindings/src/addresses.rs similarity index 100% rename from bindings/src/addresses.rs rename to crates/bindings/src/addresses.rs diff --git a/bindings/src/contract.rs b/crates/bindings/src/contract.rs similarity index 100% rename from bindings/src/contract.rs rename to crates/bindings/src/contract.rs diff --git a/bindings/src/conversion.rs b/crates/bindings/src/conversion.rs similarity index 100% rename from bindings/src/conversion.rs rename to crates/bindings/src/conversion.rs diff --git a/bindings/src/error.rs b/crates/bindings/src/error.rs similarity index 100% rename from bindings/src/error.rs rename to crates/bindings/src/error.rs diff --git a/bindings/src/generated/i_commitment_tree.rs b/crates/bindings/src/generated/i_commitment_tree.rs similarity index 100% rename from bindings/src/generated/i_commitment_tree.rs rename to crates/bindings/src/generated/i_commitment_tree.rs diff --git a/bindings/src/generated/i_nullifier_set.rs b/crates/bindings/src/generated/i_nullifier_set.rs similarity index 100% rename from bindings/src/generated/i_nullifier_set.rs rename to crates/bindings/src/generated/i_nullifier_set.rs diff --git a/bindings/src/generated/i_protocol_adapter.rs b/crates/bindings/src/generated/i_protocol_adapter.rs similarity index 100% rename from bindings/src/generated/i_protocol_adapter.rs rename to crates/bindings/src/generated/i_protocol_adapter.rs diff --git a/bindings/src/generated/mod.rs b/crates/bindings/src/generated/mod.rs similarity index 100% rename from bindings/src/generated/mod.rs rename to crates/bindings/src/generated/mod.rs diff --git a/bindings/src/generated/protocol_adapter.rs b/crates/bindings/src/generated/protocol_adapter.rs similarity index 100% rename from bindings/src/generated/protocol_adapter.rs rename to crates/bindings/src/generated/protocol_adapter.rs diff --git a/bindings/src/helpers.rs b/crates/bindings/src/helpers.rs similarity index 100% rename from bindings/src/helpers.rs rename to crates/bindings/src/helpers.rs diff --git a/bindings/src/lib.rs b/crates/bindings/src/lib.rs similarity index 100% rename from bindings/src/lib.rs rename to crates/bindings/src/lib.rs diff --git a/bindings/tests/contract.rs b/crates/bindings/tests/contract.rs similarity index 100% rename from bindings/tests/contract.rs rename to crates/bindings/tests/contract.rs diff --git a/bindings/tests/conversion.rs b/crates/bindings/tests/conversion.rs similarity index 100% rename from bindings/tests/conversion.rs rename to crates/bindings/tests/conversion.rs diff --git a/bindings/tests/deployments.rs b/crates/bindings/tests/deployments.rs similarity index 100% rename from bindings/tests/deployments.rs rename to crates/bindings/tests/deployments.rs diff --git a/example-tx-generation/Cargo.toml b/crates/example-tx-generation/Cargo.toml similarity index 100% rename from example-tx-generation/Cargo.toml rename to crates/example-tx-generation/Cargo.toml diff --git a/example-tx-generation/LICENSE b/crates/example-tx-generation/LICENSE similarity index 100% rename from example-tx-generation/LICENSE rename to crates/example-tx-generation/LICENSE diff --git a/example-tx-generation/README.md b/crates/example-tx-generation/README.md similarity index 100% rename from example-tx-generation/README.md rename to crates/example-tx-generation/README.md diff --git a/example-tx-generation/src/main.rs b/crates/example-tx-generation/src/main.rs similarity index 100% rename from example-tx-generation/src/main.rs rename to crates/example-tx-generation/src/main.rs diff --git a/crates/integration-test/Cargo.toml b/crates/integration-test/Cargo.toml new file mode 100644 index 00000000..8f47fc48 --- /dev/null +++ b/crates/integration-test/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "anoma-pa-evm-integration-test" +description = "Integration and e2e tests for the Anoma EVM protocol adapter." +version = "0.1.0" +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +edition.workspace = true +publish = false + +[features] +default = ["local"] +local = ["anoma-pa-testkit/local"] +e2e = ["anoma-pa-testkit/e2e", "dep:dotenvy"] + +[dependencies] +anoma-pa-testkit = { workspace = true, features = ["fixtures"] } +anoma-pa-evm-bindings = { workspace = true } +anoma-risc0-verifier-bindings = { workspace = true } + +alloy = { workspace = true, features = ["json", "signer-local", "sol-types"] } +alloy-chains = { workspace = true } +anoma-rm-risc0 = { workspace = true } +anyhow = { workspace = true } +dotenvy = { workspace = true, optional = true } +hex = { workspace = true } +risc0-zkvm = { workspace = true } + +[dev-dependencies] +anoma-pa-testkit = { workspace = true, features = ["fixtures", "mocks"] } +bincode = { workspace = true } +regex = { workspace = true } +rstest = { workspace = true } +tokio = { workspace = true, features = ["macros"] } diff --git a/crates/integration-test/src/deploy/mock_risc0_bindings.rs b/crates/integration-test/src/deploy/mock_risc0_bindings.rs new file mode 100644 index 00000000..6c9d781f --- /dev/null +++ b/crates/integration-test/src/deploy/mock_risc0_bindings.rs @@ -0,0 +1,55 @@ +//! Deploys risc0's mock verifier stack for the local environment — the +//! `RiscZeroMockVerifier` wrapped in a `RiscZeroVerifierEmergencyStop` and +//! registered in a `RiscZeroVerifierRouter`, mirroring risc0-ethereum's +//! `DeployRiscZeroContractsMock` script. The contract bindings come from +//! `anoma-risc0-verifier-bindings`. The mock verifier accepts seals of the +//! form `SELECTOR ++ ReceiptClaim::ok(imageId, journalDigest).digest()`, which is +//! exactly what the testkit's local prover emits. + +use alloy::primitives::Address; +use alloy::primitives::FixedBytes; +use alloy::providers::DynProvider; +use anoma_risc0_verifier_bindings::generated::risc_zero_mock_verifier::RiscZeroMockVerifier; +use anoma_risc0_verifier_bindings::generated::risc_zero_verifier_emergency_stop::RiscZeroVerifierEmergencyStop; +use anoma_risc0_verifier_bindings::generated::risc_zero_verifier_router::RiscZeroVerifierRouter; +use anyhow::Context; + +/// Selector the mock verifier is deployed and registered under (matches +/// `MOCK_VERIFIER_SELECTOR` in risc0-ethereum's mock deploy script). +pub const MOCK_VERIFIER_SELECTOR: [u8; 4] = [0xFF, 0xFF, 0xFF, 0xFF]; + +#[derive(Clone)] +pub struct MockRisc0Stack { + pub router: RiscZeroVerifierRouter::RiscZeroVerifierRouterInstance, +} + +pub async fn deploy_mock_risc0_stack( + provider: &DynProvider, + guardian: Address, +) -> anyhow::Result { + let selector = FixedBytes::<4>::from(MOCK_VERIFIER_SELECTOR); + + let mock_verifier = RiscZeroMockVerifier::deploy(provider.clone(), selector) + .await + .context("failed to deploy RiscZeroMockVerifier")?; + + let emergency_stop = + RiscZeroVerifierEmergencyStop::deploy(provider.clone(), *mock_verifier.address(), guardian) + .await + .context("failed to deploy RiscZeroVerifierEmergencyStop")?; + + let router = RiscZeroVerifierRouter::deploy(provider.clone(), guardian) + .await + .context("failed to deploy RiscZeroVerifierRouter")?; + + router + .addVerifier(selector, *emergency_stop.address()) + .send() + .await + .context("failed to submit addVerifier call to verifier router")? + .get_receipt() + .await + .context("failed to fetch addVerifier receipt from verifier router")?; + + Ok(MockRisc0Stack { router }) +} diff --git a/crates/integration-test/src/deploy/mod.rs b/crates/integration-test/src/deploy/mod.rs new file mode 100644 index 00000000..3d2b0fed --- /dev/null +++ b/crates/integration-test/src/deploy/mod.rs @@ -0,0 +1,5 @@ +//! Contract deploy helpers and bindings for the EVM protocol adapter: +//! the protocol-adapter instance helper and the mock risc0 verifier stack. + +pub mod mock_risc0_bindings; +pub mod pa; diff --git a/crates/integration-test/src/deploy/pa.rs b/crates/integration-test/src/deploy/pa.rs new file mode 100644 index 00000000..530ff693 --- /dev/null +++ b/crates/integration-test/src/deploy/pa.rs @@ -0,0 +1,14 @@ +use alloy::primitives::Address; +use alloy::providers::Provider; +use anoma_pa_evm_bindings::generated::protocol_adapter::ProtocolAdapter; + +#[inline] +pub fn protocol_adapter

( + address: Address, + provider: P, +) -> ProtocolAdapter::ProtocolAdapterInstance

+where + P: Provider, +{ + ProtocolAdapter::ProtocolAdapterInstance::new(address, provider) +} diff --git a/crates/integration-test/src/envs.rs b/crates/integration-test/src/envs.rs new file mode 100644 index 00000000..f807aa2e --- /dev/null +++ b/crates/integration-test/src/envs.rs @@ -0,0 +1,8 @@ +#[cfg(any(feature = "local", feature = "e2e"))] +mod common; + +#[cfg(feature = "local")] +pub mod local; + +#[cfg(feature = "e2e")] +pub mod e2e; diff --git a/crates/integration-test/src/envs/common/execute.rs b/crates/integration-test/src/envs/common/execute.rs new file mode 100644 index 00000000..97d5831b --- /dev/null +++ b/crates/integration-test/src/envs/common/execute.rs @@ -0,0 +1,224 @@ +use alloy::providers::DynProvider; +use alloy::providers::Provider; +use alloy::rpc::types::TransactionTrait; +use anoma_pa_evm_bindings::generated::protocol_adapter::ProtocolAdapter as PaContract; +use anyhow::Context; + +pub(super) const ERROR_STRING_SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0]; +pub(super) const PANIC_SELECTOR: [u8; 4] = [0x4e, 0x48, 0x7b, 0x71]; + +pub(in crate::envs) async fn execute_on_pa( + pa: &PaContract::ProtocolAdapterInstance, + tx: PaContract::Transaction, +) -> anyhow::Result { + preflight_check(pa, tx.clone()).await?; + + let receipt = pa + .execute(tx) + .send() + .await + .context("failed to submit protocol adapter execution transaction")? + .get_receipt() + .await + .context("failed to fetch protocol adapter execution receipt")?; + + if !receipt.status() { + let gas_diagnostic = gas_diagnostic(pa, &receipt).await; + anyhow::bail!( + "protocol adapter execution failed: tx_hash={:?}, block_number={:?}, \ + gas_used={}, effective_gas_price={:?}, logs={}, \ + gas_diagnostic={}", + receipt.transaction_hash, + receipt.block_number, + receipt.gas_used, + receipt.effective_gas_price, + receipt.logs().len(), + gas_diagnostic, + ); + } + + Ok(receipt) +} + +async fn preflight_check( + pa: &PaContract::ProtocolAdapterInstance, + tx: PaContract::Transaction, +) -> anyhow::Result<()> { + match pa.execute(tx).call().await { + Ok(_) => Ok(()), + Err(err) => anyhow::bail!( + "protocol adapter preflight failed: {}", + decode_revert_detail(&err.to_string()) + ), + } +} + +async fn gas_diagnostic( + pa: &PaContract::ProtocolAdapterInstance, + receipt: &alloy::rpc::types::TransactionReceipt, +) -> String { + let tx_lookup = pa + .provider() + .get_transaction_by_hash(receipt.transaction_hash) + .await + .context("failed to fetch protocol adapter transaction by hash for diagnostics"); + + match tx_lookup { + Ok(Some(chain_tx)) => { + let gas_limit = chain_tx.gas_limit(); + if receipt.gas_used == gas_limit { + format!("gas_limit={gas_limit}, possible_out_of_gas=true") + } else { + format!("gas_limit={gas_limit}, possible_out_of_gas=false") + } + } + Ok(None) => "transaction not found by hash".to_string(), + Err(err) => format!("lookup failed ({err:#})"), + } +} + +pub(super) fn decode_revert_detail(err: &str) -> String { + let Some(raw) = find_hex_payload(err) else { + return err.to_string(); + }; + + let bytes = match hex::decode(&raw[2..]) { + Ok(bytes) => bytes, + Err(_) => return err.to_string(), + }; + + if bytes.len() < 4 { + return format!("{err}; revert_data={raw}"); + } + + let selector = [bytes[0], bytes[1], bytes[2], bytes[3]]; + if selector == ERROR_STRING_SELECTOR + && let Some(reason) = decode_error_string_payload(&bytes[4..]) + { + return format!("execution reverted: {reason}"); + } + + if selector == PANIC_SELECTOR + && bytes.len() >= 36 + && let Some(code) = decode_word_usize(&bytes[4..36]) + { + return format!("execution panicked with code 0x{code:x}"); + } + + format!("{err}; selector=0x{}; revert_data={raw}", &raw[2..10]) +} + +pub(super) fn find_hex_payload(input: &str) -> Option<&str> { + input + .split(|c: char| c.is_whitespace() || c == ',' || c == ')' || c == '(') + .find(|token| { + token.starts_with("0x") + && token.len() > 10 + && token.len() % 2 == 0 + && hex::decode(&token[2..]).is_ok() + }) +} + +fn decode_error_string_payload(payload: &[u8]) -> Option { + if payload.len() < 64 { + return None; + } + + let offset = decode_word_usize(&payload[0..32])?; + if offset + 32 > payload.len() { + return None; + } + + let len = decode_word_usize(&payload[offset..offset + 32])?; + let start = offset + 32; + let end = start.checked_add(len)?; + if end > payload.len() { + return None; + } + + std::str::from_utf8(&payload[start..end]) + .ok() + .map(ToOwned::to_owned) +} + +fn decode_word_usize(word: &[u8]) -> Option { + if word.len() != 32 || word[..24].iter().any(|&b| b != 0) { + return None; + } + + let mut low = [0u8; 8]; + low.copy_from_slice(&word[24..32]); + usize::try_from(u64::from_be_bytes(low)).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn find_hex_payload_returns_first_valid_hex_token() { + let err = "rpc error (data: 0xdeadbeef1234), trailing"; + let payload = find_hex_payload(err).expect("must find hex payload"); + assert_eq!(payload, "0xdeadbeef1234"); + } + + #[test] + fn decode_revert_detail_decodes_error_string() { + let revert = encode_error_string_revert("oops"); + let err = format!("execution reverted: {revert}"); + + let decoded = decode_revert_detail(&err); + assert_eq!(decoded, "execution reverted: oops"); + } + + #[test] + fn decode_revert_detail_decodes_panic_code() { + let revert = encode_panic_revert(0x11); + let err = format!("vm error with data {revert}"); + + let decoded = decode_revert_detail(&err); + assert_eq!(decoded, "execution panicked with code 0x11"); + } + + #[test] + fn decode_revert_detail_falls_back_for_unknown_selector() { + let err = "failed with data 0x12345678abcd"; + let decoded = decode_revert_detail(err); + + assert!(decoded.contains("selector=0x12345678")); + assert!(decoded.contains("revert_data=0x12345678abcd")); + } + + fn encode_error_string_revert(reason: &str) -> String { + let reason_bytes = reason.as_bytes(); + let padded_len = reason_bytes.len().div_ceil(32) * 32; + + let mut data = Vec::with_capacity(4 + 32 + 32 + padded_len); + data.extend_from_slice(&ERROR_STRING_SELECTOR); + + let mut offset = [0u8; 32]; + offset[31] = 32; + data.extend_from_slice(&offset); + + let mut len = [0u8; 32]; + len[24..32].copy_from_slice(&(reason_bytes.len() as u64).to_be_bytes()); + data.extend_from_slice(&len); + + let mut payload = vec![0u8; padded_len]; + payload[..reason_bytes.len()].copy_from_slice(reason_bytes); + data.extend_from_slice(&payload); + + format!("0x{}", hex::encode(data)) + } + + fn encode_panic_revert(code: u64) -> String { + let mut data = Vec::with_capacity(4 + 32); + data.extend_from_slice(&PANIC_SELECTOR); + + let mut word = [0u8; 32]; + word[24..32].copy_from_slice(&code.to_be_bytes()); + data.extend_from_slice(&word); + + format!("0x{}", hex::encode(data)) + } +} diff --git a/crates/integration-test/src/envs/common/mod.rs b/crates/integration-test/src/envs/common/mod.rs new file mode 100644 index 00000000..4cdcb5a8 --- /dev/null +++ b/crates/integration-test/src/envs/common/mod.rs @@ -0,0 +1,4 @@ +//! Building blocks shared by the `local` and `e2e` environments: the on-chain +//! execution and revert-diagnostics glue, identical across both. + +pub(in crate::envs) mod execute; diff --git a/crates/integration-test/src/envs/e2e/config.rs b/crates/integration-test/src/envs/e2e/config.rs new file mode 100644 index 00000000..52de5d5c --- /dev/null +++ b/crates/integration-test/src/envs/e2e/config.rs @@ -0,0 +1,47 @@ +use std::env; + +use alloy_chains::NamedChain; +use anyhow::Context; + +/// Configuration for the e2e environment, read from the process environment. +/// +/// Selects the chain to fork (and read deployed addresses for) and the proving +/// queue to submit to. The fork RPC and the per-chain protocol-adapter address +/// are resolved from `anoma-pa-evm-bindings`; only the queue connection lives +/// here. +pub struct E2eConfig { + /// Chain to fork and whose deployed contracts the test targets. + pub chain: NamedChain, + /// Base URL of the remote proving queue. + pub queue_base_url: String, + /// Auth token for the remote proving queue. + pub queue_auth_token: String, +} + +impl E2eConfig { + /// Read the config from the environment. + /// + /// `E2E_CHAIN_ID` selects the chain (defaults to Base Sepolia); + /// `QUEUE_BASE_URL` and `QUEUE_AUTH_TOKEN` configure the proving queue. + pub fn from_env() -> anyhow::Result { + dotenvy::dotenv().ok(); + + let chain = match env::var("E2E_CHAIN_ID") { + Ok(raw) => { + let chain_id: u64 = raw + .parse() + .context("E2E_CHAIN_ID must be a numeric chain id")?; + NamedChain::try_from(chain_id) + .map_err(|_| anyhow::anyhow!("unsupported E2E_CHAIN_ID {chain_id}"))? + } + Err(_) => NamedChain::BaseSepolia, + }; + + Ok(Self { + chain, + queue_base_url: env::var("QUEUE_BASE_URL").context("failed to read QUEUE_BASE_URL")?, + queue_auth_token: env::var("QUEUE_AUTH_TOKEN") + .context("failed to read QUEUE_AUTH_TOKEN")?, + }) + } +} diff --git a/crates/integration-test/src/envs/e2e/mod.rs b/crates/integration-test/src/envs/e2e/mod.rs new file mode 100644 index 00000000..464b6479 --- /dev/null +++ b/crates/integration-test/src/envs/e2e/mod.rs @@ -0,0 +1,159 @@ +use alloy::node_bindings::AnvilInstance; +use alloy::primitives::B256; +use alloy::providers::DynProvider; +use anoma_pa_evm_bindings::generated::protocol_adapter::ProtocolAdapter as PaContract; +use anoma_pa_testkit::environment::CommitmentTree as CoreCommitmentTree; +use anoma_pa_testkit::environment::Environment as CoreEnvironment; +use anoma_pa_testkit::environment::ProtocolAdapter as CoreProtocolAdapter; +use anoma_pa_testkit::environment::State; +use anoma_pa_testkit::environment::Transaction as CoreTransaction; +use anoma_rm_risc0::action_tree::MerkleTree as ArmTree; +use anoma_rm_risc0::merkle_path::MerklePath; +use anoma_rm_risc0::transaction::Transaction as ArmTxn; +use anyhow::Context; +use risc0_zkvm::Digest; + +pub mod config; +mod setup; + +pub use anoma_pa_testkit::transaction::Transaction; +pub use config::E2eConfig; + +pub struct Environment { + pub anvil: AnvilInstance, + pub state: State, + pub prover: anoma_pa_testkit::prover::QueueProver, + pub protocol_adapter: ProtocolAdapter, +} + +pub struct ProtocolAdapter { + pub pa: PaContract::ProtocolAdapterInstance, + pub commitment_tree: CommitmentTree, +} + +#[derive(Default)] +pub struct CommitmentTree { + leaves: Vec, +} + +impl CoreEnvironment for Environment { + type Transaction = Transaction; + type ProtocolAdapter = ProtocolAdapter; + type Prover = anoma_pa_testkit::prover::QueueProver; + + fn prover(&self) -> &Self::Prover { + &self.prover + } + + fn state(&self) -> &State { + &self.state + } + + fn state_mut(&mut self) -> &mut State { + &mut self.state + } + + fn protocol_adapter(&self) -> &Self::ProtocolAdapter { + &self.protocol_adapter + } + + fn protocol_adapter_mut(&mut self) -> &mut Self::ProtocolAdapter { + &mut self.protocol_adapter + } +} + +impl CoreProtocolAdapter for ProtocolAdapter { + type Transaction = Transaction; + type CommitmentTree = CommitmentTree; + + async fn execute(&mut self, transaction: Self::Transaction) -> anyhow::Result<()> { + let created_commitments: Vec = transaction.created_commitments()?.collect(); + let tx = transaction.into_arm(); + + self.assert_root_consistency(&tx).await?; + + let pa_tx: PaContract::Transaction = tx.into(); + + crate::envs::common::execute::execute_on_pa(&self.pa, pa_tx).await?; + + self.commitment_tree.leaves.extend(created_commitments); + + Ok(()) + } + + fn commitment_tree(&self) -> &Self::CommitmentTree { + &self.commitment_tree + } +} + +impl ProtocolAdapter { + async fn assert_root_consistency(&self, tx: &ArmTxn) -> anyhow::Result<()> { + let local_root = self.commitment_tree.root()?; + let pa_root = self + .pa + .latestCommitmentTreeRoot() + .call() + .await + .context("failed to query latest commitment tree root from protocol adapter")?; + + let local_root_b256 = B256::from_slice(local_root.as_bytes()); + anyhow::ensure!( + local_root_b256 == pa_root, + "commitment tree root mismatch before execution: local={local_root_b256:?}, pa={pa_root:?}" + ); + + for (action_idx, action) in tx.actions.iter().enumerate() { + for (unit_idx, unit) in action.compliance_units.iter().enumerate() { + let instance = unit.get_instance().with_context(|| { + format!("failed to decode compliance instance for action {action_idx} unit {unit_idx}") + })?; + + let consumed_root = + B256::from_slice(instance.consumed_commitment_tree_root.as_bytes()); + let contained = self + .pa + .isCommitmentTreeRootContained(consumed_root) + .call() + .await + .with_context(|| { + format!( + "failed to query root containment for action {action_idx} unit {unit_idx}" + ) + })?; + + anyhow::ensure!( + contained, + "consumed commitment tree root not found in PA for action {action_idx} unit \ + {unit_idx}: root={consumed_root:?}, pa_latest={pa_root:?}, \ + local_latest={local_root_b256:?}" + ); + } + } + + Ok(()) + } +} + +impl CoreCommitmentTree for CommitmentTree { + fn root(&self) -> anyhow::Result { + if self.leaves.is_empty() { + return Ok(*anoma_rm_risc0::compliance::INITIAL_ROOT); + } + + Ok(self.build_tree().root()?) + } + + fn path_to(&self, leaf: Digest) -> anyhow::Result { + Ok(self.build_tree().generate_path(&leaf)?) + } +} + +impl CommitmentTree { + fn build_tree(&self) -> ArmTree { + let mut leaves = self.leaves.clone(); + if leaves.is_empty() || leaves.len().is_power_of_two() { + leaves.push(*anoma_rm_risc0::merkle_path::PADDING_LEAF); + } + ArmTree::new(leaves) + } +} diff --git a/crates/integration-test/src/envs/e2e/setup.rs b/crates/integration-test/src/envs/e2e/setup.rs new file mode 100644 index 00000000..4c0f42e7 --- /dev/null +++ b/crates/integration-test/src/envs/e2e/setup.rs @@ -0,0 +1,94 @@ +use alloy::node_bindings::Anvil; +use alloy::primitives::utils::parse_ether; +use alloy::providers::Provider; +use alloy::providers::ProviderBuilder; +use alloy::providers::ext::AnvilApi; +use anoma_pa_evm_bindings::addresses::protocol_adapter_address; +use anoma_pa_evm_bindings::helpers::alchemy_url; +use anoma_pa_testkit::environment::StateBuilder; +use anoma_pa_testkit::fixtures::identities; + +use crate::keychain::EvmSigner; +use anoma_pa_testkit::prover::QueueProver; +use anyhow::Context; + +use crate::deploy::pa::protocol_adapter; +use crate::state::actors::insert_default_signer; +use crate::state::chains::insert_chain; +use crate::state::pa::insert_pa_address; + +use super::CommitmentTree; +use super::Environment; +use super::ProtocolAdapter; +use super::config::E2eConfig; + +impl Environment { + pub async fn setup_bare() -> anyhow::Result { + Self::setup(async |_| anyhow::Ok(())).await + } + + pub async fn setup(insert_additional: F) -> anyhow::Result + where + F: AsyncFnOnce(&mut StateBuilder) -> anyhow::Result<()>, + { + let config = E2eConfig::from_env().context("failed to parse e2e test config")?; + let chain = config.chain; + + // Fork the real chain. The protocol adapter is already deployed there, so + // we read its address from the bindings rather than deploying a fresh one; + // forking keeps real on-chain state from being mutated. + let fork_url = alchemy_url(&chain) + .with_context(|| format!("failed to resolve fork RPC url for chain {chain:?}"))?; + + // Forking a live chain can be slow; give anvil a generous boot window. + let anvil = Anvil::new() + .fork(fork_url.to_string()) + .timeout(30_000) // 30 seconds + .spawn(); + + let signer = identities::alice()?.signer(); + let deployer = signer.address(); + + let provider = ProviderBuilder::new() + .wallet(signer) + .connect_http(anvil.endpoint_url()) + .erased(); + + provider + .anvil_set_balance( + deployer, + parse_ether("100").context("failed to parse deployer balance amount")?, + ) + .await?; + + let pa_address = protocol_adapter_address(&chain) + .with_context(|| format!("no protocol adapter deployment for chain {chain:?}"))?; + let pa = protocol_adapter(pa_address, provider.clone()); + + let prover = QueueProver::new(&config.queue_base_url, &config.queue_auth_token) + .context("failed to build queue prover")?; + + let state = { + let mut builder = StateBuilder::new(); + + insert_default_signer(&mut builder, provider.clone()); + insert_chain(&mut builder, chain); + insert_pa_address(&mut builder, pa_address); + insert_additional(&mut builder) + .await + .context("failed to insert additional data into state")?; + + builder.finalize() + }; + + Ok(Self { + anvil, + state, + prover, + protocol_adapter: ProtocolAdapter { + pa, + commitment_tree: CommitmentTree::default(), + }, + }) + } +} diff --git a/crates/integration-test/src/envs/local/mod.rs b/crates/integration-test/src/envs/local/mod.rs new file mode 100644 index 00000000..52918dad --- /dev/null +++ b/crates/integration-test/src/envs/local/mod.rs @@ -0,0 +1,164 @@ +use alloy::node_bindings::AnvilInstance; +use alloy::primitives::B256; +use alloy::providers::DynProvider; +use anoma_pa_evm_bindings::generated::protocol_adapter::ProtocolAdapter as PaContract; +use anoma_pa_testkit::environment::CommitmentTree as CoreCommitmentTree; +use anoma_pa_testkit::environment::Environment as CoreEnvironment; +use anoma_pa_testkit::environment::ProtocolAdapter as CoreProtocolAdapter; +use anoma_pa_testkit::environment::State; +use anoma_pa_testkit::environment::Transaction as CoreTransaction; +use anoma_rm_risc0::action_tree::MerkleTree as ArmTree; +use anoma_rm_risc0::merkle_path::MerklePath; +use anoma_rm_risc0::transaction::Transaction as ArmTxn; +use anyhow::Context; +use risc0_zkvm::Digest; + +mod setup; + +pub use anoma_pa_testkit::transaction::Transaction; + +/// Integration test execution environment. +/// +/// Setup contract: +/// - All fields on this environment and its nested structures are public on purpose. +/// - Setup code should mutate/inspect the concrete environment directly. +/// - Test execution code should accept `impl anoma_pa_testkit::environment::Environment` +/// and use typed state helpers instead of concrete fields. +pub struct Environment { + pub anvil: AnvilInstance, + pub state: State, + pub prover: anoma_pa_testkit::prover::LocalProver, + pub protocol_adapter: ProtocolAdapter, +} + +pub struct ProtocolAdapter { + pub pa: PaContract::ProtocolAdapterInstance, + pub commitment_tree: CommitmentTree, +} + +#[derive(Default)] +pub struct CommitmentTree { + leaves: Vec, +} + +impl CoreEnvironment for Environment { + type Transaction = Transaction; + type ProtocolAdapter = ProtocolAdapter; + type Prover = anoma_pa_testkit::prover::LocalProver; + + fn prover(&self) -> &Self::Prover { + &self.prover + } + + fn state(&self) -> &State { + &self.state + } + + fn state_mut(&mut self) -> &mut State { + &mut self.state + } + + fn protocol_adapter(&self) -> &Self::ProtocolAdapter { + &self.protocol_adapter + } + + fn protocol_adapter_mut(&mut self) -> &mut Self::ProtocolAdapter { + &mut self.protocol_adapter + } +} + +impl CoreProtocolAdapter for ProtocolAdapter { + type Transaction = Transaction; + type CommitmentTree = CommitmentTree; + + async fn execute(&mut self, transaction: Self::Transaction) -> anyhow::Result<()> { + let created_commitments: Vec = transaction.created_commitments()?.collect(); + let tx = transaction.into_arm(); + + self.assert_root_consistency(&tx).await?; + + let pa_tx: PaContract::Transaction = tx.into(); + + crate::envs::common::execute::execute_on_pa(&self.pa, pa_tx).await?; + + self.commitment_tree.leaves.extend(created_commitments); + + Ok(()) + } + + fn commitment_tree(&self) -> &Self::CommitmentTree { + &self.commitment_tree + } +} + +impl ProtocolAdapter { + async fn assert_root_consistency(&self, tx: &ArmTxn) -> anyhow::Result<()> { + let local_root = self.commitment_tree.root()?; + let pa_root = self + .pa + .latestCommitmentTreeRoot() + .call() + .await + .context("failed to query latest commitment tree root from protocol adapter")?; + + let local_root_b256 = B256::from_slice(local_root.as_bytes()); + anyhow::ensure!( + local_root_b256 == pa_root, + "commitment tree root mismatch before execution: local={local_root_b256:?}, pa={pa_root:?}" + ); + + for (action_idx, action) in tx.actions.iter().enumerate() { + for (unit_idx, unit) in action.compliance_units.iter().enumerate() { + let instance = unit.get_instance().with_context(|| { + format!("failed to decode compliance instance for action {action_idx} unit {unit_idx}") + })?; + + let consumed_root = + B256::from_slice(instance.consumed_commitment_tree_root.as_bytes()); + let contained = self + .pa + .isCommitmentTreeRootContained(consumed_root) + .call() + .await + .with_context(|| { + format!( + "failed to query root containment for action {action_idx} unit {unit_idx}" + ) + })?; + + anyhow::ensure!( + contained, + "consumed commitment tree root not found in PA for action {action_idx} unit \ + {unit_idx}: root={consumed_root:?}, pa_latest={pa_root:?}, \ + local_latest={local_root_b256:?}" + ); + } + } + + Ok(()) + } +} + +impl CoreCommitmentTree for CommitmentTree { + fn root(&self) -> anyhow::Result { + if self.leaves.is_empty() { + return Ok(*anoma_rm_risc0::compliance::INITIAL_ROOT); + } + + Ok(self.build_tree().root()?) + } + + fn path_to(&self, leaf: Digest) -> anyhow::Result { + Ok(self.build_tree().generate_path(&leaf)?) + } +} + +impl CommitmentTree { + fn build_tree(&self) -> ArmTree { + let mut leaves = self.leaves.clone(); + if leaves.is_empty() || leaves.len().is_power_of_two() { + leaves.push(*anoma_rm_risc0::merkle_path::PADDING_LEAF); + } + ArmTree::new(leaves) + } +} diff --git a/crates/integration-test/src/envs/local/setup.rs b/crates/integration-test/src/envs/local/setup.rs new file mode 100644 index 00000000..0a9cf08e --- /dev/null +++ b/crates/integration-test/src/envs/local/setup.rs @@ -0,0 +1,103 @@ +use alloy::node_bindings::Anvil; +use alloy::primitives::utils::parse_ether; +use alloy::providers::Provider; +use alloy::providers::ProviderBuilder; +use alloy::providers::ext::AnvilApi; +use alloy_chains::NamedChain; +use anoma_pa_testkit::environment::StateBuilder; +use anoma_pa_testkit::fixtures::identities; + +use crate::keychain::EvmSigner; +use anyhow::Context; + +use crate::deploy::pa::protocol_adapter; +use crate::state::actors::insert_default_signer; +use crate::state::chains::insert_chain; +use crate::state::pa::insert_pa_address; + +use super::{CommitmentTree, Environment, ProtocolAdapter}; +use crate::deploy::mock_risc0_bindings::{MOCK_VERIFIER_SELECTOR, deploy_mock_risc0_stack}; + +impl Environment { + pub async fn setup_bare() -> anyhow::Result { + Self::setup(async |_| anyhow::Ok(())).await + } + + pub async fn setup(insert_additional: F) -> anyhow::Result + where + F: AsyncFnOnce(&mut StateBuilder) -> anyhow::Result<()>, + { + let anvil = Anvil::new().spawn(); + + let signer = identities::alice()?.signer(); + let deployer = signer.address(); + + let provider = ProviderBuilder::new() + .wallet(signer) + .connect_http(anvil.endpoint_url()) + .erased(); + + provider + .anvil_set_balance( + deployer, + parse_ether("100").context("failed to parse deployer balance amount")?, + ) + .await?; + + let pa_address = deploy_protocol_adapter(&provider, deployer).await?; + let pa = protocol_adapter(pa_address, provider.clone()); + + let chain_id = provider.get_chain_id().await?; + let named_chain = NamedChain::try_from(chain_id) + .with_context(|| format!("unsupported chain id {chain_id}"))?; + + let state = { + let mut builder = StateBuilder::new(); + + insert_default_signer(&mut builder, provider.clone()); + insert_chain(&mut builder, named_chain); + insert_pa_address(&mut builder, pa_address); + insert_additional(&mut builder) + .await + .context("failed to insert additional data into state")?; + + builder.finalize() + }; + + Ok(Self { + anvil, + state, + prover: anoma_pa_testkit::prover::LocalProver, + protocol_adapter: ProtocolAdapter { + pa, + commitment_tree: CommitmentTree::default(), + }, + }) + } +} + +/// Deploys a protocol adapter backed by a freshly deployed mock Risc0 verifier +/// stack, returning its address. +async fn deploy_protocol_adapter( + default_signer: &alloy::providers::DynProvider, + fee_recipient: alloy::primitives::Address, +) -> anyhow::Result { + use alloy::primitives::FixedBytes; + use anoma_pa_evm_bindings::generated::protocol_adapter::ProtocolAdapter; + + let mock_risc0 = deploy_mock_risc0_stack(default_signer, fee_recipient) + .await + .context("failed to deploy mock Risc0 verifier stack")?; + let selector = FixedBytes::<4>::from(MOCK_VERIFIER_SELECTOR); + + let deployed = ProtocolAdapter::deploy( + default_signer.clone(), + *mock_risc0.router.address(), + selector, + fee_recipient, + ) + .await + .context("failed to deploy protocol adapter")?; + + Ok(*deployed.address()) +} diff --git a/crates/integration-test/src/keychain.rs b/crates/integration-test/src/keychain.rs new file mode 100644 index 00000000..4c42b5f2 --- /dev/null +++ b/crates/integration-test/src/keychain.rs @@ -0,0 +1,25 @@ +//! EVM-signing extension for the testkit [`Keychain`]: derives the secp256k1 +//! signer and its address from the keychain's account key. The alloy signer type +//! lives here, in the EVM layer, not in the chain-agnostic testkit. + +use alloy::primitives::Address; +use alloy::signers::local::PrivateKeySigner; +use anoma_pa_testkit::fixtures::identities::Keychain; + +/// Adds EVM signing to a test [`Keychain`]. +pub trait EvmSigner { + /// The secp256k1 signer for this identity. + fn signer(&self) -> PrivateKeySigner; + + /// The EVM address of this identity. + fn address(&self) -> Address { + self.signer().address() + } +} + +impl EvmSigner for Keychain { + fn signer(&self) -> PrivateKeySigner { + PrivateKeySigner::from_bytes(&self.signing_key.into()) + .expect("keychain holds a valid secp256k1 key") + } +} diff --git a/crates/integration-test/src/lib.rs b/crates/integration-test/src/lib.rs new file mode 100644 index 00000000..1955a232 --- /dev/null +++ b/crates/integration-test/src/lib.rs @@ -0,0 +1,4 @@ +pub mod deploy; +pub mod envs; +pub mod keychain; +pub mod state; diff --git a/crates/integration-test/src/state/actors.rs b/crates/integration-test/src/state/actors.rs new file mode 100644 index 00000000..6f6557d3 --- /dev/null +++ b/crates/integration-test/src/state/actors.rs @@ -0,0 +1,78 @@ +use alloy::providers::DynProvider; +use anoma_pa_testkit::environment::Environment; +use anoma_pa_testkit::environment::State; +use anoma_pa_testkit::environment::StateBuilder; +use anyhow::Context; + +pub const KEY_DEFAULT_SIGNER: &str = "evm.actor.default_signer"; + +#[inline] +pub fn insert_default_signer(builder: &mut StateBuilder, provider: DynProvider) { + builder.insert(KEY_DEFAULT_SIGNER, provider); +} + +#[inline] +pub fn default_signer(env: &E) -> anyhow::Result +where + E: Environment, +{ + default_signer_in_state(env.state()) +} + +#[inline] +pub fn default_signer_in_state(state: &State) -> anyhow::Result { + state + .get::(KEY_DEFAULT_SIGNER) + .cloned() + .context("failed to retrieve default signer from env") +} + +#[cfg(test)] +mod tests { + use super::*; + + use alloy::providers::{Provider, ProviderBuilder}; + use anoma_pa_testkit::mocks::MockEnvironment; + + #[test] + fn default_signer_key_format() { + assert_eq!(KEY_DEFAULT_SIGNER, "evm.actor.default_signer"); + } + + #[test] + fn insert_and_resolve_default_signer() { + let provider = ProviderBuilder::new() + .connect_http( + "http://127.0.0.1:8545" + .parse() + .expect("valid local rpc url"), + ) + .erased(); + + let state = { + let mut builder = StateBuilder::new(); + insert_default_signer(&mut builder, provider.clone()); + builder.finalize() + }; + + let mut env = MockEnvironment::new(); + env.expect_state().return_const(state); + + let _resolved = default_signer(&env).expect("must resolve stored default signer"); + } + + #[test] + fn missing_default_signer_errors() { + let state = StateBuilder::new().finalize(); + + let mut env = MockEnvironment::new(); + env.expect_state().return_const(state); + + let err = default_signer(&env).expect_err("must fail on missing default signer key"); + assert!( + err.to_string() + .contains("failed to retrieve default signer from env"), + "error should mention missing default signer, got: {err}" + ); + } +} diff --git a/crates/integration-test/src/state/chains.rs b/crates/integration-test/src/state/chains.rs new file mode 100644 index 00000000..18b3b19a --- /dev/null +++ b/crates/integration-test/src/state/chains.rs @@ -0,0 +1,96 @@ +use alloy_chains::NamedChain; +use anoma_pa_testkit::environment::Environment; +use anoma_pa_testkit::environment::State; +use anoma_pa_testkit::environment::StateBuilder; +use anyhow::Context; + +use crate::state::keys::chain_id_key; +use crate::state::keys::chain_name_key; + +#[inline] +pub fn insert_chain(builder: &mut StateBuilder, chain: NamedChain) { + builder.insert(chain_id_key(), chain as u64); + builder.insert(chain_name_key(), chain); +} + +#[inline] +pub fn chain_id(env: &E) -> anyhow::Result +where + E: Environment, +{ + chain_id_in_state(env.state()) +} + +#[inline] +pub fn chain_id_in_state(state: &State) -> anyhow::Result { + state + .get::(chain_id_key()) + .copied() + .context("failed to retrieve chain id from env") +} + +#[inline] +pub fn chain_name(env: &E) -> anyhow::Result +where + E: Environment, +{ + chain_name_in_state(env.state()) +} + +#[inline] +pub fn chain_name_in_state(state: &State) -> anyhow::Result { + state + .get::(chain_name_key()) + .copied() + .context("failed to retrieve chain name from env") +} + +#[cfg(test)] +mod tests { + use super::*; + + use anoma_pa_testkit::mocks::MockEnvironment; + + #[test] + fn insert_and_resolve_chain_id_for_named_chain() { + let mut builder = StateBuilder::new(); + insert_chain(&mut builder, NamedChain::Sepolia); + + let state = builder.finalize(); + + let mut env = MockEnvironment::new(); + env.expect_state().return_const(state); + + let resolved = chain_id(&env).expect("must resolve chain id"); + assert_eq!(resolved, 11155111); + } + + #[test] + fn insert_chain_id_resolves_named_chain() { + let mut builder = StateBuilder::new(); + insert_chain(&mut builder, NamedChain::Mainnet); + + let state = builder.finalize(); + + let mut env = MockEnvironment::new(); + env.expect_state().return_const(state); + + let resolved = chain_name(&env).expect("must resolve chain name"); + assert_eq!(resolved, NamedChain::Mainnet); + } + + #[test] + fn missing_chain_id_key_errors() { + let state = StateBuilder::new().finalize(); + + let mut env = MockEnvironment::new(); + env.expect_state().return_const(state); + + let err = chain_id(&env).expect_err("must fail on missing key"); + assert!( + err.to_string() + .contains("failed to retrieve chain id from env"), + "error should mention missing key, got: {err}" + ); + } +} diff --git a/crates/integration-test/src/state/keys.rs b/crates/integration-test/src/state/keys.rs new file mode 100644 index 00000000..bc5625f5 --- /dev/null +++ b/crates/integration-test/src/state/keys.rs @@ -0,0 +1,23 @@ +pub const KEY_CHAIN_ID: &str = "evm.chain.id"; +pub const KEY_CHAIN_NAME: &str = "evm.chain.name"; + +#[inline] +pub fn chain_id_key() -> &'static str { + KEY_CHAIN_ID +} + +#[inline] +pub fn chain_name_key() -> &'static str { + KEY_CHAIN_NAME +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chain_state_keys_are_stable() { + assert_eq!(chain_id_key(), "evm.chain.id"); + assert_eq!(chain_name_key(), "evm.chain.name"); + } +} diff --git a/crates/integration-test/src/state/mod.rs b/crates/integration-test/src/state/mod.rs new file mode 100644 index 00000000..72175b26 --- /dev/null +++ b/crates/integration-test/src/state/mod.rs @@ -0,0 +1,4 @@ +pub mod actors; +pub mod chains; +pub mod keys; +pub mod pa; diff --git a/crates/integration-test/src/state/pa.rs b/crates/integration-test/src/state/pa.rs new file mode 100644 index 00000000..debac087 --- /dev/null +++ b/crates/integration-test/src/state/pa.rs @@ -0,0 +1,72 @@ +use alloy::primitives::Address; +use anoma_pa_testkit::environment::Environment; +use anoma_pa_testkit::environment::State; +use anoma_pa_testkit::environment::StateBuilder; +use anyhow::Context; + +pub const KEY_PA_ADDRESS: &str = "evm.pa.address"; + +#[inline] +pub fn insert_pa_address(builder: &mut StateBuilder, address: Address) { + builder.insert(KEY_PA_ADDRESS, address); +} + +#[inline] +pub fn pa_address(env: &E) -> anyhow::Result

+where + E: Environment, +{ + pa_address_in_state(env.state()) +} + +#[inline] +pub fn pa_address_in_state(state: &State) -> anyhow::Result
{ + state + .get::
(KEY_PA_ADDRESS) + .copied() + .context("failed to retrieve protocol adapter address from env") +} + +#[cfg(test)] +mod tests { + use super::*; + + use anoma_pa_testkit::mocks::MockEnvironment; + + #[test] + fn pa_address_key_format() { + assert_eq!(KEY_PA_ADDRESS, "evm.pa.address"); + } + + #[test] + fn insert_and_resolve_pa_address() { + let expected = Address::from([0x33; 20]); + + let state = { + let mut builder = StateBuilder::new(); + insert_pa_address(&mut builder, expected); + builder.finalize() + }; + + let mut env = MockEnvironment::new(); + env.expect_state().return_const(state); + + let resolved = pa_address(&env).expect("must resolve stored protocol adapter address"); + assert_eq!(resolved, expected); + } + + #[test] + fn missing_pa_address_errors() { + let state = StateBuilder::new().finalize(); + + let mut env = MockEnvironment::new(); + env.expect_state().return_const(state); + + let err = pa_address(&env).expect_err("must fail on missing protocol adapter address key"); + assert!( + err.to_string() + .contains("failed to retrieve protocol adapter address from env"), + "error should mention missing pa address, got: {err}" + ); + } +} diff --git a/crates/integration-test/tests/conversion.rs b/crates/integration-test/tests/conversion.rs new file mode 100644 index 00000000..6e598e16 --- /dev/null +++ b/crates/integration-test/tests/conversion.rs @@ -0,0 +1,21 @@ +//! Chain-free test that a locally-proven ARM transaction converts into a +//! protocol-adapter transaction. (The trivial-action / local-prover smoke tests +//! themselves live in the testkit; this asserts the EVM conversion seam, which +//! needs the bindings.) + +use anoma_pa_evm_bindings::generated::protocol_adapter::ProtocolAdapter as PaContract; +use anoma_pa_testkit::environment::Prover; +use anoma_pa_testkit::fixtures::trivial; +use anoma_pa_testkit::prover::LocalProver; +use anoma_rm_risc0::transaction::Transaction as ArmTxn; + +#[tokio::test] +async fn proven_transaction_into_pa_transaction_yields_actions_and_delta_proof() { + let actions = trivial::build_many(2, 1).expect("must build trivial action witnesses"); + let tx = LocalProver.prove(&actions).await.unwrap(); + let arm_tx: ArmTxn = tx.into_arm(); + + let pa_tx: PaContract::Transaction = arm_tx.into(); + assert!(!pa_tx.actions.is_empty()); + assert!(!pa_tx.deltaProof.is_empty()); +} diff --git a/crates/integration-test/tests/integration.rs b/crates/integration-test/tests/integration.rs new file mode 100644 index 00000000..71633d5b --- /dev/null +++ b/crates/integration-test/tests/integration.rs @@ -0,0 +1,108 @@ +#[cfg(feature = "e2e")] +use anoma_pa_evm_integration_test::envs::e2e::Environment as EvmE2eEnv; +use anoma_pa_evm_integration_test::envs::local::Environment as EvmLocalEnv; +use anoma_pa_testkit::assert::{Needle, expect_integration_panic}; +use anoma_pa_testkit::environment::Environment; +use anoma_pa_testkit::fixtures::trivial; +use anoma_pa_testkit::transaction::Transaction; +use anoma_pa_testkit::{commitment_root, execute_tx, prove_actions}; +use anyhow::Context; +use rstest::*; + +#[rstest] +#[case::local(EvmLocalEnv::setup_bare())] +#[cfg_attr(feature = "e2e", case::e2e_test(EvmE2eEnv::setup_bare()))] +#[tokio::test] +async fn execute_tx_settles_a_trivial_transaction( + #[future(awt)] + #[case] + env: anyhow::Result, +) -> anyhow::Result<()> { + let mut env = env.context("env setup failed")?; + + let before = commitment_root(&env)?; + + let action = trivial::build(1, trivial::Overrides::default()) + .context("failed to build trivial action")? + .witnesses; + let tx = prove_actions(&env, &[action]).await?; + + execute_tx(&mut env, tx).await?; + + let after = commitment_root(&env)?; + + anyhow::ensure!(before != after, "commitment tree root must change"); + Ok(()) +} + +#[rstest] +#[case::local( + EvmLocalEnv::setup_bare(), + expect_integration_panic(Needle::Regexp( + regex::Regex::new( + r#"proving failed: [^\n]*\n\s*left: 1[^\n]*\n\s*right: 0"#, + ) + .unwrap(), + )), +)] +#[tokio::test] +async fn prove_actions_errors_on_nonzero_quantity( + #[future(awt)] + #[case] + env: anyhow::Result, + #[case] assert_err: impl FnOnce(anyhow::Result) -> anyhow::Result<()>, +) -> anyhow::Result<()> { + let env = env.expect("env setup failed"); + + let bad = trivial::build(7, trivial::Overrides::invalid_nonzero_quantity()) + .expect("failed to build invalid trivial action"); + + assert_err(prove_actions(&env, &[bad.witnesses]).await) +} + +#[rstest] +#[case::local( + EvmLocalEnv::setup_bare(), + expect_integration_panic(Needle::Static("assertion failed: self.resource.is_ephemeral")) +)] +#[tokio::test] +async fn prove_actions_errors_on_non_ephemeral_consumed( + #[future(awt)] + #[case] + env: anyhow::Result, + #[case] assert_err: impl FnOnce(anyhow::Result) -> anyhow::Result<()>, +) -> anyhow::Result<()> { + let env = env.expect("env setup failed"); + + let bad = trivial::build(8, trivial::Overrides::invalid_consumed_non_ephemeral()) + .expect("failed to build invalid trivial action"); + + assert_err(prove_actions(&env, &[bad.witnesses]).await) +} + +#[rstest] +#[case::local( + EvmLocalEnv::setup_bare(), + Transaction::tamper_first_logic_seal, + // RiscZeroMockVerifier rejects the tampered seal with `VerificationFailed()` (0x439cc0cd). + expect_integration_panic(Needle::Static("execution reverted: custom error 0x439cc0cd")) +)] +#[tokio::test] +async fn execute_tx_reverts_on_invalid_seal( + #[future(awt)] + #[case] + env: anyhow::Result, + #[case] tamper: impl FnOnce(&mut Env::Transaction) -> anyhow::Result<()>, + #[case] assert_err: impl FnOnce(anyhow::Result<()>) -> anyhow::Result<()>, +) -> anyhow::Result<()> { + let mut env = env.context("env setup failed")?; + + let actions = trivial::build_many(1, 11).context("failed to build trivial actions")?; + let mut tx = prove_actions(&env, &actions) + .await + .context("valid witnesses should prove before tampering")?; + + tamper(&mut tx).context("failed to tamper transaction proof")?; + + assert_err(execute_tx(&mut env, tx).await) +} diff --git a/justfile b/justfile index 2deac6a2..6fed99f4 100644 --- a/justfile +++ b/justfile @@ -53,7 +53,7 @@ contracts-gen-bindings: cd contracts && forge clean && forge bind \ --skip test --skip script \ --select '^(ProtocolAdapter|IProtocolAdapter|ICommitmentTree|INullifierSet)$' \ - --bindings-path ../bindings/src/generated/ \ + --bindings-path ../crates/bindings/src/generated/ \ --module \ --overwrite @@ -110,28 +110,28 @@ contracts-publish version *args: # Clean bindings bindings-clean: - cd bindings && cargo clean + cd crates/bindings && cargo clean # Build bindings bindings-build *args: - cd bindings && cargo build {{ args }} + cd crates/bindings && cargo build {{ args }} # Test bindings bindings-test *args: - cd bindings && cargo test {{ args }} + cd crates/bindings && cargo test {{ args }} # Check bindings are up-to-date bindings-check: contracts-gen-bindings - git diff --exit-code bindings/src/generated/ + git diff --exit-code crates/bindings/src/generated/ # Publish bindings bindings-publish *args: - cd bindings && cargo publish {{ args }} + cd crates/bindings && cargo publish {{ args }} # Lint bindings (clippy) bindings-lint: - cd bindings && cargo clippy --no-deps -- -Dwarnings - cd bindings && cargo clippy --no-deps --tests -- -Dwarnings + cd crates/bindings && cargo clippy --no-deps -- -Dwarnings + cd crates/bindings && cargo clippy --no-deps --tests -- -Dwarnings # Format bindings bindings-fmt: @@ -141,42 +141,68 @@ bindings-fmt: bindings-fmt-check: cargo fmt -- --check +# --- Crates (workspace-wide Rust) --- + +# Clean all crates +crates-clean: + cargo clean + +# Build all crates +crates-build *args: + cargo build {{ args }} + +# Test all crates +crates-test *args: + cargo test {{ args }} + +# Lint all crates (clippy) +crates-lint: + cargo clippy --all-targets --no-deps -- -Dwarnings + +# Format all crates +crates-fmt *args: + cargo fmt --all {{ args }} + +# Check all crates formatting +crates-fmt-check: + cargo fmt --all -- --check + # --- All --- -# Lint all (contracts + bindings) +# Lint all (contracts + crates) all-lint: @echo "==> Linting contracts..." @just contracts-lint - @echo "==> Linting bindings..." - @just bindings-lint + @echo "==> Linting crates..." + @just crates-lint -# Format all (contracts + bindings) +# Format all (contracts + crates) all-fmt: @echo "==> Formatting contracts..." @just contracts-fmt - @echo "==> Formatting bindings..." - @just bindings-fmt + @echo "==> Formatting crates..." + @just crates-fmt -# Check formatting for all (contracts + bindings) +# Check formatting for all (contracts + crates) all-fmt-check: @echo "==> Checking contract formatting..." @just contracts-fmt-check - @echo "==> Checking bindings formatting..." - @just bindings-fmt-check + @echo "==> Checking crates formatting..." + @just crates-fmt-check -# Build all (contracts + bindings) +# Build all (contracts + crates) all-build: @echo "==> Building contracts..." @just contracts-build - @echo "==> Building bindings..." - @just bindings-build + @echo "==> Building crates..." + @just crates-build -# Test all (contracts + bindings) +# Test all (contracts + crates) all-test: @echo "==> Testing contracts..." @just contracts-test - @echo "==> Testing bindings..." - @just bindings-test + @echo "==> Testing crates..." + @just crates-test # Prerequisites check (mirrors CI) all-check: