diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1df1e1..1c29c86 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,9 @@ on: pull_request: jobs: - native-tests: + # Default backend: pure-Rust MOCK ledger. Needs no C++ toolchain, no network, no + # submodule source beyond the headers — the fast, always-on gate. + mock-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -18,8 +20,75 @@ jobs: - name: Install libclang for bindgen run: sudo apt-get update && sudo apt-get install -y clang libclang-dev - - name: Run mocked native tests - run: cargo test --features native + - name: Run mocked tests + run: cargo test --features mock + + # Formatting, lints, and docs. All three steps run even if an earlier one fails + # (`if: ${{ !cancelled() }}`) so each reports independently. + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Install libclang for bindgen + run: sudo apt-get update && sudo apt-get install -y clang libclang-dev + + # NOTE: this is a CHECK only — it never rewrites source. At the time this job was + # added the tree was NOT fully rustfmt-clean (pre-existing formatting in build.rs + # and src/*.rs owned by another workstream), so this step will stay RED until a + # one-time `cargo fmt --all` pass lands. That pass is a tracked follow-up. + - name: rustfmt (check only) + if: ${{ !cancelled() }} + run: cargo fmt --all --check + + # WARNING-ONLY for now: there are pre-existing clippy lints in src/ that are owned + # by another workstream and must not be fixed here. Once they are cleared, tighten + # this to `-- -D warnings` so new lints fail CI. + # TODO(clippy-deny): append `-- -D warnings` after the src/ clippy cleanup lands. + - name: clippy (mock) + if: ${{ !cancelled() }} + run: cargo clippy --features mock --all-targets + + - name: cargo doc (mock) + if: ${{ !cancelled() }} + run: cargo doc --no-deps --features mock + + # REAL libcma compiled + linked for the host (x86_64). This is the backend that must + # produce byte-identical records to the riscv64 build, so it exercises the real C++ + # ledger and the records-layout / reproducibility tests. Needs a g++ >= 14 toolchain, + # bindgen's libclang, and network access (Boost + nlohmann/json are fetched by build.rs). + host-real-tests: + runs-on: ubuntu-latest + env: + # build.rs's host path honours these (default g++/gcc, which on the runner is < 14). + CMA_HOST_CXX: g++-14 + CMA_HOST_CC: gcc-14 + # bindgen's builtin-header fallback shells out to $CC; keep it on the same major. + CC: gcc-14 + CXX: g++-14 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: dtolnay/rust-toolchain@stable + + - name: Install C++ toolchain + bindgen/build deps + run: | + sudo apt-get update + sudo apt-get install -y \ + g++-14 gcc-14 build-essential \ + clang libclang-dev \ + wget make cmake + + - name: Build + run host-real tests (real libcma linked for the host) + run: cargo test --no-default-features --features host-real -- --nocapture riscv-link-check: runs-on: ubuntu-latest @@ -59,3 +128,43 @@ jobs: ar p "$lib" "$member" > "$obj" file "$obj" | grep -F 'RISC-V' rm -f "$obj" + + # =========================================================================== + # TODO(cross-arch-differential): the ULTIMATE host<->machine reproducibility + # invariant — not yet implemented (heavy: needs a riscv64 build AND running it + # under QEMU user emulation, then a byte-for-byte image diff). + # + # tests/host_real_records_layout.rs already pins the 32-byte record layout and + # asserts host determinism, and riscv-link-check proves the riscv64 archive + # builds. The missing piece is proving the two backends emit the SAME records + # image for the SAME credits, byte for byte — the property that makes off-chain + # host prediction of the on-chain ledger sound. + # + # Intended shape of the job: + # cross-arch-differential: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # with: { submodules: recursive } + # - uses: dtolnay/rust-toolchain@stable + # - uses: docker/setup-qemu-action@v3 # register binfmt for riscv64 user emu + # - name: Install host + riscv64 GCC 14 toolchains, libclang, qemu-user + # run: | + # sudo apt-get update + # sudo apt-get install -y \ + # g++-14 gcc-14 g++-14-riscv64-linux-gnu gcc-14-riscv64-linux-gnu \ + # build-essential clang libclang-dev wget make cmake \ + # qemu-user qemu-user-static libc6-riscv64-cross libstdc++6-riscv64-cross + # # 1. Build a tiny harness that credits a FIXED (address, balance) set into a + # # buffer-backed single-asset ledger and writes the 128 KiB records prefix + # # to a file. Build it twice from the same source: + # # a) host-real -> native x86_64 binary -> host.records + # # b) riscv64 -> riscv64 binary, run via `qemu-riscv64` -> riscv.records + # # (The riscv64 crate currently builds a static archive; a small #[no_mangle] + # # entrypoint or an integration test cross-compiled to riscv64-unknown-linux-gnu + # # and executed under qemu is the mechanism to add.) + # # 2. Assert byte-for-byte equality: + # # cmp host.records riscv.records + # # A mismatch means the host can NOT soundly predict the machine ledger and + # # must fail CI hard. + # =========================================================================== diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1dec807 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,68 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +While the crate is pre-1.0 (`0.0.x`), any release may contain breaking changes. + +## [Unreleased] + +### Added +- **Single-asset ledger API.** New bindings and `Ledger` wrapper support for the + single-asset `cma` ledger. +- **`host-real` feature.** Builds and links the real C++ `libcma` for the host + (x86_64) instead of the mock, for off-chain use (e.g. a sequencer predicting + the machine's ledger). Complements the existing `riscv64` cross-build path. +- Packaging metadata for crates.io / docs.rs: `LICENSE` (MIT), `rust-version` + (MSRV `1.74`), a `documentation` link, and `[package.metadata.docs.rs]` (an + offline `mock`-only docs build). Added `CHANGELOG.md`, `CONTRIBUTING.md`, + `SECURITY.md`, `deny.toml`, `rust-toolchain.toml`, and `rustfmt.toml`. + +### Changed +- **Vendored `libcma` bumped to the uint96 single-asset format (drive format v2; + machine-asset-tools `e4bfc24`).** The single-asset drive record widened its + balance from `uint64` to `uint96`, consuming the former 4-byte pad: the 32-byte + record is now `balance_lo (u64 LE) | balance_hi (u32 LE) | owner (20B)`, with the + owner moved from offset 8 to **offset 12**. Total supply and virtual (internal + account-id) balances widened to full 256-bit. The public C API (and therefore the + Rust wrapper surface) is UNCHANGED — deposits/withdrawals/balances already used + 256-bit `cma_amount_t` at the boundary; only code that parses the raw 32-byte + records must adopt the new offsets. **The on-drive format is not backward + compatible** (`MemoryFooter::VERSION` 1 → 2): a v1 drive would be silently + misread. Downstream that reads the records image directly (e.g. a sequencer's + `create_dump` / snapshot parser and the emergency-withdrawal output builder) MUST + be updated to the offset-12 owner and uint96 balance. +- **BREAKING: reshaped the ledger API around a single-asset ledger.** Removed + `LedgerMemoryMode` and reshaped `LedgerFileConfig`. Code that constructed a + ledger via the old memory-mode / file-config shape must be updated. +- **BREAKING: renamed the default mock feature `native` → `mock`.** The default + backend is now `mock`. Update any `--features native` usage accordingly. The + three mutually-exclusive backends are now `mock` (default), `host-real`, and + `riscv64`. + +### Fixed +- **Relocation safety for `cma_ledger_t`.** The self-referential C++ ledger is + now boxed so it is not moved after construction, preventing dangling internal + self-pointers. + +### Security / hardening +- Mutual-exclusion guards: enabling more than one backend feature now fails at + compile time (`compile_error!`) instead of silently letting the mock win in a + real build. +- `build.rs` verifies a checksum of the vendored/built libcma source. + +### Changed (breaking) +- **Migrated off the EOL `ethers-rs` onto `alloy`** (`alloy-primitives` + + `alloy-dyn-abi`). The public `Address` / `U256` types are now + `alloy_primitives::{Address, U256}` — a breaking change for downstream code + that used the ethers-typed API (hence the `0.0.1` → `0.1.0` bump). The ABI + parser (`parser.rs`) was reimplemented over `alloy-dyn-abi`; byte-for-byte + equivalence is pinned by the existing parser test vectors (all pass). + +### Known issues / tech debt +- **`json` (0.12)** — largely unmaintained (RUSTSEC-2022-0081). Planned + migration to `serde_json`. Surfaced by `deny.toml`. + +[Unreleased]: https://github.com/Mugen-Builders/libcma_binding_rust/commits/main diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..60a1863 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing + +Thanks for your interest in improving `libcma_binding_rust`. + +## Prerequisites + +Clone with submodules — the C headers live under `third_party/`: + +```bash +git clone --recurse-submodules https://github.com/Mugen-Builders/libcma_binding_rust +# or, if already cloned: +git submodule update --init --recursive +``` + +(`build.rs` will auto-init the submodules if they are missing, but doing it +yourself is more predictable.) + +## Building + +The default build uses the pure-Rust **`mock`** backend — no network or C++ +toolchain required: + +```bash +cargo build +``` + +A **real** libcma build links the compiled C++ library instead of the mock: + +```bash +# host (x86_64), off-chain use: +cargo build --no-default-features --features host-real + +# Cartesi machine target (riscv64): +cargo build --no-default-features --features riscv64 +``` + +Real builds additionally require **g++ ≥ 14**, GNU `make`, and **network access** +(`build.rs` fetches / compiles the archive from source). For `riscv64` you also +need the RISC-V GCC 14 cross toolchain (`g++-14-riscv64-linux-gnu`). + +### Feature rule: exactly one backend + +`mock`, `host-real`, and `riscv64` are **mutually exclusive** — exactly one must +be enabled, and a `compile_error!` guard enforces it. Because `mock` is a default +feature, selecting a real backend means also disabling defaults: + +```bash +cargo build --no-default-features --features host-real +``` + +Enabling a real backend without `--no-default-features` leaves `mock` on, which +the guard rejects. + +## Testing + +```bash +cargo test +``` + +Tests run against the `mock` backend by default. + +## Formatting and linting + +Before opening a pull request: + +```bash +cargo fmt --all +cargo clippy --all-targets +``` + +The repo pins `stable` via `rust-toolchain.toml` and ships a `rustfmt.toml`, so +formatting stays consistent across contributors. + +## Pull requests + +- Keep changes focused, and call out breaking changes clearly (the crate is + pre-1.0, so breaking changes are allowed but should be documented). +- Update `CHANGELOG.md` under the `## [Unreleased]` section. diff --git a/Cargo.lock b/Cargo.lock index 952dd0d..27d2353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,415 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloy-dyn-abi" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a04eb4abc2b5074a18e687ee63918f407cc7990083cba9b999445f839796060" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "itoa", + "serde", + "serde_json", + "winnow 1.0.4", +] + +[[package]] +name = "alloy-json-abi" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cee30dd4c2f4b23f434fdf675e7bf9681b86768141277266c6f548ef25cba0a" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-primitives" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f007e257069855bdf21d27762fd3f3705a613f805c9a08309bf353503f081d71" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "fixed-cache", + "foldhash", + "hashbrown 0.17.1", + "indexmap 2.12.1", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.5", + "rapidhash", + "ruint", + "rustc-hash 2.1.3", + "secp256k1", + "serde", + "sha3", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24671b1f62edcf0f9b62994c7bf72cd621a04a4b99f5020ece1a647b40e2f103" +dependencies = [ + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5655c38d5f84955bf727b2eeb62fddd91ebb98fd1d7ae6eb77f73ea88f9b9cf" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error3", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6277c780e07b76951e09a59788dde230d1582612324177d11a43a61e21a6bb83" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.12.1", + "proc-macro-error3", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.111", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9762b2ad3e5a0c09886de54fe549ab0056681df843cb082e2df7e1c0eb270d30" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.111", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da4c7130f0f01f4719678bda3db3bc7267fc2f7f9d0565e3bd964cd2bb45050d" +dependencies = [ + "serde", + "winnow 1.0.4", +] + +[[package]] +name = "alloy-sol-types" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96e74d6213180f78dbdccddce8af02a639c160c94b0a543fa35c77c58b8a7fc" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" +dependencies = [ + "ark-ff-asm 0.6.0", + "ark-ff-macros 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "educe", + "num-bigint", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-ff-asm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" +dependencies = [ + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-ff-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.6.0", + "digest 0.10.7", + "num-bigint", + "serde_with", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -36,9 +445,15 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" -version = "0.1.1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -55,7 +470,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools", + "itertools 0.12.1", "lazy_static", "lazycell", "log", @@ -63,12 +478,48 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", - "shlex", + "rustc-hash 1.1.0", + "shlex 1.3.0", "syn 2.0.111", "which", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.2.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", +] + [[package]] name = "bitflags" version = "2.10.0" @@ -96,6 +547,30 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "byte-slice-cast" version = "1.2.3" @@ -117,6 +592,16 @@ dependencies = [ "serde", ] +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex 2.0.1", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -138,7 +623,10 @@ version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ + "iana-time-zone", "num-traits", + "serde", + "windows-link", ] [[package]] @@ -152,6 +640,18 @@ dependencies = [ "libloading", ] +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -178,6 +678,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -187,6 +702,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -195,12 +719,12 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -215,34 +739,75 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "der" -version = "0.6.1" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", + "rustc_version 0.4.1", "syn 2.0.111", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", ] [[package]] @@ -251,21 +816,58 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" -version = "0.14.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -276,23 +878,43 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "der", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", - "rand_core", + "pkcs8", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -310,87 +932,51 @@ dependencies = [ ] [[package]] -name = "ethabi" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror", - "uint", -] - -[[package]] -name = "ethbloom" -version = "0.13.0" +name = "fastrlp" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" dependencies = [ - "crunchy", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", + "arrayvec", + "auto_impl", + "bytes", ] [[package]] -name = "ethereum-types" -version = "0.14.1" +name = "fastrlp" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", + "arrayvec", + "auto_impl", + "bytes", ] [[package]] -name = "ethers-core" -version = "1.0.2" +name = "ff" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade3e9c97727343984e1ceada4fdab11142d2ee3472d2c67027d56b1251d4f15" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "arrayvec", - "bytes", - "chrono", - "elliptic-curve", - "ethabi", - "generic-array", - "hex", - "k256", - "open-fastrlp", - "rand", - "rlp", - "rlp-derive", - "serde", - "serde_json", - "strum", - "thiserror", - "tiny-keccak", - "unicode-xid", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "ff" -version = "0.12.1" +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-cache" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +checksum = "2fe63500644ef0269fe6b744e7e5dc5c20b5eebf3d881bc2be53f194636f6583" dependencies = [ - "rand_core", - "subtle", + "equivalent", + "rapidhash", ] [[package]] @@ -400,17 +986,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand", + "rand 0.8.5", "rustc-hex", "static_assertions", ] +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -419,6 +1035,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -432,6 +1049,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" version = "0.3.3" @@ -440,26 +1069,43 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "group" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", + "serde", + "serde_core", +] + [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -467,13 +1113,31 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" +dependencies = [ + "arrayvec", +] + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -486,30 +1150,45 @@ dependencies = [ ] [[package]] -name = "impl-codec" -version = "0.6.0" +name = "hybrid-array" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ - "parity-scale-codec", + "typenum", ] [[package]] -name = "impl-rlp" -version = "0.3.0" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "rlp", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "impl-serde" -version = "0.4.0" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "serde", + "cc", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", ] [[package]] @@ -523,6 +1202,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.12.1" @@ -530,7 +1220,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", ] [[package]] @@ -542,12 +1243,32 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "json" version = "0.12.4" @@ -556,24 +1277,35 @@ checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] name = "k256" -version = "0.11.6" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", + "once_cell", "sha2", - "sha3", ] [[package]] name = "keccak" -version = "0.1.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ - "cpufeatures", + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "keccak-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" +dependencies = [ + "digest 0.10.7", + "sha3-asm", ] [[package]] @@ -596,10 +1328,11 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libcma_binding_rust" -version = "0.0.1" +version = "0.1.0" dependencies = [ + "alloy-dyn-abi", + "alloy-primitives", "bindgen", - "ethers-core", "hex", "json", "lazy_static", @@ -615,6 +1348,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -627,6 +1366,17 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "macro-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "memchr" version = "2.7.6" @@ -650,45 +1400,46 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-bigint" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ - "autocfg", + "num-integer", + "num-traits", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "num-conv" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] -name = "open-fastrlp" -version = "0.1.4" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", + "num-traits", ] [[package]] -name = "open-fastrlp-derive" -version = "0.1.1" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", + "autocfg", + "libm", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -717,16 +1468,44 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pest" +version = "2.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkcs8" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -754,9 +1533,6 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", "uint", ] @@ -769,6 +1545,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr3" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82366fd7d8b7a440d66d13418820c69df9b3908bcb1a0476d7f5ce5d12f5a04d" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error3" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b511283ea8a74b4b39447b128c5d00f03a356b7424554b13e298a5550100d9ac" +dependencies = [ + "proc-macro-error-attr3", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -778,6 +1576,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "unarray", +] + [[package]] name = "quote" version = "1.0.42" @@ -787,6 +1600,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -800,27 +1619,96 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "ref-cast" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" dependencies = [ - "ppv-lite86", - "rand_core", + "ref-cast-impl", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "ref-cast-impl" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" dependencies = [ - "getrandom", + "proc-macro2", + "quote", + "syn 3.0.3", ] [[package]] @@ -854,13 +1742,12 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rfc6979" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "crypto-bigint", "hmac", - "zeroize", + "subtle", ] [[package]] @@ -874,28 +1761,76 @@ dependencies = [ ] [[package]] -name = "rlp-derive" -version = "0.1.0" +name = "ruint" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +checksum = "45caf26f647c19115bf9c453c70ffe4a4a3a6390dceebd942610584f99b8ddce" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "ark-ff 0.6.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rand 0.9.5", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", ] +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc-hex" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", +] + [[package]] name = "rustix" version = "0.38.44" @@ -922,34 +1857,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] -name = "scale-info" -version = "2.11.6" +name = "schemars" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ - "cfg-if", - "derive_more", - "parity-scale-codec", - "scale-info-derive", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] -name = "scale-info-derive" -version = "2.11.6" +name = "schemars" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.111", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] name = "sec1" -version = "0.3.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", @@ -959,6 +1894,50 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.5", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.228" @@ -1002,6 +1981,25 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "time", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1009,41 +2007,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] name = "sha3" -version = "0.10.8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" dependencies = [ - "digest", + "digest 0.11.3", "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signature" -version = "1.6.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", - "rand_core", + "digest 0.10.7", + "rand_core 0.6.4", ] +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "spki" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -1056,38 +2076,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "strum" -version = "0.24.1" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" -dependencies = [ - "strum_macros", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "strum_macros" -version = "0.24.3" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "heck", "proc-macro2", "quote", - "rustversion", - "syn 1.0.109", + "unicode-ident", ] [[package]] -name = "subtle" -version = "2.6.1" +name = "syn" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] name = "syn" -version = "1.0.109" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1095,14 +2115,15 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.111" +name = "syn-solidity" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "083be3061e64d362cbe6ef12cfe1307ba3884326d8856448fe8a120fa2c44ebf" dependencies = [ + "paste", "proc-macro2", "quote", - "unicode-ident", + "syn 2.0.111", ] [[package]] @@ -1112,34 +2133,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "thiserror" -version = "1.0.69" +name = "time" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ - "thiserror-impl", + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", ] [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", + "num-conv", + "time-core", ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "tinyvec" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ - "crunchy", + "tinyvec_macros", ] +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml_datetime" version = "0.7.3" @@ -1155,10 +2192,10 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap", + "indexmap 2.12.1", "toml_datetime", "toml_parser", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -1167,14 +2204,20 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "winnow", + "winnow 0.7.14", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -1188,18 +2231,36 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -1212,6 +2273,60 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.111", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + [[package]] name = "which" version = "4.4.2" @@ -1224,12 +2339,65 @@ dependencies = [ "rustix", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1321,6 +2489,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wyz" version = "0.5.1" @@ -1355,3 +2538,17 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] diff --git a/Cargo.toml b/Cargo.toml index f6d96cd..acc7f8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "libcma_binding_rust" -version = "0.0.1" +version = "0.1.0" edition = "2021" -authors = ["Idogwu Chinonso idogwuchi@gmail.com"] -description = "Rust utilities for Cartesi applicaitons, functions as a plugin wallet lib for managing applicaiton assets and also a parser for encoding and decoding vouchers and applicaion inputs." +# Minimum Supported Rust Version. Conservative, plausible floor for edition 2021 +# plus the crate's dependencies; verified to build with the current toolchain. +rust-version = "1.74" +authors = ["Idogwu Chinonso "] +description = "Rust utilities for Cartesi applications: a plugin wallet library for managing application assets, plus a parser for encoding and decoding vouchers and application inputs." license = "MIT" repository = "https://github.com/Mugen-Builders/libcma_binding_rust" +documentation = "https://docs.rs/libcma_binding_rust" readme = "README.md" keywords = ["cartesi", "rollup", "ledger", "parser", "blockchain"] categories = ["api-bindings", "cryptography", "blockchain"] @@ -17,13 +21,37 @@ path = "src/lib.rs" [dependencies] hex = "0.4" lazy_static = { version = "1.4", optional = true } +# NOTE: `json` (0.12) is largely unmaintained. Tech debt: migrate to `serde_json` +# (the de-facto standard). Tracked in CHANGELOG.md and surfaced by deny.toml. json = "0.12" -ethers-core = "1.0.0" +# Ethereum primitives + dynamic ABI. Migrated off the EOL `ethers-rs` onto `alloy` +# (aligned with the `alloy-primitives`/`alloy-dyn-abi` 1.6.x the downstream sequencer resolves). +alloy-primitives = "1.6" +alloy-dyn-abi = "1.6" [build-dependencies] bindgen = "0.69" +# docs.rs builds the crate OFFLINE (no network, no C++ toolchain), so it must use +# the `mock` backend — a pure-Rust stub that needs neither. `host-real`/`riscv64` +# would try to fetch/compile the real libcma and fail on the docs.rs sandbox. +[package.metadata.docs.rs] +features = ["mock"] +default-target = "x86_64-unknown-linux-gnu" + [features] -default = ["native"] -native = ["lazy_static"] # Mac testing with mocks -riscv64 = [] # Cross-build later +# Exactly ONE backend must be enabled — they are mutually exclusive (enforced by the +# compile_error! guard at the top of src/lib.rs). `mock` is the default so `cargo build` +# and `cargo test` work out of the box, but it is a STUB ledger, NOT real libcma. +# +# IMPORTANT for consumers: selecting a REAL backend requires ALSO disabling defaults, +# otherwise `mock` stays enabled and silently wins (a fake ledger in production): +# default-features = false, features = ["host-real"] # (or "riscv64") +default = ["mock"] +mock = ["lazy_static"] # In-memory MOCK ledger (thread-local stubs in src/mocks.rs) — plumbing/tests only, NOT real libcma +riscv64 = [] # Cross-build the real C++ libcma for the Cartesi machine (riscv64) +# Build the real C++ libcma for the HOST (x86_64) and link it instead of the mock. +# Used off-chain (e.g. by a sequencer predicting the machine's ledger): the DEFS in +# machine-asset-tools force generic, SIMD-free code paths so the 32-byte account records +# are byte-identical to the riscv64 build — the property that makes host prediction sound. +host-real = [] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2cb1cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Idogwu Chinonso + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 7edcd2a..3da7e68 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,60 @@ The Cartesi SDK / app Docker image used to build the machine already provides al ## Feature flags -| Feature | Default | Purpose | -| --------- | ------- | ------- | -| `native` | yes | Compiles `src/mocks.rs` shims so host tests run without the RISC-V `libcma` archive | -| `riscv64` | no | Cross-build path that links the real C++ `cma` library; `build.rs` cross-compiles `build/riscv64/libcma.a` from the submodule source if it isn't already present (needs the RISC-V GCC 14 cross toolchain) | +The crate has **three mutually exclusive backends**. Exactly one must be enabled; +enabling zero or more than one is a hard `compile_error!` (enforced at the top of +`src/lib.rs`). + +| Feature | Default | Real libcma? | When to use | +| ----------- | ------- | ------------------------ | ----------- | +| `mock` | yes | No — in-memory **stub** | Host development and `cargo test`: compiles the thread-local stubs in `src/mocks.rs` so the crate builds and the plumbing/parser tests run with no C++ toolchain, no network, and no RISC-V archive. **Never use in production — it is not a real ledger.** | +| `host-real` | no | Yes (host, x86_64) | Running the **real** C++ `libcma` off-chain on the host, e.g. a sequencer predicting the Cartesi machine's ledger state. `build.rs` builds and links the real static archive for the host. | +| `riscv64` | no | Yes (Cartesi machine) | Running the **real** C++ `libcma` **inside** the Cartesi machine. `build.rs` cross-compiles `build/riscv64/libcma.a` from the submodule source if it isn't already present (needs the RISC-V GCC 14 cross toolchain). | + +### Selecting a real backend (important footgun) + +The backends are mutually exclusive **and** `mock` is a default feature, so the +link gate in `build.rs` keys off `mock`. To build against the real `libcma` you +MUST also turn default features off — otherwise the default `mock` stays enabled +and you silently link the stub instead of the real ledger: ```bash +# real libcma on the host (off-chain, e.g. sequencer prediction) +cargo build --no-default-features --features host-real + +# real libcma cross-compiled for the Cartesi machine cargo build --no-default-features --features riscv64 ``` +In `Cargo.toml`: + +```toml +libcma_binding_rust = { version = "...", default-features = false, features = ["host-real"] } # or "riscv64" +``` + +If you forget `default-features = false`, enabling `host-real` or `riscv64` +alongside the default `mock` trips the mutual-exclusivity `compile_error!` — read +its message; the fix is to disable default features. + +### Determinism / reproducibility + +`host-real` and `riscv64` compile the C++ `libcma` with SIMD-free / generic flags +(`-DBOOST_UNORDERED_DISABLE_SSE2`, `-DBOOST_UNORDERED_DISABLE_NEON`, +`-DBOOST_INTERPROCESS_FORCE_GENERIC_EMULATION`). This makes the on-disk 32-byte +account records (single-asset drive format v2: `balance` uint96 little-endian +[low u64 | high u32] | `owner` 20 bytes, no padding) **byte-identical** across +x86_64 and riscv64. That invariant is what +makes off-chain prediction with `host-real` sound: the host reproduces, byte for +byte, exactly what the machine computes on-chain. + +### Thread safety + +`Ledger` wraps a self-referential C++ object (Boost.Interprocess) held on the +heap for relocation safety, and is therefore **`!Send` / `!Sync`**. Do not move +or share a `Ledger` across threads without external synchronization. Downstream +code that needs `Send` typically wraps the `Ledger` in a mutex together with its +own `unsafe impl Send`. + ## Ledger wrapper `Ledger` wraps `cma_ledger_*` with helpers for file/buffer initialization, asset/account retrieval, deposit/withdraw/transfer, balance, and total supply. @@ -93,9 +138,9 @@ cargo test - `tests/parser_tests.rs` — integration tests against the pure-Rust parser - `tests/parser_vectors.rs` — vectors ported from `third_party/machine-asset-tools/tests/parser.c` -- `tests/ledger_tests.rs` — ledger tests via native mocks +- `tests/ledger_tests.rs` — ledger tests via the `mock` backend -CI runs native tests on every push/PR and attempts an riscv64 link check when `libcma` can be built. +CI runs the `mock`-backend tests on every push/PR and attempts an riscv64 link check when `libcma` can be built. ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..fe7fb4f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +# Security Policy + +## Supported versions + +The crate is pre-1.0 and under active development. Only the latest published +`0.0.x` release receives security fixes. + +| Version | Supported | +| ---------------- | ------------------ | +| 0.0.x (latest) | :white_check_mark: | +| older 0.0.x | :x: | + +## Reporting a vulnerability + +Please report security vulnerabilities **privately** — do not open a public +issue or pull request. + +- Preferred: open a private advisory via GitHub Security Advisories on the + [repository](https://github.com/Mugen-Builders/libcma_binding_rust/security/advisories/new). +- Alternatively, email the maintainer: **idogwuchi@gmail.com**. + +Please include a description, the affected version(s), and reproduction steps +where possible. You can expect an initial acknowledgement within a reasonable +time frame; we will then coordinate a fix and a disclosure timeline with you. + +## Scope note + +The default `mock` backend is an in-memory stub for development and testing — it +is **not** the real libcma ledger and must not be used to custody real assets in +production. Use the `host-real` or `riscv64` backends for real deployments. diff --git a/STRUCTURE.md b/STRUCTURE.md index b87b8cd..c11008b 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -20,7 +20,7 @@ libcma_binding_rust/ # crate root (see Cargo.toml [package] name) │ ├── ledger.rs # Ledger wrapper + file/buffer init configs │ ├── parser.rs # High-level parser / voucher helpers │ ├── helpers.rs # Shared helpers -│ └── mocks.rs # #[cfg(feature = "native")] C ABI shims for tests +│ └── mocks.rs # #[cfg(feature = "mock")] C ABI stub shims for tests ├── tests/ │ ├── ledger_tests.rs # Ledger behavior (mock-backed by default) │ └── parser_tests.rs # Parser / encoding tests @@ -47,12 +47,15 @@ There is **no** checked-in `lib/cpp-build` tree in this layout: bindgen runs aga - `third_party/machine-guest-tools/sys-utils/libcmt/include` 2. **Header root** — `wrapper.h` at the crate root. 3. **Generated output** — `$OUT_DIR/bindings.rs` (included from `src/lib.rs` inside the `bindings` module). -4. **Linking** — If the **`native` feature is disabled**, `build.rs` adds `-L third_party/machine-asset-tools/build/riscv64` and links `static=cma`. With **`native` enabled (default)**, it does not link that archive; `src/mocks.rs` supplies compatible `#[no_mangle]` symbols for development and `cargo test` on the host. +4. **Linking** — The link gate keys off the **`mock`** feature. With **`mock` enabled (default)**, `build.rs` links no C++ archive; `src/mocks.rs` supplies compatible `#[no_mangle]` stub symbols for development and `cargo test` on the host. With a **real backend** — `host-real` (host x86_64) or `riscv64` (Cartesi machine), each requiring `--no-default-features` — `build.rs` builds and links the static `cma` archive instead of the mock (for `riscv64` it adds `-L third_party/machine-asset-tools/build/riscv64` and links `static=cma`). ## Feature flags -- **`native` (default)** — Compiles `mocks.rs`. Intended for host builds and unit/integration tests without the RISC-V static library. -- **`riscv64`** — Placeholder feature for cross-compilation workflows; default build still keys off “not native” for linking. +Three **mutually exclusive** backends; exactly one must be enabled (a `compile_error!` in `src/lib.rs` enforces this). Selecting a real backend requires `default-features = false` — otherwise the default `mock` stays on and silently wins. + +- **`mock` (default)** — Compiles `src/mocks.rs`: an in-memory **stub** ledger for host builds and unit/integration tests without any C++ toolchain or the RISC-V static library. Not real libcma; never for production. +- **`host-real`** — Builds and links the **real** C++ `libcma` for the host (x86_64). Used off-chain, e.g. a sequencer predicting the machine's ledger. Built SIMD-free so its 32-byte account records are byte-identical to the `riscv64` build. +- **`riscv64`** — Cross-compiles and links the **real** C++ `libcma` for the Cartesi machine (riscv64). ## Public surface (`src/lib.rs`) @@ -64,7 +67,7 @@ Re-exports include: ## Tests -- **`tests/ledger_tests.rs`** — Ledger API; uses mock implementations when `native` is on. +- **`tests/ledger_tests.rs`** — Ledger API; uses mock implementations when the `mock` backend (default) is on. - **`tests/parser_tests.rs`** — Parser and voucher-related coverage. Run: `cargo test`. diff --git a/build.rs b/build.rs index 6c751ec..95ee99c 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,11 @@ use std::{env, path::Path, path::PathBuf, process::Command}; +/// Pinned SHA-256 of the nlohmann/json v3.12.0 single-header release asset (`json.hpp`). +/// Verified out-of-band against the upstream GitHub release download. A mismatch means the +/// fetched header was corrupted or tampered with — the build must refuse to proceed. +const NLOHMANN_JSON_SHA256: &str = + "aaf127c04cb31c406e5b04a63f1ae89369fccde6d8fa7cdda1ed4f32dfc5de63"; + fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); @@ -10,7 +16,11 @@ fn main() { // make the include-path lookups below fail. Pull the submodules in automatically so the crate // builds with nothing more than `cargo build` — no out-of-band setup step required. if !mat.join("include").exists() { - run("git", &["submodule", "update", "--init", "--recursive"], &manifest_dir); + run( + "git", + &["submodule", "update", "--init", "--recursive"], + &manifest_dir, + ); } let cma_include_dir = mat.join("include").canonicalize().expect( @@ -56,31 +66,76 @@ fn main() { .write_to_file(out_dir.join("bindings.rs")) .expect("Failed to write bindings"); - // Link the real C++ libcma when not using the native mock. - if !cfg!(feature = "native") { - let lib_dir = mat.join("build/riscv64"); + // MOCK backend active: shout about it so a fake in-memory ledger can never be shipped to + // production unnoticed. NOTE also that everything in the `!mock` block below (the wget of + // nlohmann/json, `make third-party` which fetches Boost et al., and the C++ compile of + // libcma.a) is gated OFF here — so a `mock` build downloads no third-party C++ sources and + // needs no C++ toolchain. + if cfg!(feature = "mock") { + println!( + "cargo:warning=libcma_binding_rust: building with the MOCK ledger (feature `mock`) — \ + this is NOT real libcma; never use in production. For a real build use \ + default-features = false, features = [\"host-real\"] (or \"riscv64\")." + ); + } + + // Link the real C++ libcma when the MOCK is NOT selected. Two targets: + // - `riscv64` → cross-compile for the Cartesi machine (the non-mock default). + // - `host-real` → compile for the host (x86_64), so an off-chain consumer runs the + // *same* ledger the machine will. The DEFS in machine-asset-tools force + // SIMD-free/generic paths so the record bytes match across arches. + // + // Everything inside this block fetches third-party C++ sources from the NETWORK and invokes a + // C++ COMPILER; none of it runs for a `mock` build. Keeping the fetch/compile confined here is + // what keeps the default `mock` path hermetic (no network, no toolchain, docs.rs/offline-safe). + if !cfg!(feature = "mock") { + // Real builds are NOT hermetic: they require network access and a C++ toolchain (g++ >= 14 + // for C++20/23). Make that requirement visible in the build log up front. + println!( + "cargo:warning=libcma_binding_rust: building REAL libcma from C++ source — this build \ + requires network access and a C++ toolchain (g++ >= 14). See build.rs for details." + ); + + let host = cfg!(feature = "host-real"); + // Distinct object dirs so a host build and a cross build never clobber each other. + let obj_subdir = if host { "build/host" } else { "build/riscv64" }; + let lib_dir = mat.join(obj_subdir); let lib_path = lib_dir.join("libcma.a"); // Build libcma.a from source if it isn't already present. This is what lets the crate be // consumed as a plain `git`/`crates.io` dependency WITHOUT vendoring a prebuilt archive. // - // Build-environment requirements (the Cartesi SDK / app Dockerfile provides these): + // Build-environment requirements (the Cartesi SDK / app Dockerfile provide the cross set): // - GNU make, wget, and network access - // - the RISC-V GCC 14 cross toolchain: g++-14-riscv64-linux-gnu / gcc-14-riscv64-linux-gnu - // (libcma's C++ source requires GCC >= 14). - // Override the compiler names with CMA_RISCV64_CXX / CMA_RISCV64_CC if your toolchain - // differs, or skip this whole path by pre-building build/riscv64/libcma.a yourself. + // - riscv64: the RISC-V GCC 14 cross toolchain (g++-14-riscv64-linux-gnu / gcc-14-…). + // - host-real: a host C++ toolchain with g++ >= 14 (C++20/C++23) and Boost is fetched. + // Override the compiler names with CMA_RISCV64_CXX/CC or CMA_HOST_CXX/CC. if !lib_path.exists() { - let cxx = - env::var("CMA_RISCV64_CXX").unwrap_or_else(|_| "riscv64-linux-gnu-g++-14".into()); - let cc = - env::var("CMA_RISCV64_CC").unwrap_or_else(|_| "riscv64-linux-gnu-gcc-14".into()); + let (toolchain_prefix, cxx, cc, ar) = if host { + ( + String::new(), + env::var("CMA_HOST_CXX").unwrap_or_else(|_| "g++".into()), + env::var("CMA_HOST_CC").unwrap_or_else(|_| "gcc".into()), + "ar".to_string(), + ) + } else { + ( + "riscv64-linux-gnu-".to_string(), + env::var("CMA_RISCV64_CXX") + .unwrap_or_else(|_| "riscv64-linux-gnu-g++-14".into()), + env::var("CMA_RISCV64_CC") + .unwrap_or_else(|_| "riscv64-linux-gnu-gcc-14".into()), + "riscv64-linux-gnu-ar".to_string(), + ) + }; // machine-asset-tools' `third-party` target fetches Boost/emulator/guest-tools but not // nlohmann/json, so fetch that single header first. let nlohmann = mat.join("third-party/nlohmann/json.hpp"); if !nlohmann.exists() { std::fs::create_dir_all(mat.join("third-party/nlohmann")).ok(); + // GNU `wget` has no `--checksum` flag (only wget2 does), so we download here and + // verify the SHA-256 against a pinned constant below rather than at fetch time. run( "wget", &[ @@ -91,17 +146,40 @@ fn main() { &mat, ); } + // Supply-chain gate: pin + verify the one header the Makefile does not fetch itself. + // This runs on BOTH a fresh download and a pre-existing/vendored copy, so a tampered or + // corrupted cache is caught too. A mismatch is a hard, un-ignorable build failure. + verify_sha256(&nlohmann, NLOHMANN_JSON_SHA256); - // Download + stage the third-party deps, then cross-compile the static archive. - run("make", &["third-party", "TOOLCHAIN_PREFIX=riscv64-linux-gnu-"], &mat); + // Download + stage the third-party deps, then compile the static archive. The Makefile + // hardcodes `libcma_OBJDIR := build/riscv64`; override it so the host build lands in its + // own dir (command-line assignments beat the Makefile's `:=`). + // + // Build ONLY the pieces libcma.a actually needs — Boost, the guest-tools libcmt + // headers, and nlohmann/json. The blanket `make third-party` also downloads and + // extracts the prebuilt cartesi-machine emulator .deb (~57 MB, xz-compressed), which is + // linked ONLY by the host-side `account-driver-reader` tool, NOT by libcma.a. Pulling it + // in needs `xz` (and downloads tens of MB) for nothing, and breaks minimal build + // environments such as the Cartesi machine cross-build image (which ships no `xz`). run( "make", &[ - "build/riscv64/libcma.a", - "TOOLCHAIN_PREFIX=riscv64-linux-gnu-", + "third-party-boost", + "third-party-guest-tools", + "third-party-nlohmann-json", + &format!("TOOLCHAIN_PREFIX={toolchain_prefix}"), + ], + &mat, + ); + run( + "make", + &[ + &format!("{obj_subdir}/libcma.a"), + &format!("libcma_OBJDIR={obj_subdir}"), + &format!("TOOLCHAIN_PREFIX={toolchain_prefix}"), &format!("CXX={cxx}"), &format!("CC={cc}"), - "AR=riscv64-linux-gnu-ar", + &format!("AR={ar}"), ], &mat, ); @@ -132,7 +210,10 @@ fn main() { /// Used as a fallback so bindgen works even when libclang ships without its own resource headers. fn gcc_builtin_include() -> Option { let cc = env::var("CC").unwrap_or_else(|_| "cc".into()); - let out = Command::new(cc).arg("-print-file-name=include").output().ok()?; + let out = Command::new(cc) + .arg("-print-file-name=include") + .output() + .ok()?; if !out.status.success() { return None; } @@ -144,6 +225,55 @@ fn gcc_builtin_include() -> Option { } } +/// Verify a file against a pinned SHA-256, failing the build loudly on mismatch. Used as the +/// supply-chain gate for the nlohmann/json header fetched over the network. Shells out to a +/// system hashing tool so no extra crate dependency is required. +fn verify_sha256(path: &Path, expected: &str) { + let actual = sha256_of(path).unwrap_or_else(|| { + panic!( + "cannot verify {}: no usable SHA-256 tool found (need `sha256sum`, `shasum`, or \ + `openssl` on PATH). Refusing to build against an unverified nlohmann/json header.", + path.display() + ) + }); + assert!( + actual.eq_ignore_ascii_case(expected), + "SHA-256 mismatch for {}: expected {expected}, got {actual}. Refusing to build against an \ + unverified nlohmann/json header (possible supply-chain tampering or a corrupted download). \ + Delete the file and re-fetch, or update the pin in build.rs if the upstream release changed.", + path.display() + ); +} + +/// Compute the lowercase hex SHA-256 of `path` using whatever system tool is available +/// (`sha256sum`, then `shasum -a 256`, then `openssl dgst -sha256 -r`). Returns None if none +/// produced a valid 64-char hex digest. Avoids pulling in a hashing crate as a build-dependency. +fn sha256_of(path: &Path) -> Option { + let p = path.to_str()?; + // (command, args preceding the file path); each tool prints the digest as the first token. + let candidates: [(&str, &[&str]); 3] = [ + ("sha256sum", &[]), + ("shasum", &["-a", "256"]), + ("openssl", &["dgst", "-sha256", "-r"]), + ]; + for (cmd, pre) in candidates { + let mut args: Vec<&str> = pre.to_vec(); + args.push(p); + if let Ok(out) = Command::new(cmd).args(&args).output() { + if out.status.success() { + if let Ok(s) = String::from_utf8(out.stdout) { + if let Some(tok) = s.split_whitespace().next() { + if tok.len() == 64 && tok.bytes().all(|b| b.is_ascii_hexdigit()) { + return Some(tok.to_ascii_lowercase()); + } + } + } + } + } + } + None +} + /// Run a command in `cwd`, panicking with a helpful message if it is missing or fails. fn run(cmd: &str, args: &[&str], cwd: &Path) { let status = Command::new(cmd) diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..9ff5aa4 --- /dev/null +++ b/deny.toml @@ -0,0 +1,65 @@ +# cargo-deny configuration — https://embarkstudios.github.io/cargo-deny/ +# +# Run: cargo deny check +# +# Purpose here is twofold: +# 1. Enforce a permissive-only license allow-list across the dependency tree. +# 2. Surface this crate's known tech debt — the deprecated `json` and +# `ethers-core` dependencies — via security-advisory and ban checks, as a +# standing reminder to migrate to `serde_json` and `alloy` respectively. + +[graph] +all-features = false + +# --------------------------------------------------------------------------- +# Security advisories (RustSec DB). +# --------------------------------------------------------------------------- +[advisories] +version = 2 +# Vulnerabilities are always denied under schema v2. +# Check every crate (direct + transitive) for `unmaintained` advisories so +# `json` (RUSTSEC-2022-0081) and any ethers-rs advisory are flagged. +unmaintained = "all" +yanked = "warn" +ignore = [] + +# --------------------------------------------------------------------------- +# License policy — permissive licenses only. +# --------------------------------------------------------------------------- +[licenses] +version = 2 +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Zlib", + "Unicode-DFS-2016", + "Unicode-3.0", + "CC0-1.0", + "Unlicense", + "MPL-2.0", +] +confidence-threshold = 0.8 + +# --------------------------------------------------------------------------- +# Bans — explicitly flag the deprecated direct dependencies. +# --------------------------------------------------------------------------- +[bans] +multiple-versions = "warn" +wildcards = "warn" +# These are intentionally listed so `cargo deny check bans` surfaces them as a +# migration reminder. Remove each entry once the corresponding replacement lands. +deny = [ + { name = "json", reason = "Unmaintained (RUSTSEC-2022-0081); migrate to serde_json." }, + # ethers-core removed — the crate migrated to alloy (alloy-primitives / alloy-dyn-abi). +] + +# --------------------------------------------------------------------------- +# Source registries. +# --------------------------------------------------------------------------- +[sources] +unknown-registry = "warn" +unknown-git = "warn" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..73cb934 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "clippy"] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..f42c8b3 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +edition = "2021" +max_width = 100 diff --git a/src/error.rs b/src/error.rs index cd4a152..ecef7f3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,19 +28,43 @@ impl LedgerError { x if x == bindings::CMA_LEDGER_SUCCESS as i32 => unreachable!(), x if x == bindings::CMA_LEDGER_ERROR_UNKNOWN as i32 => LedgerError::Unknown, x if x == bindings::CMA_LEDGER_ERROR_EXCEPTION as i32 => LedgerError::Exception, - x if x == bindings::CMA_LEDGER_ERROR_INSUFFICIENT_FUNDS as i32 => LedgerError::InsufficientFunds, - x if x == bindings::CMA_LEDGER_ERROR_ACCOUNT_NOT_FOUND as i32 => LedgerError::AccountNotFound, - x if x == bindings::CMA_LEDGER_ERROR_ASSET_NOT_FOUND as i32 => LedgerError::AssetNotFound, - x if x == bindings::CMA_LEDGER_ERROR_BALANCE_NOT_FOUND as i32 => LedgerError::BalanceNotFound, - x if x == bindings::CMA_LEDGER_ERROR_SUPPLY_OVERFLOW as i32 => LedgerError::SupplyOverflow, - x if x == bindings::CMA_LEDGER_ERROR_BALANCE_OVERFLOW as i32 => LedgerError::BalanceOverflow, - x if x == bindings::CMA_LEDGER_ERROR_INVALID_ACCOUNT as i32 => LedgerError::InvalidAccount, - x if x == bindings::CMA_LEDGER_ERROR_INSERTION_ERROR as i32 => LedgerError::InsertionError, - x if x == bindings::CMA_LEDGER_ERROR_MAX_ASSETS_REACHED as i32 => LedgerError::MaxAssetsReached, - x if x == bindings::CMA_LEDGER_ERROR_MAX_ACCOUNTS_REACHED as i32 => LedgerError::MaxAccountsReached, - x if x == bindings::CMA_LEDGER_ERROR_MAX_BALANCES_REACHED as i32 => LedgerError::MaxBalancesReached, + x if x == bindings::CMA_LEDGER_ERROR_INSUFFICIENT_FUNDS as i32 => { + LedgerError::InsufficientFunds + } + x if x == bindings::CMA_LEDGER_ERROR_ACCOUNT_NOT_FOUND as i32 => { + LedgerError::AccountNotFound + } + x if x == bindings::CMA_LEDGER_ERROR_ASSET_NOT_FOUND as i32 => { + LedgerError::AssetNotFound + } + x if x == bindings::CMA_LEDGER_ERROR_BALANCE_NOT_FOUND as i32 => { + LedgerError::BalanceNotFound + } + x if x == bindings::CMA_LEDGER_ERROR_SUPPLY_OVERFLOW as i32 => { + LedgerError::SupplyOverflow + } + x if x == bindings::CMA_LEDGER_ERROR_BALANCE_OVERFLOW as i32 => { + LedgerError::BalanceOverflow + } + x if x == bindings::CMA_LEDGER_ERROR_INVALID_ACCOUNT as i32 => { + LedgerError::InvalidAccount + } + x if x == bindings::CMA_LEDGER_ERROR_INSERTION_ERROR as i32 => { + LedgerError::InsertionError + } + x if x == bindings::CMA_LEDGER_ERROR_MAX_ASSETS_REACHED as i32 => { + LedgerError::MaxAssetsReached + } + x if x == bindings::CMA_LEDGER_ERROR_MAX_ACCOUNTS_REACHED as i32 => { + LedgerError::MaxAccountsReached + } + x if x == bindings::CMA_LEDGER_ERROR_MAX_BALANCES_REACHED as i32 => { + LedgerError::MaxBalancesReached + } x if x == bindings::CMA_LEDGER_ERROR_ASSET_SUPPLY as i32 => LedgerError::AssetSupply, - x if x == bindings::CMA_LEDGER_ERROR_ACCOUNT_BALANCE as i32 => LedgerError::AccountBalance, + x if x == bindings::CMA_LEDGER_ERROR_ACCOUNT_BALANCE as i32 => { + LedgerError::AccountBalance + } x if x == bindings::CMA_LEDGER_ERROR_REMOVE as i32 => LedgerError::Remove, _ => LedgerError::Other(code), } @@ -52,9 +76,7 @@ impl LedgerError { if msg.is_null() { format!("{:?}", self) } else { - std::ffi::CStr::from_ptr(msg) - .to_string_lossy() - .to_string() + std::ffi::CStr::from_ptr(msg).to_string_lossy().to_string() } } } @@ -85,9 +107,15 @@ impl ParserError { x if x == bindings::CMA_PARSER_SUCCESS as i32 => unreachable!(), x if x == bindings::CMA_PARSER_ERROR_UNKNOWN as i32 => ParserError::Unknown, x if x == bindings::CMA_PARSER_ERROR_EXCEPTION as i32 => ParserError::Exception, - x if x == bindings::CMA_PARSER_ERROR_INCOMPATIBLE_INPUT as i32 => ParserError::IncompatibleInput, - x if x == bindings::CMA_PARSER_ERROR_MALFORMED_INPUT as i32 => ParserError::MalformedInput, - x if x == bindings::CMA_PARSER_ERROR_INVALID_AMOUNT as i32 => ParserError::InvalidAmount, + x if x == bindings::CMA_PARSER_ERROR_INCOMPATIBLE_INPUT as i32 => { + ParserError::IncompatibleInput + } + x if x == bindings::CMA_PARSER_ERROR_MALFORMED_INPUT as i32 => { + ParserError::MalformedInput + } + x if x == bindings::CMA_PARSER_ERROR_INVALID_AMOUNT as i32 => { + ParserError::InvalidAmount + } _ => ParserError::Other(code), } } @@ -98,9 +126,7 @@ impl ParserError { if msg.is_null() { format!("{:?}", self) } else { - std::ffi::CStr::from_ptr(msg) - .to_string_lossy() - .to_string() + std::ffi::CStr::from_ptr(msg).to_string_lossy().to_string() } } } @@ -112,4 +138,4 @@ impl std::fmt::Display for ParserError { } } -impl std::error::Error for ParserError {} \ No newline at end of file +impl std::error::Error for ParserError {} diff --git a/src/helpers.rs b/src/helpers.rs index 0a87a3b..f4288f4 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,7 +1,7 @@ -use hex; -use ethers_core::types::{Address}; -use json::{JsonValue, object}; use crate::parser::CmaVoucher; +use alloy_primitives::Address; +use hex; +use json::{object, JsonValue}; pub fn hex_to_string(hex: &str) -> Result> { let hexstr = hex.strip_prefix("0x").unwrap_or(hex); @@ -40,4 +40,4 @@ impl ToJson for CmaVoucher { "payload" => format!("{}", self.payload), } } -} \ No newline at end of file +} diff --git a/src/ledger.rs b/src/ledger.rs index c37cbca..de3d259 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -1,3 +1,29 @@ +//! Safe Rust wrapper around the C++ `libcma` ledger. +//! +//! # Backends +//! +//! Exactly one backend feature is compiled in (they are mutually exclusive; see +//! the `compile_error!` guards in `lib.rs`): +//! +//! - **`mock`** (default) — an in-memory **stub** ledger (`src/mocks.rs`). Needs +//! no C++ toolchain or network; for compile/plumbing tests only. It is **not** +//! real libcma and must never be used in production. +//! - **`host-real`** — the real C++ libcma built for the host (x86_64), used +//! off-chain (e.g. a sequencer predicting the Cartesi machine's ledger). +//! - **`riscv64`** — the real C++ libcma cross-compiled to run inside the +//! Cartesi machine. +//! +//! # Reproducibility invariant +//! +//! `host-real` and `riscv64` build libcma with SIMD-free / generic flags +//! (`-DBOOST_UNORDERED_DISABLE_SSE2`, `-DBOOST_UNORDERED_DISABLE_NEON`, +//! `-DBOOST_INTERPROCESS_FORCE_GENERIC_EMULATION`) so the on-disk 32-byte account +//! records (single-asset drive format v2: `balance` uint96 little-endian [low u64 | +//! high u32] | `owner` 20 bytes, no padding) are +//! byte-identical across x86_64 and riscv64. This is what makes off-chain +//! prediction with `host-real` sound: the host reproduces, byte for byte, +//! exactly what the machine computes on-chain. + use crate::bindings; use crate::error::LedgerError; use crate::types::*; @@ -5,44 +31,84 @@ use std::ffi::CString; use std::path::Path; use std::ptr; -/// Storage mode for file-backed ledgers. +/// Configuration for file-backed ledger initialization. +/// +/// The backing file is now always opened in create-or-open mode: it is created +/// when missing and validated (size/version) when it already exists. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LedgerFileConfig { + pub offset: usize, + pub memory_length: usize, + pub max_accounts: usize, + pub max_assets: usize, + pub max_balances: usize, +} + +impl Default for LedgerFileConfig { + fn default() -> Self { + Self { + offset: 0, + memory_length: 1024 * 1024, + max_accounts: 256, + max_assets: 256, + max_balances: 1024, + } + } +} + +/// The single, immutable asset that a single-asset ledger tracks. +/// +/// Chosen once when the ledger is created and fixed for the lifetime of the +/// backing store. Reopening the same file with a different asset is rejected by +/// the underlying library. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LedgerMemoryMode { - OpenOnly, - CreateOnly, +pub enum LedgerAsset { + /// The base asset (ether). No token address. + Ether, + /// A single ERC-20 token, identified by its contract address. + Erc20(TokenAddress), } -impl LedgerMemoryMode { - fn to_c(self) -> bindings::cma_ledger_memory_mode_t { +impl LedgerAsset { + /// Lower to the C `(asset_type, token_address)` pair. The address is returned + /// by value so the caller can keep it alive while passing a pointer to it. + fn to_c( + self, + ) -> ( + bindings::cma_ledger_asset_type_t, + Option, + ) { match self { - LedgerMemoryMode::OpenOnly => bindings::cma_ledger_memory_mode_t_CMA_LEDGER_OPEN_ONLY, - LedgerMemoryMode::CreateOnly => { - bindings::cma_ledger_memory_mode_t_CMA_LEDGER_CREATE_ONLY - } + LedgerAsset::Ether => ( + bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_BASE, + None, + ), + LedgerAsset::Erc20(addr) => ( + bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS, + Some(addr.to_c()), + ), } } } -/// Configuration for file-backed ledger initialization. +/// Configuration for file-backed single-asset ledger initialization. +/// +/// Unlike [`LedgerFileConfig`], a single-asset ledger has no `max_assets` +/// (there is exactly one) or `max_balances`; `max_accounts` is the capacity of +/// the withdrawable-balance drive. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct LedgerFileConfig { - pub mode: LedgerMemoryMode, +pub struct LedgerSingleFileConfig { pub offset: usize, pub memory_length: usize, pub max_accounts: usize, - pub max_assets: usize, - pub max_balances: usize, } -impl Default for LedgerFileConfig { +impl Default for LedgerSingleFileConfig { fn default() -> Self { Self { - mode: LedgerMemoryMode::CreateOnly, offset: 0, memory_length: 1024 * 1024, max_accounts: 256, - max_assets: 256, - max_balances: 1024, } } } @@ -65,15 +131,31 @@ impl Default for LedgerBufferConfig { } } -/// Safe wrapper around the C ledger +/// Safe wrapper around the C++ `libcma` ledger. +/// +/// # Thread safety +/// +/// `Ledger` wraps a self-referential C++ object (Boost.Interprocess): the +/// backend caches an internal reference bound to its own storage, so the object +/// is heap-pinned via [`Box`] to keep its address stable across moves. As a +/// consequence `Ledger` is **`!Send`** and **`!Sync`** — it must not be moved or +/// shared across threads without external synchronization. Downstream code that +/// needs `Send` typically wraps the `Ledger` in a mutex together with its own +/// `unsafe impl Send`. pub struct Ledger { - inner: bindings::cma_ledger_t, + // Boxed so the C++ ledger object has a STABLE heap address. The backends + // (`cma_ledger_memory`, `cma_ledger_single`) are self-referential — they cache + // a `managed_memory &m_memory` bound to their own `m_state` member — so the + // `cma_ledger_t` storage must never be relocated after init. Holding it inline + // would let a move of `Ledger` (e.g. returning it by value) memcpy the bytes and + // dangle that reference; the box keeps the storage put and moves only the pointer. + inner: Box, } impl Ledger { fn restore_empty_ledger(&mut self) { unsafe { - let _ = bindings::cma_ledger_init(&mut self.inner); + let _ = bindings::cma_ledger_init(&mut *self.inner); } } @@ -82,12 +164,12 @@ impl Ledger { init_fn: impl FnOnce(*mut bindings::cma_ledger_t) -> i32, ) -> Result<(), LedgerError> { unsafe { - let fini_result = bindings::cma_ledger_fini(&mut self.inner); + let fini_result = bindings::cma_ledger_fini(&mut *self.inner); if fini_result < 0 { return Err(LedgerError::from_code(fini_result)); } - let init_result = init_fn(&mut self.inner); + let init_result = init_fn(&mut *self.inner); if init_result < 0 { self.restore_empty_ledger(); return Err(LedgerError::from_code(init_result)); @@ -100,19 +182,21 @@ impl Ledger { /// Initialize a new ledger pub fn new() -> Result { unsafe { - let mut ledger = std::mem::zeroed::(); - let result = bindings::cma_ledger_init(&mut ledger); + // Allocate the storage on the heap FIRST, then construct the C++ object + // in place, so its address is fixed for the lifetime of the `Ledger`. + let mut inner = Box::new(std::mem::zeroed::()); + let result = bindings::cma_ledger_init(&mut *inner); if result < 0 { return Err(LedgerError::from_code(result)); } - Ok(Ledger { inner: ledger }) + Ok(Ledger { inner }) } } /// Reset the ledger pub fn reset(&mut self) -> Result<(), LedgerError> { unsafe { - let result = bindings::cma_ledger_reset(&mut self.inner); + let result = bindings::cma_ledger_reset(&mut *self.inner); if result < 0 { return Err(LedgerError::from_code(result)); } @@ -140,7 +224,7 @@ impl Ledger { .unwrap_or_else(|| bindings::cmt_abi_u256_t { data: [0u8; 32] }); let result = bindings::cma_ledger_retrieve_asset( - &mut self.inner, + &mut *self.inner, &mut out_asset_id, if token_address.is_some() { &mut out_token_address @@ -231,7 +315,7 @@ impl Ledger { }; let result = bindings::cma_ledger_retrieve_account( - &mut self.inner, + &mut *self.inner, &mut out_account_id, &mut c_account, addr_ptr, @@ -275,7 +359,7 @@ impl Ledger { unsafe { let c_amount = amount.to_c(); let result = bindings::cma_ledger_deposit( - &mut self.inner, + &mut *self.inner, asset_id.0, to_account_id.0, &c_amount, @@ -299,7 +383,7 @@ impl Ledger { unsafe { let c_amount = amount.to_c(); let result = bindings::cma_ledger_withdraw( - &mut self.inner, + &mut *self.inner, asset_id.0, from_account_id.0, &c_amount, @@ -324,7 +408,7 @@ impl Ledger { unsafe { let c_amount = amount.to_c(); let result = bindings::cma_ledger_transfer( - &mut self.inner, + &mut *self.inner, asset_id.0, from_account_id.0, to_account_id.0, @@ -348,7 +432,7 @@ impl Ledger { unsafe { let mut out_balance = std::mem::zeroed::(); let result = bindings::cma_ledger_get_balance( - &self.inner as *const _ as *mut _, + &*self.inner as *const _ as *mut _, asset_id.0, account_id.0, &mut out_balance, @@ -363,14 +447,14 @@ impl Ledger { } } - /// Get total supply for an asset (via [`cma_ledger_retrieve_asset`] with find). + /// Get total supply for an asset (via `cma_ledger_retrieve_asset` with find). pub fn get_total_supply(&self, asset_id: LedgerAssetId) -> Result { unsafe { let mut asset_id_mut = asset_id.0; let mut asset_type = bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_ID; let mut out_supply = std::mem::zeroed::(); let result = bindings::cma_ledger_retrieve_asset( - &self.inner as *const _ as *mut _, + &*self.inner as *const _ as *mut _, &mut asset_id_mut, ptr::null_mut(), ptr::null_mut(), @@ -411,7 +495,6 @@ impl Ledger { bindings::cma_ledger_init_file( ledger, file_path.as_ptr(), - config.mode.to_c(), config.offset, config.memory_length, config.max_accounts, @@ -438,12 +521,67 @@ impl Ledger { ) }) } + + /// Reinitialize this ledger as a single-asset ledger backed by a file + /// (create-or-open). The asset (ether or one ERC-20) is fixed for the life + /// of the backing file; balances are 64-bit. + pub fn init_single_from_file>( + &mut self, + file_path: P, + config: LedgerSingleFileConfig, + asset: LedgerAsset, + ) -> Result<(), LedgerError> { + let file_path = CString::new(file_path.as_ref().to_string_lossy().as_bytes()) + .map_err(|_| LedgerError::Other(-22))?; + let (asset_type, token_address) = asset.to_c(); + + self.reinitialize(|ledger| unsafe { + bindings::cma_ledger_init_single_file( + ledger, + file_path.as_ptr(), + config.offset, + config.memory_length, + config.max_accounts, + asset_type, + token_address + .as_ref() + .map(|addr| addr as *const _) + .unwrap_or(ptr::null()), + ) + }) + } + + /// Reinitialize this ledger as a single-asset ledger over caller-provided + /// memory (non-persistent). `max_accounts` is the capacity of the + /// withdrawable-balance drive. + pub fn init_single_from_buffer( + &mut self, + buffer: &mut [u8], + max_accounts: usize, + asset: LedgerAsset, + ) -> Result<(), LedgerError> { + let (asset_type, token_address) = asset.to_c(); + + self.reinitialize(|ledger| unsafe { + bindings::cma_ledger_init_single_buffer( + ledger, + buffer.as_mut_ptr() as *mut _, + buffer.len(), + max_accounts, + asset_type, + token_address + .as_ref() + .map(|addr| addr as *const _) + .unwrap_or(ptr::null()), + ) + }) + } } impl Drop for Ledger { fn drop(&mut self) { unsafe { - bindings::cma_ledger_fini(&mut self.inner); + bindings::cma_ledger_fini(&mut *self.inner); } } } diff --git a/src/lib.rs b/src/lib.rs index d6b313c..d88f070 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,72 @@ +//! Rust bindings for Cartesi's `libcma` (Cartesi Machine Application tooling): +//! parse rollup inputs, build on-chain voucher payloads, and manage a ledger of +//! application assets (ETH, ERC-20, ERC-721, ERC-1155). +//! +//! # Backends +//! +//! Exactly one backend feature is compiled in — they are **mutually exclusive**, +//! enforced by the `compile_error!` guards below: +//! +//! - **`mock`** (default) — an in-memory **stub** ledger (`src/mocks.rs`). Needs +//! no C++ toolchain, network, or RISC-V archive; for compile/plumbing tests +//! only. It is **not** real libcma and must never be used in production. +//! - **`host-real`** — the real C++ libcma built for the host (x86_64). Used +//! off-chain, e.g. a sequencer predicting the Cartesi machine's ledger. +//! - **`riscv64`** — the real C++ libcma cross-compiled to run inside the +//! Cartesi machine. +//! +//! Selecting a real backend requires disabling default features, otherwise the +//! default `mock` stays enabled and silently wins (you link the stub): +//! +//! ```text +//! cargo build --no-default-features --features host-real # or riscv64 +//! ``` +//! +//! # Reproducibility invariant +//! +//! `host-real` and `riscv64` compile libcma with SIMD-free / generic flags +//! (`-DBOOST_UNORDERED_DISABLE_SSE2`, `-DBOOST_UNORDERED_DISABLE_NEON`, +//! `-DBOOST_INTERPROCESS_FORCE_GENERIC_EMULATION`) so the on-disk 32-byte account +//! records (single-asset drive format v2: `balance` uint96 little-endian [low u64 | +//! high u32] | `owner` 20 bytes, no padding) are +//! byte-identical across x86_64 and riscv64. That invariant is what makes +//! off-chain prediction with `host-real` sound: the host reproduces, byte for +//! byte, exactly what the machine computes on-chain. +//! +//! See [`ledger::Ledger`] for the ledger wrapper and its thread-safety contract. + +// --------------------------------------------------------------------------- +// Backend selection guard. +// +// Exactly one backend feature must be enabled — they are mutually exclusive: +// `mock` — in-memory STUB ledger (default; NOT real libcma, never use in production). +// `host-real` — real C++ libcma compiled for the host (x86_64). +// `riscv64` — real C++ libcma cross-compiled for the Cartesi machine (riscv64). +// +// The build.rs link gate keys off `mock`, so enabling two backends (e.g. leaving the +// default `mock` on while adding `host-real`) would silently link the fake ledger. Turn +// that footgun into a hard compile error. +#[cfg(any( + all(feature = "mock", feature = "host-real"), + all(feature = "mock", feature = "riscv64"), + all(feature = "host-real", feature = "riscv64"), +))] +compile_error!( + "libcma_binding_rust: more than one backend feature is enabled, but they are mutually \ + exclusive — enable exactly one of `mock`, `host-real`, or `riscv64`; for a real build use \ + `default-features = false, features = [\"host-real\"]` (or `riscv64`). If you enabled a real \ + backend without `default-features = false`, the default `mock` feature is still on — that is \ + almost certainly the cause; disable default features." +); + +#[cfg(not(any(feature = "mock", feature = "host-real", feature = "riscv64")))] +compile_error!( + "libcma_binding_rust: no backend feature is enabled — enable exactly one of `mock`, \ + `host-real`, or `riscv64`; for a real build use `default-features = false, \ + features = [\"host-real\"]` (or `riscv64`)." +); +// --------------------------------------------------------------------------- + pub mod bindings { #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] @@ -5,16 +74,27 @@ pub mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } -#[cfg(feature = "native")] +#[cfg(feature = "mock")] mod mocks; pub mod error; -pub mod types; -pub mod ledger; pub mod helpers; +pub mod ledger; pub mod parser; +pub mod types; pub use error::{LedgerError, ParserError}; -pub use ledger::{Ledger, LedgerBufferConfig, LedgerFileConfig, LedgerMemoryMode}; -pub use parser::{CmaParserInputType, CmaParserVoucherType, CmaParserError, CmaVoucher, CmaParserUnidentifiedInput, CmaParserEtherDeposit, CmaParserErc20Deposit, CmaParserErc721Deposit, CmaParserErc1155SingleDeposit, CmaParserErc1155BatchDeposit, CmaParserEtherWithdrawal, CmaParserErc20Withdrawal, CmaParserErc721Withdrawal, CmaParserErc1155SingleWithdrawal, CmaParserErc1155BatchWithdrawal, CmaParserEtherTransfer, CmaParserErc20Transfer, CmaParserErc721Transfer, CmaParserErc1155SingleTransfer, CmaParserErc1155BatchTransfer, CmaParserBalance, CmaParserSupply, CmaParserInput, CmaParserInputData, cma_parser_get_last_error_message}; -pub use types::*; \ No newline at end of file +pub use ledger::{ + Ledger, LedgerAsset, LedgerBufferConfig, LedgerFileConfig, LedgerSingleFileConfig, +}; +pub use parser::{ + cma_parser_get_last_error_message, CmaParserBalance, CmaParserErc1155BatchDeposit, + CmaParserErc1155BatchTransfer, CmaParserErc1155BatchWithdrawal, CmaParserErc1155SingleDeposit, + CmaParserErc1155SingleTransfer, CmaParserErc1155SingleWithdrawal, CmaParserErc20Deposit, + CmaParserErc20Transfer, CmaParserErc20Withdrawal, CmaParserErc721Deposit, + CmaParserErc721Transfer, CmaParserErc721Withdrawal, CmaParserError, CmaParserEtherDeposit, + CmaParserEtherTransfer, CmaParserEtherWithdrawal, CmaParserInput, CmaParserInputData, + CmaParserInputType, CmaParserSupply, CmaParserUnidentifiedInput, CmaParserVoucherType, + CmaVoucher, +}; +pub use types::*; diff --git a/src/mocks.rs b/src/mocks.rs index 5b20440..38a756b 100644 --- a/src/mocks.rs +++ b/src/mocks.rs @@ -1,8 +1,24 @@ -#![cfg(feature = "native")] +//! MOCK libcma backend — a behavioural STUB for compile/plumbing tests ONLY. +//! +//! This module provides in-memory, thread-local stand-ins for the `cma_*` FFI symbols so the +//! crate can be built and exercised without the real C++ libcma (and without a C++ toolchain or +//! network). It is enabled by the default `mock` feature. +//! +//! It does NOT reproduce libcma's real ledger / records-image semantics. In particular the +//! `cma_ledger_init_single_*` stubs ignore `asset_type`, `token_address`, and `n_accounts` and +//! simply delegate to `cma_ledger_init`, so the account records they produce are NOT byte-identical +//! to the real single-asset records image. Balances, overflow behaviour, and encoding are likewise +//! only approximated. +//! +//! Consequences: tests run against this mock validate plumbing (pointer/marshalling/error paths), +//! NOT ledger correctness. Passing here is NOT evidence that a change is correct against real +//! libcma. Build with the `host-real` feature (`default-features = false, features = ["host-real"]`) +//! to validate against the actual C++ ledger. +#![cfg(feature = "mock")] use crate::bindings; -use std::collections::HashMap; use std::cell::RefCell; +use std::collections::HashMap; use std::ptr; // Simple in-memory storage for mock ledger @@ -13,7 +29,7 @@ struct MockLedgerState { accounts: HashMap, balances: HashMap<(u64, u64), [u8; 32]>, // (asset_id, account_id) -> balance asset_lookup: HashMap<([u8; 20], [u8; 32]), u64>, // (token_addr, token_id) -> asset_id - account_lookup: HashMap<[u8; 20], u64>, // wallet_address -> account_id + account_lookup: HashMap<[u8; 20], u64>, // wallet_address -> account_id } #[allow(dead_code)] @@ -66,7 +82,6 @@ pub unsafe extern "C" fn cma_ledger_init(ledger: *mut bindings::cma_ledger_t) -> pub unsafe extern "C" fn cma_ledger_init_file( ledger: *mut bindings::cma_ledger_t, memory_file_name: *const std::ffi::c_char, - _mode: bindings::cma_ledger_memory_mode_t, _offset: usize, mem_length: usize, _n_accounts: usize, @@ -80,6 +95,39 @@ pub unsafe extern "C" fn cma_ledger_init_file( cma_ledger_init(ledger) } +#[no_mangle] +pub unsafe extern "C" fn cma_ledger_init_single_file( + ledger: *mut bindings::cma_ledger_t, + memory_file_name: *const std::ffi::c_char, + _offset: usize, + mem_length: usize, + _n_accounts: usize, + _asset_type: bindings::cma_ledger_asset_type_t, + _token_address: *const bindings::cma_token_address_t, +) -> i32 { + if ledger.is_null() || memory_file_name.is_null() || mem_length == 0 { + return bindings::CMA_LEDGER_ERROR_UNKNOWN as i32; + } + + cma_ledger_init(ledger) +} + +#[no_mangle] +pub unsafe extern "C" fn cma_ledger_init_single_buffer( + ledger: *mut bindings::cma_ledger_t, + buffer: *mut std::ffi::c_void, + mem_length: usize, + _n_accounts: usize, + _asset_type: bindings::cma_ledger_asset_type_t, + _token_address: *const bindings::cma_token_address_t, +) -> i32 { + if ledger.is_null() || buffer.is_null() || mem_length == 0 { + return bindings::CMA_LEDGER_ERROR_UNKNOWN as i32; + } + + cma_ledger_init(ledger) +} + #[no_mangle] pub unsafe extern "C" fn cma_ledger_init_buffer( ledger: *mut bindings::cma_ledger_t, @@ -508,8 +556,7 @@ pub unsafe extern "C" fn cma_parser_decode_advance( out: *mut bindings::cma_parser_input_t, ) -> i32 { if !out.is_null() { - (*out).type_ = - bindings::cma_parser_input_type_t_CMA_PARSER_INPUT_TYPE_ETHER_DEPOSIT; + (*out).type_ = bindings::cma_parser_input_type_t_CMA_PARSER_INPUT_TYPE_ETHER_DEPOSIT; // union access generated by bindgen: let dep = &mut (*out).__bindgen_anon_1.ether_deposit; diff --git a/src/parser.rs b/src/parser.rs index 2352e27..5e72f46 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,12 +1,111 @@ use crate::helpers::hex_to_string; -use ethers_core::abi::{ParamType, Token, decode, encode}; -use ethers_core::types::{Address, Bytes, U256}; -use ethers_core::utils::{id, to_checksum}; +use alloy_primitives::{Address, Bytes, U256}; use hex; use json::JsonValue; use std::cell::RefCell; +use self::abi_compat::{decode, encode, id, to_checksum, ParamType, Token}; + +/// Minimal `ethers_core::abi`-shaped adapter over `alloy-dyn-abi`, covering only the ABI +/// variants this parser uses (`Uint(256)`, `Bytes`, `Address`, `Array`). ethers-rs is EOL; +/// `encode`/`decode` map to alloy's `abi_encode_params`/`abi_decode_params`, the byte-for-byte +/// equivalents of ethers' free `encode`/`decode` — the parser's ABI test vectors pin this. +mod abi_compat { + use alloy_dyn_abi::{DynSolType, DynSolValue}; + use alloy_primitives::{keccak256, Address, U256}; + + #[derive(Clone, Debug)] + pub enum ParamType { + Uint(usize), + Bytes, + Address, + Array(Box), + } + + #[derive(Clone, Debug)] + pub enum Token { + Uint(U256), + Bytes(Vec), + Address(Address), + Array(Vec), + } + + impl ParamType { + fn to_dyn(&self) -> DynSolType { + match self { + ParamType::Uint(bits) => DynSolType::Uint(*bits), + ParamType::Bytes => DynSolType::Bytes, + ParamType::Address => DynSolType::Address, + ParamType::Array(inner) => DynSolType::Array(Box::new(inner.to_dyn())), + } + } + } + + impl Token { + fn to_dyn(&self) -> DynSolValue { + match self { + Token::Uint(v) => DynSolValue::Uint(*v, 256), + Token::Bytes(b) => DynSolValue::Bytes(b.clone()), + Token::Address(a) => DynSolValue::Address(*a), + Token::Array(items) => { + DynSolValue::Array(items.iter().map(Token::to_dyn).collect()) + } + } + } + + fn from_dyn(value: &DynSolValue) -> Option { + match value { + DynSolValue::Uint(v, _) => Some(Token::Uint(*v)), + DynSolValue::Bytes(b) => Some(Token::Bytes(b.clone())), + DynSolValue::Address(a) => Some(Token::Address(*a)), + DynSolValue::Array(items) | DynSolValue::FixedArray(items) => items + .iter() + .map(Token::from_dyn) + .collect::>>() + .map(Token::Array), + _ => None, + } + } + } + + /// ABI-encode tokens as top-level params — equivalent of ethers `encode(&[Token])`. + pub fn encode(tokens: &[Token]) -> Vec { + DynSolValue::Tuple(tokens.iter().map(Token::to_dyn).collect()).abi_encode_params() + } + + /// Opaque decode error; call sites only map it to their own error type. + #[derive(Debug)] + pub struct AbiDecodeError; + + /// ABI-decode top-level params — equivalent of ethers `decode(&[ParamType], data)`. + pub fn decode(types: &[ParamType], data: &[u8]) -> Result, AbiDecodeError> { + let tuple_ty = DynSolType::Tuple(types.iter().map(ParamType::to_dyn).collect()); + match tuple_ty + .abi_decode_params(data) + .map_err(|_| AbiDecodeError)? + { + DynSolValue::Tuple(items) => items + .iter() + .map(Token::from_dyn) + .collect::>>() + .ok_or(AbiDecodeError), + _ => Err(AbiDecodeError), + } + } + + /// 4-byte function selector — equivalent of `ethers_core::utils::id`. + pub fn id(signature: &str) -> [u8; 4] { + let hash = keccak256(signature.as_bytes()); + [hash[0], hash[1], hash[2], hash[3]] + } + + /// Checksummed address string — equivalent of `ethers_core::utils::to_checksum`. + pub fn to_checksum(address: &Address, chain_id: Option) -> String { + address.to_checksum(chain_id) + } +} + thread_local! { static LAST_PARSER_ERROR: RefCell = RefCell::new(String::new()); } @@ -19,8 +118,7 @@ fn payload_bytes(input: &JsonValue) -> Result, CmaParserError> { let payload_hex = input["data"]["payload"] .as_str() .ok_or(CmaParserError::MalformedInput)?; - hex::decode(payload_hex.trim_start_matches("0x")) - .map_err(|_| CmaParserError::MalformedInput) + hex::decode(payload_hex.trim_start_matches("0x")).map_err(|_| CmaParserError::MalformedInput) } fn require_len(bytes: &[u8], min: usize) -> Result<(), CmaParserError> { @@ -35,11 +133,8 @@ fn decode_abi_tail_two_bytes(tail: &[u8]) -> Result<(Bytes, Bytes), CmaParserErr if tail.is_empty() { return Ok((Bytes::from(vec![]), Bytes::from(vec![]))); } - let decoded = decode( - &[ParamType::Bytes, ParamType::Bytes], - tail, - ) - .map_err(|_| CmaParserError::MalformedInput)?; + let decoded = decode(&[ParamType::Bytes, ParamType::Bytes], tail) + .map_err(|_| CmaParserError::MalformedInput)?; let base = match &decoded[0] { Token::Bytes(b) => Bytes::from(b.clone()), _ => return Err(CmaParserError::MalformedInput), @@ -128,7 +223,7 @@ fn parse_hex_account_id(value: &str) -> Result { return Err(CmaParserError::MalformedInput); } bytes[offset..offset + decoded.len()].copy_from_slice(&decoded); - Ok(U256::from_big_endian(&bytes)) + Ok(U256::from_be_slice(&bytes)) } fn parse_hex_token_id(value: &str) -> Result { @@ -150,7 +245,7 @@ fn parse_hex_token_id(value: &str) -> Result { return Err(CmaParserError::MalformedInput); } bytes[offset..offset + decoded.len()].copy_from_slice(&decoded); - Ok(U256::from_big_endian(&bytes)) + Ok(U256::from_be_slice(&bytes)) } fn parse_token_address(value: &str) -> Result { @@ -196,7 +291,7 @@ pub enum TxHexCodes { TransferErc1155Single = 0xe1c913ed, // Bytecode for solidity TransferErc1155Batch(address,bytes32,uint256[],uint256[],bytes) = 638ac6f9 TransferErc1155Batch = 0x638ac6f9, - Unidentified + Unidentified, } impl TxHexCodes { @@ -212,7 +307,7 @@ impl TxHexCodes { Self::TransferErc721 => "0xaf615a5a", Self::TransferErc1155Single => "0xe1c913ed", Self::TransferErc1155Batch => "0x638ac6f9", - Self::Unidentified => "0x00000000" + Self::Unidentified => "0x00000000", } } @@ -237,13 +332,21 @@ impl TxHexCodes { Self::WithdrawEther => CmaParserInputType::CmaParserInputTypeEtherWithdrawal, Self::WithdrawErc20 => CmaParserInputType::CmaParserInputTypeErc20Withdrawal, Self::WithdrawErc721 => CmaParserInputType::CmaParserInputTypeErc721Withdrawal, - Self::WithdrawErc1155Single => CmaParserInputType::CmaParserInputTypeErc1155SingleWithdrawal, - Self::WithdrawErc1155Batch => CmaParserInputType::CmaParserInputTypeErc1155BatchWithdrawal, + Self::WithdrawErc1155Single => { + CmaParserInputType::CmaParserInputTypeErc1155SingleWithdrawal + } + Self::WithdrawErc1155Batch => { + CmaParserInputType::CmaParserInputTypeErc1155BatchWithdrawal + } Self::TransferEther => CmaParserInputType::CmaParserInputTypeEtherTransfer, Self::TransferErc20 => CmaParserInputType::CmaParserInputTypeErc20Transfer, Self::TransferErc721 => CmaParserInputType::CmaParserInputTypeErc721Transfer, - Self::TransferErc1155Single => CmaParserInputType::CmaParserInputTypeErc1155SingleTransfer, - Self::TransferErc1155Batch => CmaParserInputType::CmaParserInputTypeErc1155BatchTransfer, + Self::TransferErc1155Single => { + CmaParserInputType::CmaParserInputTypeErc1155SingleTransfer + } + Self::TransferErc1155Batch => { + CmaParserInputType::CmaParserInputTypeErc1155BatchTransfer + } Self::Unidentified => CmaParserInputType::CmaParserInputTypeUnidentified, } } @@ -304,7 +407,9 @@ impl CmaParserInputType { "Erc721Transfer" => CmaParserInputType::CmaParserInputTypeErc721Transfer, "Erc1155SingleTransfer" => CmaParserInputType::CmaParserInputTypeErc1155SingleTransfer, "Erc1155BatchTransfer" => CmaParserInputType::CmaParserInputTypeErc1155BatchTransfer, - "ledger_getBalance" | "ledgerGetBalance" => CmaParserInputType::CmaParserInputTypeBalance, + "ledger_getBalance" | "ledgerGetBalance" => { + CmaParserInputType::CmaParserInputTypeBalance + } "ledger_getTotalSupply" | "ledgerGetTotalSupply" => { CmaParserInputType::CmaParserInputTypeSupply } @@ -614,10 +719,12 @@ fn handle_unidentified_method(input: JsonValue) -> Result Result { @@ -625,7 +732,7 @@ fn handle_parse_ether_deposit(input: JsonValue) -> Result Result Result Result Result { @@ -679,8 +785,8 @@ fn handle_parse_erc1155_single_deposit( let token = Address::from_slice(&bytes[0..20]); let sender = Address::from_slice(&bytes[20..40]); - let token_id = U256::from_big_endian(&bytes[40..72]); - let amount = U256::from_big_endian(&bytes[72..104]); + let token_id = U256::from_be_slice(&bytes[40..72]); + let amount = U256::from_be_slice(&bytes[72..104]); let (base_layer_data, exec_layer_data) = decode_abi_tail_two_bytes(&bytes[104..])?; Ok(CmaParserInputData::Erc1155SingleDeposit( @@ -742,11 +848,8 @@ fn handle_ether_withdrawal(input: JsonValue) -> Result *v, @@ -757,11 +860,13 @@ fn handle_ether_withdrawal(input: JsonValue) -> Result return Err(CmaParserError::MalformedInput), }; - Ok(CmaParserInputData::EtherWithdrawal(CmaParserEtherWithdrawal { - receiver, - amount, - exec_layer_data: exec_layer_hex(exec_layer_byte), - })) + Ok(CmaParserInputData::EtherWithdrawal( + CmaParserEtherWithdrawal { + receiver, + amount, + exec_layer_data: exec_layer_hex(exec_layer_byte), + }, + )) } fn handle_erc20_withdrawal(input: JsonValue) -> Result { @@ -788,12 +893,14 @@ fn handle_erc20_withdrawal(input: JsonValue) -> Result return Err(CmaParserError::MalformedInput), }; - Ok(CmaParserInputData::Erc20Withdrawal(CmaParserErc20Withdrawal { - receiver, - token, - amount, - exec_layer_data: exec_layer_hex(exec_layer_byte), - })) + Ok(CmaParserInputData::Erc20Withdrawal( + CmaParserErc20Withdrawal { + receiver, + token, + amount, + exec_layer_data: exec_layer_hex(exec_layer_byte), + }, + )) } fn handle_erc721_withdrawal(input: JsonValue) -> Result { @@ -820,12 +927,14 @@ fn handle_erc721_withdrawal(input: JsonValue) -> Result return Err(CmaParserError::MalformedInput), }; - Ok(CmaParserInputData::Erc721Withdrawal(CmaParserErc721Withdrawal { - receiver, - token, - token_id, - exec_layer_data: exec_layer_hex(exec_layer_byte), - })) + Ok(CmaParserInputData::Erc721Withdrawal( + CmaParserErc721Withdrawal { + receiver, + token, + token_id, + exec_layer_data: exec_layer_hex(exec_layer_byte), + }, + )) } fn handle_erc1155_single_withdrawal( @@ -874,9 +983,7 @@ fn handle_erc1155_single_withdrawal( )) } -fn handle_erc1155_batch_withdrawal( - input: JsonValue, -) -> Result { +fn handle_erc1155_batch_withdrawal(input: JsonValue) -> Result { let bytes = payload_bytes(&input)?; let encoded_args = decode_after_selector(&bytes)?; let receiver = withdrawal_receiver(&input)?; @@ -1027,17 +1134,17 @@ fn handle_erc721_transfer(input: JsonValue) -> Result return Err(CmaParserError::MalformedInput), }; - Ok(CmaParserInputData::Erc721Transfer(CmaParserErc721Transfer { - receiver, - token, - token_id, - exec_layer_data: exec_layer_hex(exec_layer_byte), - })) + Ok(CmaParserInputData::Erc721Transfer( + CmaParserErc721Transfer { + receiver, + token, + token_id, + exec_layer_data: exec_layer_hex(exec_layer_byte), + }, + )) } -fn handle_erc1155_single_transfer( - input: JsonValue, -) -> Result { +fn handle_erc1155_single_transfer(input: JsonValue) -> Result { let bytes = payload_bytes(&input)?; let encoded_args = decode_after_selector(&bytes)?; @@ -1085,9 +1192,7 @@ fn handle_erc1155_single_transfer( )) } -fn handle_erc1155_batch_transfer( - input: JsonValue, -) -> Result { +fn handle_erc1155_batch_transfer(input: JsonValue) -> Result { let bytes = payload_bytes(&input)?; let encoded_args = decode_after_selector(&bytes)?; @@ -1163,7 +1268,7 @@ fn cma_decode_advance_inner( input: data, }); } - CmaParserInputType::CmaParserInputTypeErc1155SingleDeposit=> { + CmaParserInputType::CmaParserInputTypeErc1155SingleDeposit => { return handle_parse_erc1155_single_deposit(input).map(|data| CmaParserInput { req_type, input: data, @@ -1210,12 +1315,8 @@ fn cma_decode_advance_inner( CmaParserInputType::CmaParserInputTypeErc1155BatchWithdrawal => { handle_erc1155_batch_withdrawal(input) } - CmaParserInputType::CmaParserInputTypeEtherTransfer => { - handle_ether_transfer(input) - } - CmaParserInputType::CmaParserInputTypeErc20Transfer => { - handle_erc20_transfer(input) - } + CmaParserInputType::CmaParserInputTypeEtherTransfer => handle_ether_transfer(input), + CmaParserInputType::CmaParserInputTypeErc20Transfer => handle_erc20_transfer(input), CmaParserInputType::CmaParserInputTypeErc721Transfer => { handle_erc721_transfer(input) } @@ -1231,7 +1332,10 @@ fn cma_decode_advance_inner( _ => Err(CmaParserError::IncompatibleInput), }; - result.map(|data| CmaParserInput { req_type, input: data }) + result.map(|data| CmaParserInput { + req_type, + input: data, + }) } _ => Err(CmaParserError::Unknown), } @@ -1262,8 +1366,8 @@ fn handle_ledger_get_balance(parsed_json: JsonValue) -> Result Result Result Result { - handle_ledger_get_balance(payload_json).map(|data| CmaParserInput { + CmaParserInputType::CmaParserInputTypeBalance => handle_ledger_get_balance(payload_json) + .map(|data| CmaParserInput { req_type, input: CmaParserInputData::Balance(data), - }) - } + }), CmaParserInputType::CmaParserInputTypeSupply => { handle_ledger_get_supply(payload_json).map(|data| CmaParserInput { req_type, @@ -1356,8 +1457,7 @@ fn handle_ether_voucher_encoding( voucher_request: &CmaVoucherFieldType, ) -> Result { if let CmaVoucherFieldType::EtherVoucherFields(fields) = voucher_request { - let mut value_bytes = [0u8; 32]; - fields.amount.to_big_endian(&mut value_bytes); + let value_bytes = fields.amount.to_be_bytes::<32>(); Ok(CmaVoucher { destination: to_checksum(&fields.receiver, None), @@ -1450,11 +1550,8 @@ fn handle_erc1155_batch_voucher_encoding( return Err(CmaParserError::MalformedInput); } - let token_id_tokens: Vec = fields - .token_ids - .iter() - .map(|id| Token::Uint(*id)) - .collect(); + let token_id_tokens: Vec = + fields.token_ids.iter().map(|id| Token::Uint(*id)).collect(); let amount_tokens: Vec = fields .amounts .iter() diff --git a/src/types.rs b/src/types.rs index 3fa995a..3920870 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,6 +1,6 @@ use crate::bindings; // use std::fmt; -pub use ethers_core::types::{Address, U256}; +pub use alloy_primitives::{Address, U256}; use std::str::FromStr; pub const ADDRESS_LENGTH: usize = 20; @@ -41,15 +41,17 @@ impl AddressCBindingsExt for Address { } fn as_array(&self) -> [u8; ADDRESS_LENGTH] { - self.0 + self.0 .0 } fn as_bytes(&self) -> &[u8; ADDRESS_LENGTH] { - &self.0 + &self.0 .0 } fn to_c(&self) -> bindings::cmt_abi_address_t { - bindings::cmt_abi_address_t { data: self.as_array() } + bindings::cmt_abi_address_t { + data: self.as_array(), + } } fn from_c(c_addr: &bindings::cmt_abi_address_t) -> Self { @@ -81,11 +83,11 @@ pub trait U256CBindingsExt { impl U256CBindingsExt for U256 { fn new(bytes: [u8; U256_LENGTH]) -> Self { - U256::from_big_endian(&bytes) + U256::from_be_slice(&bytes) } fn zero() -> Self { - U256::zero() + U256::ZERO } fn from_u64(value: u64) -> Self { @@ -93,36 +95,37 @@ impl U256CBindingsExt for U256 { } fn from_be_bytes(bytes: [u8; U256_LENGTH]) -> Self { - U256::from_big_endian(&bytes) + U256::from_be_slice(&bytes) } fn from_slice(slice: &[u8]) -> Result { if slice.len() != U256_LENGTH { return Err(format!("U256 must be 32 bytes, got {}", slice.len())); } - Ok(U256::from_big_endian(slice)) + Ok(U256::from_be_slice(slice)) } fn as_be_bytes(&self) -> [u8; U256_LENGTH] { - let mut out = [0u8; U256_LENGTH]; - self.to_big_endian(&mut out); - out + self.to_be_bytes::() } fn to_u128_opt(&self) -> Option { - if self.bits() > 128 { + // `bit_len` counts significant bits; > 128 means it won't fit a u128. + if self.bit_len() > 128 { None } else { - Some(self.low_u128()) + Some(self.to::()) } } fn to_c(&self) -> bindings::cmt_abi_u256_t { - bindings::cmt_abi_u256_t { data: self.as_be_bytes() } + bindings::cmt_abi_u256_t { + data: self.as_be_bytes(), + } } fn from_c(c_u256: &bindings::cmt_abi_u256_t) -> Self { - U256::from_big_endian(&c_u256.data) + U256::from_be_slice(&c_u256.data) } } @@ -227,9 +230,7 @@ pub struct OwnedCBytes { impl OwnedCBytes { pub fn new(data: impl Into>) -> Self { - Self { - inner: data.into(), - } + Self { inner: data.into() } } pub fn as_c_bytes(&self) -> bindings::cmt_abi_bytes_t { diff --git a/tests/host_real_records_layout.rs b/tests/host_real_records_layout.rs new file mode 100644 index 0000000..b3cfe25 --- /dev/null +++ b/tests/host_real_records_layout.rs @@ -0,0 +1,144 @@ +//! host-real records byte-layout + reproducibility test. +//! +//! Builds/runs ONLY under `--no-default-features --features host-real` (the real C++ libcma +//! compiled for the host). Under the default `mock` backend the caller buffer is ignored, so +//! there are no records to inspect and the whole file compiles to nothing. +//! +//! ```sh +//! cargo test --no-default-features --features host-real --test host_real_records_layout -- --nocapture +//! ``` +//! +//! Why this test exists: host↔machine reproducibility (the sequencer predicting the Cartesi +//! machine's ledger, the watchdog byte-comparing images, the emergency-withdrawal proof) all +//! depend on the on-drive account record having a FIXED, stable byte layout. This test locks +//! that layout down and asserts it is deterministic across runs. The layout is defined by +//! `third_party/machine-asset-tools/src/ledger_impl.h`: +//! +//! ```c +//! // Drive record format v2 (machine-asset-tools e4bfc24): the withdrawable balance was +//! // widened from uint64 to uint96, consuming the former padding. The owner therefore moved +//! // from offset 8 to offset 12, and there is no trailing pad. +//! struct alignas(32) cma_ledger_single_balance { +//! uint64_t balance_lo; // bytes 0..8 low 64 bits, little-endian +//! uint32_t balance_hi; // bytes 8..12 high 32 bits, little-endian (uint96 = hi<<64 | lo) +//! cma_abi_address_t address; // bytes 12..32 20-byte owner wallet address +//! }; +//! static_assert(sizeof(cma_ledger_single_balance) == 32); +//! static_assert(offsetof(cma_ledger_single_balance, address) == 12); +//! ``` +//! +//! NOTE (drive format v2): `MemoryFooter::VERSION` is now 2 — a v1 drive (uint64 balance, owner +//! at offset 8, 4-byte pad) is NOT compatible and would be silently misread by this format. +#![cfg(feature = "host-real")] + +use libcma_binding_rust::ledger::{Ledger, LedgerAsset}; +use libcma_binding_rust::{Address, U256}; + +const MEM_LEN: usize = 4 * 1024 * 1024; +const MAX_ACCOUNTS: usize = 4096; +const RECORDS_PREFIX: usize = MAX_ACCOUNTS * 32; // 128 KiB proven records region + +// Record byte layout v2 (see module docs / ledger_impl.h). These offsets are the invariant. +const RECORD_SIZE: usize = 32; +const BALANCE_LO_RANGE: std::ops::Range = 0..8; // low 64 bits, little-endian +const BALANCE_HI_RANGE: std::ops::Range = 8..12; // high 32 bits, little-endian +const OWNER_RANGE: std::ops::Range = 12..32; // 20-byte owner address + +/// Decode the uint96 balance from a record: `hi << 64 | lo` (both little-endian). +fn record_balance(rec: &[u8]) -> u128 { + let lo = u64::from_le_bytes(rec[BALANCE_LO_RANGE].try_into().unwrap()) as u128; + let hi = u32::from_le_bytes(rec[BALANCE_HI_RANGE].try_into().unwrap()) as u128; + (hi << 64) | lo +} + +fn token() -> Address { + "0x88A2120B7068E78692C8fd12E751d610B6377E4d" + .parse() + .unwrap() +} +fn alice() -> Address { + "0x1111111111111111111111111111111111111111" + .parse() + .unwrap() +} +fn bob() -> Address { + "0x2222222222222222222222222222222222222222" + .parse() + .unwrap() +} + +/// Initialise a single-asset (ERC-20) buffer-backed ledger over `buf` and credit each +/// `(owner, balance)`. The ledger state lives in `buf`'s 32-byte records prefix once this +/// returns. Mirrors the setup in `tests/host_real_smoke.rs`. +fn build_ledger(buf: &mut [u8], credits: &[(Address, u64)]) { + let mut ledger = Ledger::new().expect("ledger init"); + ledger + .init_single_from_buffer(buf, MAX_ACCOUNTS, LedgerAsset::Erc20(token())) + .expect("init single buffer"); + let asset = ledger + .retrieve_erc20_asset_via_address(token()) + .expect("asset"); + for &(owner, bal) in credits { + let account = ledger.retrieve_account_via_address(owner).expect("account"); + ledger + .deposit(asset, account, U256::from(bal)) + .expect("deposit"); + } +} + +/// Return the 32-byte record in `buf`'s records prefix whose owner field (bytes 12..32) +/// equals `owner`, or `None` if no such record exists. +fn find_record(buf: &[u8], owner: Address) -> Option<&[u8]> { + let want = owner.0 .0; + buf[..RECORDS_PREFIX] + .chunks_exact(RECORD_SIZE) + .find(|rec| rec[OWNER_RANGE] == want) +} + +/// Each credited account's 32-byte record is exactly `balance_lo(u64 LE) | balance_hi(u32 LE) | +/// owner(20)` — a uint96 balance followed by the 20-byte owner, with no trailing pad (format v2). +#[test] +fn record_layout_is_balance96_and_owner() { + let credits = [(alice(), 250u64), (bob(), 70u64)]; + + let mut buf = vec![0u8; MEM_LEN]; + build_ledger(&mut buf, &credits); + + for &(owner, bal) in &credits { + let rec = find_record(&buf, owner).unwrap_or_else(|| { + panic!("no record for {owner:?} — ledger state is not in the caller buffer") + }); + eprintln!("record for {owner:?} = {}", hex::encode(rec)); + + // balance: uint96, little-endian (low 64 bits bytes 0..8, high 32 bits bytes 8..12) + assert_eq!( + record_balance(rec), + bal as u128, + "balance field (uint96 LE) mismatch for {owner:?}" + ); + + // owner: 20-byte address, bytes 12..32 + assert_eq!( + rec[OWNER_RANGE], owner.0 .0, + "owner field (bytes 12..32) mismatch for {owner:?}" + ); + } +} + +/// Identical inputs must produce byte-identical records: the reproducibility property the +/// watchdog byte-compare and the emergency-withdrawal proof depend on. +#[test] +fn records_are_deterministic_across_runs() { + let credits = [(alice(), 250u64), (bob(), 70u64)]; + + let mut buf_a = vec![0u8; MEM_LEN]; + let mut buf_b = vec![0u8; MEM_LEN]; + build_ledger(&mut buf_a, &credits); + build_ledger(&mut buf_b, &credits); + + assert_eq!( + buf_a[..RECORDS_PREFIX], + buf_b[..RECORDS_PREFIX], + "identical credits must yield byte-identical records (host reproducibility)" + ); +} diff --git a/tests/host_real_smoke.rs b/tests/host_real_smoke.rs new file mode 100644 index 0000000..68b01e5 --- /dev/null +++ b/tests/host_real_smoke.rs @@ -0,0 +1,155 @@ +//! Smoke tests for the `host-real` feature: real C++ libcma compiled for the host. +//! +//! These only build/run under `--no-default-features --features host-real` (otherwise +//! the crate links the in-memory mock, which ignores the caller buffer). Run with: +//! +//! ```sh +//! cargo test --no-default-features --features host-real --test host_real_smoke -- --nocapture +//! ``` +//! +//! What they establish for the sequencer integration (see the CMA app's +//! `SEQUENCER-INTEGRATION-PLAN.md`, Phase 0/2): +//! 1. real libcma links and computes on the host (not the mock); +//! 2. the ledger state DOES live in the caller buffer's 32-byte records prefix +//! (so `create_dump` can read it out for the snapshot + emergency-withdrawal proof); +//! 3. `init_single_from_buffer` zero-initialises — it does NOT re-attach to existing +//! contents — so `from_dump` must rebuild a fresh ledger by re-crediting, not by +//! re-opening a saved buffer. +#![cfg(feature = "host-real")] + +use libcma_binding_rust::ledger::{Ledger, LedgerAsset}; +use libcma_binding_rust::{Address, U256}; + +const MEM_LEN: usize = 4 * 1024 * 1024; +const MAX_ACCOUNTS: usize = 4096; +const RECORDS_PREFIX: usize = MAX_ACCOUNTS * 32; // 128 KiB proven region + +fn token() -> Address { + "0x88A2120B7068E78692C8fd12E751d610B6377E4d" + .parse() + .unwrap() +} +fn alice() -> Address { + "0x1111111111111111111111111111111111111111" + .parse() + .unwrap() +} +fn bob() -> Address { + "0x2222222222222222222222222222222222222222" + .parse() + .unwrap() +} + +/// Real libcma links and computes balances on the host (i.e. we are NOT on the mock). +#[test] +fn real_libcma_links_and_computes_balances_on_host() { + let mut buf = vec![0u8; MEM_LEN]; + let mut ledger = Ledger::new().expect("ledger init"); + ledger + .init_single_from_buffer(&mut buf, MAX_ACCOUNTS, LedgerAsset::Erc20(token())) + .expect("init single buffer"); + + let asset = ledger + .retrieve_erc20_asset_via_address(token()) + .expect("asset"); + let account = ledger + .retrieve_account_via_address(alice()) + .expect("account"); + ledger + .deposit(asset, account, U256::from(100)) + .expect("deposit"); + + assert_eq!( + ledger.get_balance(asset, account).expect("balance"), + U256::from(100) + ); +} + +/// The ledger state lives in the caller buffer's records prefix, and each 32-byte record +/// carries the owner's 20-byte address. Prints the record layout so `create_dump` can be +/// written against the real bytes. +#[test] +fn records_prefix_holds_owner_and_balance() { + let mut buf = vec![0u8; MEM_LEN]; + let mut ledger = Ledger::new().expect("ledger init"); + ledger + .init_single_from_buffer(&mut buf, MAX_ACCOUNTS, LedgerAsset::Erc20(token())) + .expect("init single buffer"); + let asset = ledger + .retrieve_erc20_asset_via_address(token()) + .expect("asset"); + let acc = ledger + .retrieve_account_via_address(alice()) + .expect("account"); + ledger + .deposit(asset, acc, U256::from(250)) + .expect("deposit"); + + // Scan the 128 KiB records prefix (32-byte strides) for alice's address bytes. + let addr = alice().0 .0; + let mut found = None; + for (i, rec) in buf[..RECORDS_PREFIX].chunks_exact(32).enumerate() { + if rec.windows(20).any(|w| w == addr) { + eprintln!("record[{i}] = {}", hex::encode(rec)); + found = Some(i); + break; + } + } + assert!( + found.is_some(), + "alice's address must appear in the records prefix — state is not in the buffer" + ); +} + +/// The snapshot/restore mechanism for `from_dump`: capture the logical (address, balance) +/// set, then rebuild a fresh ledger by re-crediting. Total supply and per-account balances +/// must match. (This is what `from_dump` will do; it does NOT rely on re-opening a buffer.) +#[test] +fn restore_by_recredit_round_trips() { + let token = token(); + + // Original ledger. + let mut buf = vec![0u8; MEM_LEN]; + let mut l1 = Ledger::new().expect("ledger init"); + l1.init_single_from_buffer(&mut buf, MAX_ACCOUNTS, LedgerAsset::Erc20(token)) + .expect("init"); + let a1 = l1.retrieve_erc20_asset_via_address(token).expect("asset"); + let alice_id = l1.retrieve_account_via_address(alice()).expect("alice"); + let bob_id = l1.retrieve_account_via_address(bob()).expect("bob"); + l1.deposit(a1, alice_id, U256::from(250)) + .expect("dep alice"); + l1.deposit(a1, bob_id, U256::from(70)).expect("dep bob"); + let supply1 = l1.get_total_supply(a1).expect("supply1"); + + // Snapshot = the logical set (in id order, so the rebuild assigns identical ids). + let snapshot = [(alice(), U256::from(250)), (bob(), U256::from(70))]; + + // Restore into a fresh ledger by re-crediting. + let mut buf2 = vec![0u8; MEM_LEN]; + let mut l2 = Ledger::new().expect("ledger init 2"); + l2.init_single_from_buffer(&mut buf2, MAX_ACCOUNTS, LedgerAsset::Erc20(token)) + .expect("init 2"); + let a2 = l2.retrieve_erc20_asset_via_address(token).expect("asset 2"); + for (addr, bal) in snapshot { + let id = l2.retrieve_account_via_address(addr).expect("acct"); + l2.deposit(a2, id, bal).expect("recredit"); + } + + // Balances and total supply match the original. + let alice2 = l2.retrieve_account_via_address(alice()).expect("alice2"); + let bob2 = l2.retrieve_account_via_address(bob()).expect("bob2"); + assert_eq!( + l2.get_balance(a2, alice2).expect("bal alice2"), + U256::from(250) + ); + assert_eq!(l2.get_balance(a2, bob2).expect("bal bob2"), U256::from(70)); + assert_eq!(l2.get_total_supply(a2).expect("supply2"), supply1); + + // And the rebuilt records prefix is byte-identical to the original (the property the + // watchdog byte-compare and the emergency-withdrawal proof both rely on). + assert_eq!( + buf[..RECORDS_PREFIX], + buf2[..RECORDS_PREFIX], + "rebuilt records prefix must be byte-identical to the original" + ); +} diff --git a/tests/ledger_tests.rs b/tests/ledger_tests.rs index c68c4c4..4d9f0e1 100644 --- a/tests/ledger_tests.rs +++ b/tests/ledger_tests.rs @@ -1,3 +1,33 @@ +//! Behavioural tests for the libcma `Ledger`. +//! +//! ## Asset types — fungible vs. non-fungible +//! +//! libcma has two token-backed asset types, and the distinction is load-bearing: +//! +//! * [`AssetType::TokenAddress`] — a **fungible** token (ERC-20), keyed by token address +//! only. Its total supply is a full 256-bit integer, so any balance is valid. +//! * [`AssetType::TokenAddressId`] — a **non-fungible** token (ERC-721 / a single ERC-1155 +//! id), keyed by *(token address, token id)*. Such an asset is unique: real libcma +//! enforces that its supply can only ever go `0 -> 1` and that the only legal deposit is +//! exactly `1` (`src/ledger_impl.cpp`). Depositing e.g. `1000` returns `SupplyOverflow` — +//! that error is the "you violated NFT uniqueness" signal, **not** an arithmetic overflow +//! (the supply field is 256-bit and nowhere near full). +//! +//! These fungible tests therefore use `TokenAddress`. The mock backend does not enforce the +//! NFT rule, so an earlier version of these tests used `TokenAddressId` with fungible amounts +//! and only passed against the mock; they failed against real libcma. See +//! `test_nft_asset_deposit_is_capped_at_one` for the enforced NFT behaviour. +//! +//! ## Backend-agnostic assertions +//! +//! These run under BOTH the default `mock` backend and the real libcma backends +//! (`host-real` / `riscv64`), which differ in incidental ways — most notably the mock uses +//! 1-based asset/account ids (reserving id 1 for the Base asset) while real libcma is 0-based. +//! So the tests assert *relationships* (a freshly created id is found again unchanged) rather +//! than absolute id values, and probe "not found" via the `Find` retrieve operation (which +//! errors on both) rather than `get_balance` (which returns 0 for an unknown pair on real +//! libcma but errors on the mock). + use libcma_binding_rust::{Ledger, LedgerError, *}; use std::fs::OpenOptions; use std::time::{SystemTime, UNIX_EPOCH}; @@ -122,25 +152,72 @@ fn test_init_from_buffer_reinitializes_ledger() { ); } +#[test] +fn test_init_single_from_file_ether() { + let mut ledger = Ledger::new().expect("Failed to initialize ledger"); + + let path = unique_temp_file_path(); + let config = LedgerSingleFileConfig::default(); + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(&path) + .expect("Should create temp file"); + file.set_len(config.memory_length as u64) + .expect("Should size temp file"); + + ledger + .init_single_from_file(&path, config, LedgerAsset::Ether) + .expect("Single-asset (ether) file-backed initialization should succeed"); + + drop(ledger); + std::fs::remove_file(path).expect("Should remove temp file"); +} + +#[test] +fn test_init_single_from_buffer_erc20() { + let mut ledger = Ledger::new().expect("Failed to initialize ledger"); + + let mut buffer = vec![0u8; 1024 * 1024]; + ledger + .init_single_from_buffer(&mut buffer, 256, LedgerAsset::Erc20(test_token_address())) + .expect("Single-asset (ERC-20) buffer-backed initialization should succeed"); +} + #[test] fn test_create_asset_by_token_address() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); let token_addr = test_token_address(); - let token_id = U256::from_u64(1); - // Create an asset using token address - let asset_id = ledger.retrieve_asset( - None, // No existing asset_id - Some(token_addr), - Some(token_id), - AssetType::TokenAddressId, - RetrieveOperation::Create, - ); + // Create a fungible (ERC-20) asset keyed by token address. + let created = ledger + .retrieve_asset( + None, // No existing asset_id + Some(token_addr), + None, + AssetType::TokenAddress, + RetrieveOperation::Create, + ) + .expect("Asset creation should succeed"); - assert!(asset_id.is_ok(), "Asset creation should succeed"); - let asset_id = asset_id.unwrap(); - assert!(asset_id.0 > 0, "Asset ID should be non-zero"); + // Backend-agnostic: the created asset must be findable again under the SAME id (the raw id + // value differs between the mock (1-based) and real libcma (0-based), so don't assert `> 0`). + let found = ledger + .retrieve_asset( + None, + Some(token_addr), + None, + AssetType::TokenAddress, + RetrieveOperation::Find, + ) + .expect("Created asset should be findable"); + assert_eq!( + created, found, + "Find must return the id that Create assigned" + ); } #[test] @@ -182,16 +259,29 @@ fn test_create_account_by_wallet_address() { let wallet_addr = test_account_address(); // Create an account using wallet address - let account_id = ledger.retrieve_account( - None, // No existing account_id - AccountType::WalletAddress, - RetrieveOperation::Create, - Some(wallet_addr.as_bytes()), - ); + let created = ledger + .retrieve_account( + None, // No existing account_id + AccountType::WalletAddress, + RetrieveOperation::Create, + Some(wallet_addr.as_slice()), + ) + .expect("Account creation should succeed"); - assert!(account_id.is_ok(), "Account creation should succeed"); - let account_id = account_id.unwrap(); - assert!(account_id.0 > 0, "Account ID should be non-zero"); + // Backend-agnostic: the account must be findable again under the SAME id (real libcma is + // 0-based, the mock 1-based — so assert the relationship, not a specific value). + let found = ledger + .retrieve_account( + None, + AccountType::WalletAddress, + RetrieveOperation::Find, + Some(wallet_addr.as_slice()), + ) + .expect("Created account should be findable"); + assert_eq!( + created, found, + "Find must return the id that Create assigned" + ); } #[test] @@ -230,15 +320,14 @@ fn test_find_or_create_account() { fn test_deposit_and_balance() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - // Create an asset + // Create a fungible asset let token_addr = test_token_address(); - let token_id = U256::from_u64(100); let asset_id = ledger .retrieve_asset( None, Some(token_addr), - Some(token_id), - AssetType::TokenAddressId, + None, + AssetType::TokenAddress, RetrieveOperation::Create, ) .expect("Should create asset"); @@ -250,7 +339,7 @@ fn test_deposit_and_balance() { None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet_addr.as_bytes()), + Some(wallet_addr.as_slice()), ) .expect("Should create account"); @@ -292,15 +381,14 @@ fn test_deposit_and_balance() { fn test_withdraw() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - // Create asset and account + // Create fungible asset and account let token_addr = test_token_address(); - let token_id = U256::from_u64(200); let asset_id = ledger .retrieve_asset( None, Some(token_addr), - Some(token_id), - AssetType::TokenAddressId, + None, + AssetType::TokenAddress, RetrieveOperation::Create, ) .expect("Should create asset"); @@ -311,7 +399,7 @@ fn test_withdraw() { None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet_addr.as_bytes()), + Some(wallet_addr.as_slice()), ) .expect("Should create account"); @@ -341,36 +429,51 @@ fn test_withdraw() { fn test_insufficient_funds_error() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - // Create asset and account + // Fungible asset. let token_addr = test_token_address(); - let token_id = U256::from_u64(300); let asset_id = ledger .retrieve_asset( None, Some(token_addr), - Some(token_id), - AssetType::TokenAddressId, + None, + AssetType::TokenAddress, RetrieveOperation::Create, ) .expect("Should create asset"); + // Fund a FIRST account so the asset's total supply is non-zero. libcma checks the asset + // supply for underflow BEFORE the per-account balance, so an empty account only reports + // InsufficientFunds (rather than a supply underflow) once the supply itself can cover it. + let mut funder_bytes = [0u8; 20]; + funder_bytes[0] = 0xF0; + let funder = Address::new(funder_bytes); + let funder_id = ledger + .retrieve_account( + None, + AccountType::WalletAddress, + RetrieveOperation::Create, + Some(funder.as_slice()), + ) + .expect("Should create funder account"); + ledger + .deposit(asset_id, funder_id, U256::from_u64(1000)) + .expect("funder deposit should succeed"); + + // A SECOND, empty account tries to withdraw more than its (zero) balance. let wallet_addr = test_account_address(); let account_id = ledger .retrieve_account( None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet_addr.as_bytes()), + Some(wallet_addr.as_slice()), ) .expect("Should create account"); - // Try to withdraw without depositing first - let withdraw_amount = U256::from_u64(100); - let result = ledger.withdraw(asset_id, account_id, withdraw_amount); - + let result = ledger.withdraw(asset_id, account_id, U256::from_u64(100)); assert!( result.is_err(), - "Withdraw should fail with insufficient funds" + "Withdraw from an empty account should fail" ); match result.unwrap_err() { LedgerError::InsufficientFunds => { @@ -384,15 +487,14 @@ fn test_insufficient_funds_error() { fn test_transfer() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - // Create asset + // Create a fungible asset let token_addr = test_token_address(); - let token_id = U256::from_u64(400); let asset_id = ledger .retrieve_asset( None, Some(token_addr), - Some(token_id), - AssetType::TokenAddressId, + None, + AssetType::TokenAddress, RetrieveOperation::Create, ) .expect("Should create asset"); @@ -406,7 +508,7 @@ fn test_transfer() { None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet1.as_bytes()), + Some(wallet1.as_slice()), ) .expect("Should create account 1"); @@ -418,7 +520,7 @@ fn test_transfer() { None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet2.as_bytes()), + Some(wallet2.as_slice()), ) .expect("Should create account 2"); @@ -465,17 +567,16 @@ fn test_transfer() { fn test_multiple_assets_and_accounts() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - // Create two different assets + // Create two different fungible assets, keyed by two distinct token addresses. let mut token1_bytes = [0u8; 20]; token1_bytes[0] = 0xA1; let token1_addr = TokenAddress::new(token1_bytes); - let token1_id = U256::from_u64(1); let asset1 = ledger .retrieve_asset( None, Some(token1_addr), - Some(token1_id), - AssetType::TokenAddressId, + None, + AssetType::TokenAddress, RetrieveOperation::Create, ) .expect("Should create asset 1"); @@ -483,13 +584,12 @@ fn test_multiple_assets_and_accounts() { let mut token2_bytes = [0u8; 20]; token2_bytes[0] = 0xB2; let token2_addr = TokenAddress::new(token2_bytes); - let token2_id = U256::from_u64(2); let asset2 = ledger .retrieve_asset( None, Some(token2_addr), - Some(token2_id), - AssetType::TokenAddressId, + None, + AssetType::TokenAddress, RetrieveOperation::Create, ) .expect("Should create asset 2"); @@ -501,7 +601,7 @@ fn test_multiple_assets_and_accounts() { None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet_addr.as_bytes()), + Some(wallet_addr.as_slice()), ) .expect("Should create account"); @@ -543,22 +643,23 @@ fn test_multiple_assets_and_accounts() { fn test_account_not_found_error() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - // Create asset + // Create a fungible asset. let token_addr = test_token_address(); - let token_id = U256::from_u64(500); let asset_id = ledger .retrieve_asset( None, Some(token_addr), - Some(token_id), - AssetType::TokenAddressId, + None, + AssetType::TokenAddress, RetrieveOperation::Create, ) .expect("Should create asset"); - // Try to get balance for non-existent account + // An OPERATION against a non-existent account must fail with AccountNotFound. (`get_balance` + // is NOT used here: real libcma returns 0 for an unknown (asset, account) pair rather than + // erroring — it is the mutating paths that validate account existence.) let fake_account_id = LedgerAccountId(99999); - let result = ledger.get_balance(asset_id, fake_account_id); + let result = ledger.deposit(asset_id, fake_account_id, U256::from_u64(1)); assert!(result.is_err(), "Should fail for non-existent account"); match result.unwrap_err() { @@ -580,13 +681,14 @@ fn test_asset_not_found_error() { None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet_addr.as_bytes()), + Some(wallet_addr.as_slice()), ) .expect("Should create account"); - // Try to get balance for non-existent asset + // An OPERATION against a non-existent asset must fail with AssetNotFound (again via a + // mutating path, not `get_balance`, which returns 0 for unknown pairs on real libcma). let fake_asset_id = LedgerAssetId(99999); - let result = ledger.get_balance(fake_asset_id, account_id); + let result = ledger.deposit(fake_asset_id, account_id, U256::from_u64(1)); assert!(result.is_err(), "Should fail for non-existent asset"); match result.unwrap_err() { @@ -647,20 +749,36 @@ fn test_find_nonexistent_account() { #[test] fn test_retrieve_ether_asset() { + // The Base (ether) asset type is only supported by the buffer/file-backed multi-asset ledger + // (`cma_ledger_memory`). The transient `Ledger::new()` backend (`cma_ledger_basic`) has no + // Base case and returns EINVAL, so back the ledger with a buffer before touching ether. let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - let asset_id = ledger + let mut buffer = vec![0u8; 1024 * 1024]; + ledger + .init_from_buffer(&mut buffer, LedgerBufferConfig::default()) + .expect("Buffer-backed initialization should succeed"); + + let created = ledger .retrieve_ether_assets() .expect("Should create base ether asset"); - assert!(asset_id.0 > 0, "Ether asset ID should be non-zero"); + // Backend-agnostic: retrieving it again (find-or-create) must return the same id. + let again = ledger + .retrieve_ether_assets() + .expect("Should find the existing base ether asset"); + assert_eq!(created, again, "Base ether asset id must be stable"); } +/// The NON-FUNGIBLE (`TokenAddressId`) asset type is capped at supply 1: a single deposit of +/// exactly 1 succeeds, and anything else (a second unit, or an initial amount > 1) is rejected +/// with `SupplyOverflow`. This is real libcma behaviour the mock does not model, so it only runs +/// against a real backend. +#[cfg(any(feature = "host-real", feature = "riscv64"))] #[test] -fn test_large_amounts() { +fn test_nft_asset_deposit_is_capped_at_one() { let mut ledger = Ledger::new().expect("Failed to initialize ledger"); - // Create asset and account let token_addr = test_token_address(); - let token_id = U256::from_u64(700); + let token_id = U256::from_u64(42); let asset_id = ledger .retrieve_asset( None, @@ -669,6 +787,54 @@ fn test_large_amounts() { AssetType::TokenAddressId, RetrieveOperation::Create, ) + .expect("Should create NFT asset"); + + let holder = ledger + .retrieve_account( + None, + AccountType::WalletAddress, + RetrieveOperation::Create, + Some(test_account_address().as_slice()), + ) + .expect("Should create holder account"); + + // Depositing more than one unit of a unique token is rejected. + match ledger.deposit(asset_id, holder, U256::from_u64(5)) { + Err(LedgerError::SupplyOverflow) => {} + other => panic!("NFT deposit > 1 should be SupplyOverflow, got {:?}", other), + } + + // Minting the single unit succeeds... + ledger + .deposit(asset_id, holder, U256::from_u64(1)) + .expect("Minting the single NFT unit should succeed"); + assert_eq!( + ledger.get_total_supply(asset_id).expect("supply"), + U256::from_u64(1), + "NFT supply must be exactly 1" + ); + + // ...but a second unit pushes supply past 1 and is rejected. + match ledger.deposit(asset_id, holder, U256::from_u64(1)) { + Err(LedgerError::SupplyOverflow) => {} + other => panic!("second NFT unit should be SupplyOverflow, got {:?}", other), + } +} + +#[test] +fn test_large_amounts() { + let mut ledger = Ledger::new().expect("Failed to initialize ledger"); + + // Create a fungible asset and account + let token_addr = test_token_address(); + let asset_id = ledger + .retrieve_asset( + None, + Some(token_addr), + None, + AssetType::TokenAddress, + RetrieveOperation::Create, + ) .expect("Should create asset"); let wallet_addr = test_account_address(); @@ -677,7 +843,7 @@ fn test_large_amounts() { None, AccountType::WalletAddress, RetrieveOperation::Create, - Some(wallet_addr.as_bytes()), + Some(wallet_addr.as_slice()), ) .expect("Should create account"); diff --git a/tests/parser_tests.rs b/tests/parser_tests.rs index cc9279f..46b7963 100644 --- a/tests/parser_tests.rs +++ b/tests/parser_tests.rs @@ -1,7 +1,43 @@ -use ethers_core::abi::{encode, AbiParser, FixedBytes, Token}; -use ethers_core::types::{Address, U256}; -use ethers_core::utils::id; +use alloy_dyn_abi::DynSolValue; +use alloy_primitives::{keccak256, Address, U256}; use json::JsonValue; + +// --- Test-local ABI oracle over alloy. The parser was migrated off the EOL ethers-rs; +// these helpers rebuild calldata the same standard-ABI way to drive the decode round-trips +// and voucher checks (byte-for-byte standard ABI encoding via alloy). --- +type FixedBytes = Vec; + +enum Token { + Uint(U256), + Address(Address), + Bytes(Vec), + FixedBytes(FixedBytes), +} + +fn token_to_dyn(t: &Token) -> DynSolValue { + match t { + Token::Uint(v) => DynSolValue::Uint(*v, 256), + Token::Address(a) => DynSolValue::Address(*a), + Token::Bytes(b) => DynSolValue::Bytes(b.clone()), + Token::FixedBytes(b) => DynSolValue::FixedBytes( + alloy_primitives::FixedBytes::<32>::right_padding_from(b), + 32, + ), + } +} + +fn encode(tokens: &[Token]) -> Vec { + DynSolValue::Tuple(tokens.iter().map(token_to_dyn).collect()).abi_encode_params() +} + +fn id(signature: &str) -> [u8; 4] { + // ethers' `AbiParser` normalized the signature (e.g. stripped whitespace) before + // hashing; mirror that so a test signature like "f(uint256, bytes)" yields the same + // 4-byte selector as the canonical "f(uint256,bytes)". + let normalized: String = signature.chars().filter(|c| !c.is_whitespace()).collect(); + let h = keccak256(normalized.as_bytes()); + [h[0], h[1], h[2], h[3]] +} use libcma_binding_rust::parser::{ cma_decode_advance, cma_decode_inspect, cma_encode_voucher, CmaParserErc20VoucherFields, CmaParserErc721VoucherFields, CmaParserEtherVoucherFields, CmaParserInputData, @@ -28,8 +64,8 @@ pub fn abi_encode_call( signature: &str, args: Vec, ) -> Result, Box> { - let function = AbiParser::default().parse_function(signature)?; - let calldata = function.encode_input(&args)?; + let mut calldata = id(signature)[..4].to_vec(); + calldata.extend_from_slice(&encode(&args)); Ok(calldata) } @@ -38,7 +74,7 @@ fn test_ether_deposit_success() { let sender: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" .parse() .unwrap(); - let amount = U256::from_dec_str("2000000000000000000").unwrap(); // 2 Ether in wei + let amount = U256::from_str_radix("2000000000000000000", 10).unwrap(); // 2 Ether in wei let payload = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000001bc16d674ec80000".to_string(); // Sample payload from the ether portal let input = create_test_input(ETHER_PORTAL, &payload); @@ -74,7 +110,7 @@ fn test_ether_deposit_success() { // let sender: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" // .parse() // .unwrap(); -// let amount = U256::from_dec_str("300000000000000000000").unwrap(); +// let amount = U256::from_str_radix("300000000000000000000", 10).unwrap(); // let token_address: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C" // .parse() // .unwrap(); @@ -112,7 +148,7 @@ fn test_erc20_deposit_success() { let sender: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" .parse() .unwrap(); - let amount = U256::from_dec_str("300000000000000000000").unwrap(); + let amount = U256::from_str_radix("300000000000000000000", 10).unwrap(); let token_address: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C" .parse() .unwrap(); @@ -143,7 +179,7 @@ fn test_erc721_deposit_success() { let sender: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" .parse() .unwrap(); - let token_id = U256::from_dec_str("1").unwrap(); // Sample token ID + let token_id = U256::from_str_radix("1", 10).unwrap(); // Sample token ID let token_address: Address = "0xBa46623aD94AB45850c4ecbA9555D26328917c3B" .parse() .unwrap(); // Sample ERC721 token address @@ -181,8 +217,8 @@ fn test_erc721_deposit_success() { #[test] fn test_ethers_withdrawal_success() { let recipient = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"; - let amount = U256::from_dec_str("1500000000000000000").unwrap(); // 1.5 Ether in wei - // let payload = r#"{"function_type": "EtherWithdrawal", "amount": "1500000000000000000", "exec_layer_data": "0x"}"#.to_string(); + let amount = U256::from_str_radix("1500000000000000000", 10).unwrap(); // 1.5 Ether in wei + // let payload = r#"{"function_type": "EtherWithdrawal", "amount": "1500000000000000000", "exec_layer_data": "0x"}"#.to_string(); let abi_encoded_input = abi_encode_call( "WithdrawEther(uint256, bytes)", @@ -197,22 +233,22 @@ fn test_ethers_withdrawal_success() { Ok(result) => { let is_correct_method = result.req_type == CmaParserInputType::CmaParserInputTypeEtherWithdrawal; - let is_correct_recipient = if let CmaParserInputData::EtherWithdrawal(withdrawal) = - result.input - { - if withdrawal.amount == amount && withdrawal.receiver == recipient.parse().unwrap() - { - println!( - "withdrawal response is {}, {}", - withdrawal.amount, withdrawal.receiver - ); - true + let is_correct_recipient = + if let CmaParserInputData::EtherWithdrawal(withdrawal) = result.input { + if withdrawal.amount == amount + && withdrawal.receiver == recipient.parse::
().unwrap() + { + println!( + "withdrawal response is {}, {}", + withdrawal.amount, withdrawal.receiver + ); + true + } else { + false + } } else { false - } - } else { - false - }; + }; assert_eq!(true, is_correct_method, "Expected Ether Withdrawal method"); assert_eq!( @@ -227,7 +263,7 @@ fn test_ethers_withdrawal_success() { #[test] fn test_erc20_withdrawal_success() { let recipient = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"; - let amount = U256::from_dec_str("50000000000000000000").unwrap(); // 50 ERC20 tokens in wei + let amount = U256::from_str_radix("50000000000000000000", 10).unwrap(); // 50 ERC20 tokens in wei let token_address: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C" .parse() .unwrap(); // TEST token address @@ -251,7 +287,7 @@ fn test_erc20_withdrawal_success() { let is_correct_recipient = if let CmaParserInputData::Erc20Withdrawal(withdrawal) = result.input { if withdrawal.amount == amount - && withdrawal.receiver == recipient.parse().unwrap() + && withdrawal.receiver == recipient.parse::
().unwrap() && withdrawal.token == token_address { true @@ -275,7 +311,7 @@ fn test_erc20_withdrawal_success() { #[test] fn test_erc721_withdrawal_success() { let recipient = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"; - let token_id = U256::from_dec_str("1").unwrap(); // Sample token ID + let token_id = U256::from_str_radix("1", 10).unwrap(); // Sample token ID let token_address: Address = "0xBa46623aD94AB45850c4ecbA9555D26328917c3B" .parse() .unwrap(); // Sample ERC721 token address @@ -299,7 +335,7 @@ fn test_erc721_withdrawal_success() { let is_correct_recipient = if let CmaParserInputData::Erc721Withdrawal(withdrawal) = result.input { if withdrawal.token_id == token_id - && withdrawal.receiver == recipient.parse().unwrap() + && withdrawal.receiver == recipient.parse::
().unwrap() && withdrawal.token == token_address { true @@ -327,9 +363,9 @@ fn test_ether_transfer_success() { recipient_bytes[31] = 120; let recipient: FixedBytes = FixedBytes::from(recipient_bytes); - let expected_receipient: U256 = U256::from_big_endian(&recipient); + let expected_receipient: U256 = U256::from_be_slice(&recipient); - let amount = U256::from_dec_str("1500000000000000000").unwrap(); // 1.5 Ether in wei + let amount = U256::from_str_radix("1500000000000000000", 10).unwrap(); // 1.5 Ether in wei let abi_encoded_input = abi_encode_call( "TransferEther(bytes32,uint256,bytes)", @@ -379,9 +415,9 @@ fn test_erc20_transfer_success() { recipient_bytes[31] = 120; let recipient: FixedBytes = FixedBytes::from(recipient_bytes); - let expected_receipient: U256 = U256::from_big_endian(&recipient); + let expected_receipient: U256 = U256::from_be_slice(&recipient); - let amount = U256::from_dec_str("1500000000000000000").unwrap(); // 1.5 Ether in wei + let amount = U256::from_str_radix("1500000000000000000", 10).unwrap(); // 1.5 Ether in wei let abi_encoded_input = abi_encode_call( "TransferErc20(address,bytes32,uint256,bytes)", @@ -425,7 +461,7 @@ fn test_erc20_transfer_success() { #[test] fn test_erc721_transfer_success() { - let token_id = U256::from_dec_str("1").unwrap(); // Sample token ID + let token_id = U256::from_str_radix("1", 10).unwrap(); // Sample token ID let sender = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"; let token_address: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C" .parse() @@ -435,7 +471,7 @@ fn test_erc721_transfer_success() { recipient_bytes[31] = 120; let recipient: FixedBytes = FixedBytes::from(recipient_bytes); - let expected_receipient: U256 = U256::from_big_endian(&recipient); + let expected_receipient: U256 = U256::from_be_slice(&recipient); let abi_encoded_input = abi_encode_call( "TransferErc721(address,bytes32,uint256,bytes)", @@ -483,16 +519,19 @@ fn test_ether_voucher_encoding_success() { let recipient: Address = "0x3e157927fb178490941bb18adcdc4144e442e32a" .parse() .unwrap(); - let amount = U256::from_dec_str("1500000000000000000").unwrap(); // 1.5 Ether in wei - let mut expected_value_bytes = [0u8; 32]; - amount.to_big_endian(&mut expected_value_bytes); + let amount = U256::from_str_radix("1500000000000000000", 10).unwrap(); // 1.5 Ether in wei + let expected_value_bytes = amount.to_be_bytes::<32>(); let request = CmaVoucherFieldType::EtherVoucherFields(CmaParserEtherVoucherFields { receiver: recipient, amount, }); - match cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeEther, None, request) { + match cma_encode_voucher( + CmaParserVoucherType::CmaParserVoucherTypeEther, + None, + request, + ) { Ok(voucher) => { assert!( voucher.destination.to_lowercase() == receipient_string, @@ -521,7 +560,7 @@ fn test_erc20_voucher_encoding_success() { let token_address: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C" .parse() .unwrap(); // TEST token address - let amount = U256::from_dec_str("50000000000000000000").unwrap(); // 50 ERC20 tokens in wei + let amount = U256::from_str_radix("50000000000000000000", 10).unwrap(); // 50 ERC20 tokens in wei // Create expected voucher payload let args: Vec = vec![Token::Address(recipient), Token::Uint(amount)]; @@ -540,7 +579,11 @@ fn test_erc20_voucher_encoding_success() { amount, receiver: recipient, }); - match cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeErc20, None, request) { + match cma_encode_voucher( + CmaParserVoucherType::CmaParserVoucherTypeErc20, + None, + request, + ) { Ok(voucher) => { // Basic checks on the voucher structure assert!( @@ -568,7 +611,7 @@ fn test_erc721_voucher_encoding_success() { let token_address: Address = "0xBa46623aD94AB45850c4ecbA9555D26328917c3B" .parse() .unwrap(); // Sample ERC721 token address - let token_id = U256::from_dec_str("1").unwrap(); // Sample token ID + let token_id = U256::from_str_radix("1", 10).unwrap(); // Sample token ID // Create expected voucher payload let args: Vec = vec![ @@ -632,8 +675,8 @@ fn test_ledger_get_balance_success() { result.req_type == CmaParserInputType::CmaParserInputTypeBalance; let is_correct_address = if let CmaParserInputData::Balance(data) = result.input { let mut expected_account = [0u8; 32]; - expected_account[12..].copy_from_slice(address.as_bytes()); - data.account == U256::from_big_endian(&expected_account) + expected_account[12..].copy_from_slice(address.as_slice()); + data.account == U256::from_be_slice(&expected_account) && data.token == erc20_token && data.token_id == U256::from(1) } else { diff --git a/tests/parser_vectors.rs b/tests/parser_vectors.rs index 4e33948..5e19763 100644 --- a/tests/parser_vectors.rs +++ b/tests/parser_vectors.rs @@ -1,6 +1,6 @@ //! Canonical parser vectors ported from `third_party/machine-asset-tools/tests/parser.c`. -use ethers_core::types::{Address, U256}; +use alloy_primitives::{Address, U256}; use json::JsonValue; use libcma_binding_rust::parser::{ cma_decode_advance, cma_decode_inspect, cma_encode_voucher, CmaParserErc721VoucherFields, @@ -99,7 +99,8 @@ fn vector_ether_transfer_from_parser_c() { #[test] fn vector_ledger_get_balance_from_parser_c() { - let payload = r#"{"method":"ledger_getBalance","params":["0x0000000000000000000000000000000000000001"]}"#; + let payload = + r#"{"method":"ledger_getBalance","params":["0x0000000000000000000000000000000000000001"]}"#; let result = cma_decode_inspect(inspect_input(payload)).expect("ledger_getBalance should decode"); @@ -115,9 +116,8 @@ fn vector_erc721_voucher_from_parser_c() { let app_address = Address::from_slice(&[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xbe, 0xef, ]); - let receiver = Address::from_slice(&[ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - ]); + let receiver = + Address::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]); let token = Address::from_slice(&[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, ]); @@ -145,20 +145,18 @@ fn vector_ether_voucher_value_from_parser_c() { let receiver: Address = "0x3e157927fb178490941bb18adcdc4144e442e32a" .parse() .unwrap(); - let amount = U256::from_dec_str("1500000000000000000").unwrap(); - let request = CmaVoucherFieldType::EtherVoucherFields(CmaParserEtherVoucherFields { - receiver, - amount, - }); + let amount = U256::from_str_radix("1500000000000000000", 10).unwrap(); + let request = + CmaVoucherFieldType::EtherVoucherFields(CmaParserEtherVoucherFields { receiver, amount }); - let voucher = cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeEther, None, request) - .expect("ether voucher should encode"); + let voucher = cma_encode_voucher( + CmaParserVoucherType::CmaParserVoucherTypeEther, + None, + request, + ) + .expect("ether voucher should encode"); - let mut expected_value = [0u8; 32]; - amount.to_big_endian(&mut expected_value); - assert_eq!( - voucher.value, - format!("0x{}", hex::encode(expected_value)) - ); + let expected_value = amount.to_be_bytes::<32>(); + assert_eq!(voucher.value, format!("0x{}", hex::encode(expected_value))); assert_eq!(voucher.payload, "0x"); } diff --git a/third_party/machine-asset-tools b/third_party/machine-asset-tools index 8787435..19a1e5e 160000 --- a/third_party/machine-asset-tools +++ b/third_party/machine-asset-tools @@ -1 +1 @@ -Subproject commit 8787435299d26be36be65aba8f18b70d9e33c6ff +Subproject commit 19a1e5efa599622f4bebbab6bb15faf29aa82b2d diff --git a/third_party/machine-guest-tools b/third_party/machine-guest-tools index 3d838a2..20eba47 160000 --- a/third_party/machine-guest-tools +++ b/third_party/machine-guest-tools @@ -1 +1 @@ -Subproject commit 3d838a2f80cd8fa614e38ea93efcfabe8ec65bea +Subproject commit 20eba47631a1b0938c6e0f2c4af1ac88a172c90d