diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 129fa1a90..1ecfeb9ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,11 @@ name: build +# Limits workflow concurrency to only the latest commit in the PR. +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + on: push: branches: [main, next] @@ -10,30 +15,32 @@ on: jobs: async: - name: Build using `async` feature + name: build using async feature runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - toolchain: [stable, nightly] steps: - uses: actions/checkout@main - - name: Build using `async` feature + - uses: Swatinem/rust-cache@v2 + with: + # Only update the cache on push onto the next branch. This strikes a nice balance between + # cache hits and cache evictions (github has a 10GB cache limit). + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} + - name: build run: | - rustup update --no-self-update ${{ matrix.toolchain }} + rustup update --no-self-update make build-async no-std: - name: Build for no-std + name: build for no-std runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - toolchain: [stable, nightly] steps: - uses: actions/checkout@main - - name: Build for no-std + - uses: Swatinem/rust-cache@v2 + with: + # Only update the cache on push onto the next branch. This strikes a nice balance between + # cache hits and cache evictions (github has a 10GB cache limit). + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} + - name: build run: | - rustup update --no-self-update ${{ matrix.toolchain }} + rustup update --no-self-update rustup target add wasm32-unknown-unknown make build-no-std diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 000000000..c890c4c26 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,23 @@ +# Runs changelog related jobs. +# CI job heavily inspired by: https://github.com/tarides/changelog-check-action + +name: changelog + +on: + pull_request: + types: [opened, reopened, synchronize, labeled, unlabeled] + +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@main + with: + fetch-depth: 0 + - name: Check for changes in changelog + env: + BASE_REF: ${{ github.event.pull_request.base.ref }} + NO_CHANGELOG_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'no changelog') }} + run: ./scripts/check-changelog.sh "${{ inputs.changelog }}" + shell: bash diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7b7814184..cd9130cee 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,6 +2,11 @@ name: lint +# Limits workflow concurrency to only the latest commit in the PR. +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + on: push: branches: [main, next] @@ -9,45 +14,60 @@ on: types: [opened, reopened, synchronize] jobs: - version: - name: check rust version consistency + clippy: + name: clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@main + - uses: Swatinem/rust-cache@v2 with: - profile: minimal - override: true - - name: check rust versions - run: ./scripts/check-rust-version.sh + # Only update the cache on push onto the next branch. This strikes a nice balance between + # cache hits and cache evictions (github has a 10GB cache limit). + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} + - name: Clippy + run: | + rustup update --no-self-update + rustup component add clippy + make clippy rustfmt: - name: rustfmt check nightly on ubuntu-latest + name: rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@main + - uses: Swatinem/rust-cache@v2 + with: + # Only update the cache on push onto the next branch. This strikes a nice balance between + # cache hits and cache evictions (github has a 10GB cache limit). + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} - name: Rustfmt run: | rustup update --no-self-update nightly rustup +nightly component add rustfmt make format-check - clippy: - name: clippy nightly on ubuntu-latest - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@main - - name: Clippy - run: | - rustup update --no-self-update nightly - rustup +nightly component add clippy - make clippy - doc: - name: doc stable on ubuntu-latest + name: doc runs-on: ubuntu-latest steps: - uses: actions/checkout@main + - uses: Swatinem/rust-cache@v2 + with: + # Only update the cache on push onto the next branch. This strikes a nice balance between + # cache hits and cache evictions (github has a 10GB cache limit). + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} - name: Build docs run: | rustup update --no-self-update make doc + + version: + name: check rust version consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + with: + profile: minimal + override: true + - name: check rust versions + run: ./scripts/check-rust-version.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2bbc3528e..1090408bf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,11 @@ name: test +# Limits workflow concurrency to only the latest commit in the PR. +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + on: push: branches: [main, next] @@ -10,19 +15,19 @@ on: jobs: test: - name: test ${{matrix.toolchain}} on ${{matrix.os}} with ${{matrix.args}} - runs-on: ${{matrix.os}}-latest - strategy: - fail-fast: false - matrix: - toolchain: [stable, nightly] - os: [ubuntu] - args: [default, prove] - timeout-minutes: 30 + name: test + runs-on: ubuntu-latest steps: - uses: actions/checkout@main - uses: taiki-e/install-action@nextest - - name: Perform tests - run: | - rustup update --no-self-update ${{matrix.toolchain}} - make test-${{matrix.args}} + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} + - name: Install rust + run: rustup update --no-self-update + - name: Build tests + run: make test-build + - name: test-default + run: make test-default + - name: test-prove + run: make test-prove diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9f8973d..62f9b192f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## 0.5.0 (2024-08-27) + +### Features + +- [BREAKING] Increase of nonce does not require changes in account state any more (#796). +- Changed `AccountCode` procedures from merkle tree to sequential hash + added storage_offset support (#763). +- Implemented merging of account deltas (#797). +- Implemented `create_note` and `move_asset_into_note` basic wallet procedures (#808). +- Made `miden_lib::notes::build_swap_tag()` function public (#817). +- [BREAKING] Changed the `NoteFile::NoteDetails` type to struct and added a `after_block_num` field (#823). + +### Changes + +- Renamed "consumed" and "created" notes into "input" and "output" respectively (#791). +- [BREAKING] Renamed `NoteType::OffChain` into `NoteType::Private`. +- [BREAKING] Renamed public accessors of the `Block` struct to match the updated fields (#791). +- [BREAKING] Changed the `TransactionArgs` to use `AdviceInputs` (#793). +- Setters in `memory` module don't drop the setting `Word` anymore (#795). +- Added `CHANGELOG.md` warning message on CI (#799). +- Added high-level methods for `MockChain` and related structures (#807). +- [BREAKING] Renamed `NoteExecutionHint` to `NoteExecutionMode` and added new `NoteExecutionHint` to `NoteMetadata` (#812, #816). +- [BREAKING] Changed the interface of the `miden::tx::add_asset_to_note` (#808). +- [BREAKING] Refactored and simplified `NoteOrigin` and `NoteInclusionProof` structs (#810, #814). +- [BREAKING] Refactored account storage and vault deltas (#822). +- Added serialization and equality comparison for `TransactionScript` (#824). +- [BREAKING] Migrated to Miden VM v0.10 (#826). +- Added conversions for `NoteExecutionHint` (#827). + ## 0.4.0 (2024-07-03) ### Features @@ -34,9 +62,9 @@ ## 0.3.1 (2024-06-12) -* Replaced `cargo-make` with just `make` for running tasks (#696). -* Made `DataStore` conditionally async using `winter-maybe-async` (#725) -* Fixed `StorageMap`s implementation and included into apply_delta (#745) +- Replaced `cargo-make` with just `make` for running tasks (#696). +- Made `DataStore` conditionally async using `winter-maybe-async` (#725) +- Fixed `StorageMap`s implementation and included into apply_delta (#745) ## 0.3.0 (2024-05-14) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d676a167..dade1662b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,7 +106,7 @@ To make sure all commits adhere to our programming standards we use [pre-commit] 2. Commit messages and code style follow conventions. 3. Tests added for new functionality. 4. Documentation/comments updated for all changes according to our documentation convention. -5. Rustfmt, Clippy and Rustdoc linting passed (Will be ran automatically by pre-commit). +5. Rustfmt, Clippy and Rustdoc linting passed (Will be run automatically by pre-commit). 6. New branch rebased from `next`.   diff --git a/Cargo.lock b/Cargo.lock index 8731013c5..a5c761cf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -19,21 +34,30 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] [[package]] name = "autocfg" @@ -41,6 +65,45 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "2.6.0" @@ -49,9 +112,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake3" -version = "1.5.1" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", "arrayvec", @@ -69,6 +132,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cast" version = "0.3.0" @@ -77,13 +146,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.104" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -121,18 +190,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.8" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstyle", "clap_lex", @@ -140,9 +209,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "constant_time_eq" @@ -152,9 +221,9 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -171,7 +240,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -190,7 +259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -244,12 +313,48 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dissimilar" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -263,7 +368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -272,6 +377,120 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "979f00864edc7516466d6b3157706e06c032f22715700ddd878228a91d02bc56" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -293,6 +512,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "glob" version = "0.3.1" @@ -317,15 +542,21 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "indenter" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -333,15 +564,21 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "itertools" version = "0.10.5" @@ -351,6 +588,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -359,9 +605,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -375,11 +621,44 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.4", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -387,18 +666,60 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + [[package]] name = "memchr" version = "2.7.4" @@ -407,24 +728,32 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miden-air" -version = "0.9.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7693eea191d7a8005486ee98d85f48769309cbd991b830548f1884994243df3" +checksum = "2702f8adb96844e3521f49149f6c3d4773ecdd2a96a3169e3c025a2e3ee32b5e" dependencies = [ "miden-core", + "miden-thiserror", "winter-air", "winter-prover", ] [[package]] name = "miden-assembly" -version = "0.9.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b26c20a3c18d414655ea8bd014c4157f786994f0924aff007fb5b4570d35b25" +checksum = "eae9cef4fbafb4fe26da18574bcdbd78815857cfe1099760782701ababb076c2" dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", "miden-core", - "num_enum", + "miden-miette", + "miden-thiserror", + "rustc_version 0.4.0", + "smallvec", "tracing", + "unicode-width", ] [[package]] @@ -436,26 +765,36 @@ dependencies = [ "miden-processor", "miden-tx", "rand", + "rand_chacha", "serde", "serde_json", ] [[package]] name = "miden-core" -version = "0.9.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc7219af4b6c5fb6a01d80b9dfdad68962157ccf986f7a923df01103e853e74" +checksum = "fc3f6db878d6b56c1566cd5b832908675566d3919b9a3523d630dfb5e2f7422d" dependencies = [ + "lock_api", + "loom", + "memchr", "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-thiserror", + "num-derive", + "num-traits", + "parking_lot", "winter-math", "winter-utils", ] [[package]] name = "miden-crypto" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40dbd2701d7a0dd2ee9bb429388c21145a83e3228144ed086dfc04d676b8bc3a" +checksum = "d6fad06fc3af260ed3c4235821daa2132813d993f96d446856036ae97e9606dd" dependencies = [ "blake3", "cc", @@ -471,9 +810,18 @@ dependencies = [ "winter-utils", ] +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width", +] + [[package]] name = "miden-lib" -version = "0.4.0" +version = "0.5.0" dependencies = [ "miden-assembly", "miden-objects", @@ -481,9 +829,51 @@ dependencies = [ "miden-stdlib", ] +[[package]] +name = "miden-miette" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c532250422d933f15b148fb81e4522a5d649c178ab420d0d596c86228da35570" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "miden-thiserror", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "syn", + "terminal_size", + "textwrap", + "trybuild", + "unicode-width", +] + +[[package]] +name = "miden-miette-derive" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cc759f0a2947acae217a2f32f722105cacc57d17d5f93bc16362142943a4edd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "miden-objects" -version = "0.4.0" +version = "0.5.0" dependencies = [ "criterion", "log", @@ -494,6 +884,7 @@ dependencies = [ "miden-processor", "miden-verifier", "rand", + "rstest", "serde", "tempfile", "winter-rand-utils", @@ -501,9 +892,9 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.9.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c71213c51622c497511c4ac2406c625a3e98a75fc951b477f52fa0b5a83f87" +checksum = "01e7b212b152b69373e89b069a18cb01742ef2c3f9c328e7b24c44e44f022e52" dependencies = [ "miden-air", "miden-core", @@ -513,9 +904,9 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.9.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a685fc0e9ba2b5c4b7203b759ee2dea469600813f213de381461996c6775c8" +checksum = "680d9203cee09b3cee6f79dc952a15f18a1eb47744ffac4f4e559d02bfcdee7a" dependencies = [ "miden-air", "miden-processor", @@ -525,16 +916,36 @@ dependencies = [ [[package]] name = "miden-stdlib" -version = "0.9.2" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71db091ccda04c854d2da3ee4978c14066bf93442c246de8585ffa22f8b0e5ef" +checksum = "41623ad4f4ea6449760f70ab8928c682c3824d735d3e330f07e3d24d1ad20bfa" dependencies = [ "miden-assembly", ] +[[package]] +name = "miden-thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183ff8de338956ecfde3a38573241eb7a6f3d44d73866c210e5629c07fa00253" +dependencies = [ + "miden-thiserror-impl", +] + +[[package]] +name = "miden-thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "miden-tx" -version = "0.4.0" +version = "0.5.0" dependencies = [ "miden-lib", "miden-objects", @@ -544,14 +955,14 @@ dependencies = [ "miden-verifier", "rand", "rand_chacha", - "winter-maybe-async", + "winter-maybe-async 0.10.0", ] [[package]] name = "miden-verifier" -version = "0.9.1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af794328085d13d11bd0ff009464b0af74206cd43f51c8aa3b1c42f17993b62" +checksum = "4e8144a126ecb1941122e8261e85f2e8e94c0b82740bc23879c0a14263f48797" dependencies = [ "miden-air", "miden-core", @@ -559,6 +970,31 @@ dependencies = [ "winter-verifier", ] +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.4.3" @@ -593,13 +1029,24 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.46" +name = "num-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "num-traits", -] + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] [[package]] name = "num-iter" @@ -634,37 +1081,79 @@ dependencies = [ ] [[package]] -name = "num_enum" -version = "0.7.2" +name = "object" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ - "num_enum_derive", + "memchr", ] [[package]] -name = "num_enum_derive" -version = "0.7.2" +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", + "lock_api", + "parking_lot_core", ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "parking_lot_core" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] [[package]] -name = "oorandom" -version = "11.1.3" +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] [[package]] name = "pin-project-lite" @@ -672,11 +1161,26 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro-crate" @@ -684,7 +1188,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit", + "toml_edit 0.21.1", ] [[package]] @@ -755,16 +1259,45 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] @@ -775,15 +1308,81 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.4", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b423f0e62bdd61734b67cd21ff50871dfaeb9cc74f869dcd6af974fbcb19936" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.0", +] + +[[package]] +name = "rstest_macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e1711e7d14f74b12a58411c542185ef7fb7f2e7f8ee6e2940a883628522b42" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.0", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.23", +] + [[package]] name = "rustix" version = "0.38.34" @@ -794,9 +1393,15 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.18" @@ -812,20 +1417,53 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" -version = "1.0.203" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", @@ -834,16 +1472,26 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "indexmap", "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "sha3" version = "0.10.8" @@ -854,11 +1502,102 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + +[[package]] +name = "supports-color" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "syn" -version = "2.0.68" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -867,14 +1606,95 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", ] [[package]] @@ -887,11 +1707,26 @@ dependencies = [ "serde_json", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.20", +] + [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -901,7 +1736,20 @@ checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.18", ] [[package]] @@ -931,6 +1779,54 @@ name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "termcolor", + "toml", +] [[package]] name = "typenum" @@ -944,11 +1840,61 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + +[[package]] +name = "unicode-xid" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] [[package]] name = "walkdir" @@ -966,13 +1912,108 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -981,7 +2022,31 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -990,28 +2055,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1024,24 +2107,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1057,11 +2164,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + [[package]] name = "winter-air" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07390d3217bdd6410c1ef43f3d06510d3a424e7259b371fccbc7cd79a9c00a15" +checksum = "b72f12b88ebb060b52c0e9aece9bb64a9fc38daf7ba689dd5ce63271b456c883" dependencies = [ "libm", "winter-crypto", @@ -1072,9 +2188,9 @@ dependencies = [ [[package]] name = "winter-crypto" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea508aa819e934c837f24bb706e69d890b9be2db82da39cde887e6f0a37246" +checksum = "00fbb724d2d9fbfd3aa16ea27f5e461d4fe1d74b0c9e0ed1bf79e9e2a955f4d5" dependencies = [ "blake3", "sha3", @@ -1084,9 +2200,9 @@ dependencies = [ [[package]] name = "winter-fri" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "660f47c5c9f5872940ac07a724b1df426590dcffad26776e0528466f2e3095f8" +checksum = "3ab6077cf4c23c0411f591f4ba29378e27f26acb8cef3c51cadd93daaf6080b3" dependencies = [ "winter-crypto", "winter-math", @@ -1095,14 +2211,25 @@ dependencies = [ [[package]] name = "winter-math" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c36d2a04b4f79f2c8c6945aab6545b7310a0cd6ae47b9210750400df6775a04" +checksum = "004f85bb051ce986ec0b9a2bd90aaf81b83e3c67464becfdf7db31f14c1019ba" dependencies = [ "serde", "winter-utils", ] +[[package]] +name = "winter-maybe-async" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce0f4161cdde50de809b3869c1cb083a09e92e949428ea28f04c0d64045875c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "winter-maybe-async" version = "0.10.0" @@ -1115,23 +2242,24 @@ dependencies = [ [[package]] name = "winter-prover" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170c1ef487df609625580156ea0350c500aeabb3f429dc88cfe800c4b7893edf" +checksum = "f17e3dbae97050f58e01ed4f12906e247841575a0518632e052941a1c37468df" dependencies = [ "tracing", "winter-air", "winter-crypto", "winter-fri", "winter-math", + "winter-maybe-async 0.9.0", "winter-utils", ] [[package]] name = "winter-rand-utils" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b19ce50e688442052e957a69d72b8057d72ae8f03a7aea7c2538e11c76b2583" +checksum = "f2b827c901ab0c316d89812858ff451d60855c0a5c7ae734b098c62a28624181" dependencies = [ "rand", "winter-utils", @@ -1139,18 +2267,18 @@ dependencies = [ [[package]] name = "winter-utils" -version = "0.8.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef7d7195967f35140fc2542b44813572e0907cde8a818507177ba8db666e7f17" +checksum = "0568612a95bcae3c94fb14da2686f8279ca77723dbdf1e97cf3673798faf6485" dependencies = [ "rayon", ] [[package]] name = "winter-verifier" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f817a425ca4aa7acefb356601798d0b86b9c0e383b397af69e7e5e97f5b4008" +checksum = "324002ade90f21e85599d51a232a80781efc8cb46f511f8bc89f9c5a4eb9cb65" dependencies = [ "winter-air", "winter-crypto", @@ -1158,3 +2286,24 @@ dependencies = [ "winter-math", "winter-utils", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 66694b453..296023df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ [workspace.package] edition = "2021" -rust-version = "1.78" +rust-version = "1.80" license = "MIT" authors = ["Miden contributors"] homepage = "https://polygon.technology/polygon-miden" @@ -31,11 +31,14 @@ codegen-units = 1 lto = true [workspace.dependencies] -assembly = { package = "miden-assembly", version = "0.9", default-features = false } -miden-crypto = { version = "0.9", default-features = false } -miden-prover = { version = "0.9", default-features = false } -miden-stdlib = { version = "0.9", default-features = false } -miden-verifier = { version = "0.9", default-features = false } +assembly = { package = "miden-assembly", version = "0.10", default-features = false } +miden-crypto = { version = "0.10", default-features = false } +miden-lib = { path = "miden-lib", version = "0.5", default-features = false } +miden-objects = { path = "objects", version = "0.5", default-features = false } +miden-prover = { version = "0.10", default-features = false } +miden-stdlib = { version = "0.10", default-features = false } +miden-tx = { path = "miden-tx", version = "0.5" } +miden-verifier = { version = "0.10", default-features = false } rand = { version = "0.8", default-features = false } -vm-core = { package = "miden-core", version = "0.9", default-features = false } -vm-processor = { package = "miden-processor", version = "0.9", default-features = false } +vm-core = { package = "miden-core", version = "0.10", default-features = false } +vm-processor = { package = "miden-processor", version = "0.10", default-features = false } diff --git a/Makefile b/Makefile index 87e26b19a..4e0a63343 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,12 @@ ALL_FEATURES_BUT_ASYNC=--features concurrent,testing,serde .PHONY: clippy clippy: ## Runs Clippy with configs - cargo +nightly clippy --workspace --all-targets $(ALL_FEATURES_BUT_ASYNC) -- -D warnings + cargo clippy --workspace --all-targets $(ALL_FEATURES_BUT_ASYNC) -- -D warnings .PHONY: fix fix: ## Runs Fix with configs - cargo +nightly fix --allow-staged --allow-dirty --all-targets $(ALL_FEATURES_BUT_ASYNC) + cargo fix --workspace --allow-staged --allow-dirty --all-targets $(ALL_FEATURES_BUT_ASYNC) .PHONY: format @@ -48,6 +48,10 @@ doc-serve: ## Serves documentation site # --- testing ------------------------------------------------------------------------------------- +.PHONY: test-build +test-build: ## Build the test binary + $(DEBUG_ASSERTIONS) cargo nextest run --cargo-profile test-release --features concurrent,testing --no-run + .PHONY: test-default test-default: ## Run default tests excluding `prove` $(DEBUG_ASSERTIONS) cargo nextest run --profile default --cargo-profile test-release --features concurrent,testing --filter-expr "not test(prove)" diff --git a/README.md b/README.md index 7ae4725f1..18b86990f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/0xPolygonMiden/miden-base/blob/main/LICENSE) [![test](https://github.com/0xPolygonMiden/miden-base/actions/workflows/test.yml/badge.svg)](https://github.com/0xPolygonMiden/miden-base/actions/workflows/test.yml) [![build](https://github.com/0xPolygonMiden/miden-base/actions/workflows/build.yml/badge.svg)](https://github.com/0xPolygonMiden/miden-base/actions/workflows/build.yml) -[![RUST_VERSION](https://img.shields.io/badge/rustc-1.78+-lightgray.svg)](https://www.rust-lang.org/tools/install) +[![RUST_VERSION](https://img.shields.io/badge/rustc-1.80+-lightgray.svg)](https://www.rust-lang.org/tools/install) [![GitHub Release](https://img.shields.io/github/release/0xPolygonMiden/miden-base)](https://github.com/0xPolygonMiden/miden-base/releases/) Description and core structures for the Miden Rollup protocol. @@ -23,7 +23,7 @@ If you want to join the technical discussion or learn more about the project, pl ## Status and features -Polygon Miden is currently on release v0.4. This is an early version of the protocol and its components. We expect to keep making changes (including breaking changes) to all components. +Polygon Miden is currently on release v0.5. This is an early version of the protocol and its components. We expect to keep making changes (including breaking changes) to all components. ### Feature highlights diff --git a/bench-tx/Cargo.toml b/bench-tx/Cargo.toml index 8274a21f8..63546fc0e 100644 --- a/bench-tx/Cargo.toml +++ b/bench-tx/Cargo.toml @@ -14,10 +14,11 @@ name = "bench-tx" path = "src/main.rs" [dependencies] -miden-lib = { path = "../miden-lib", version = "0.4" } -miden-objects = { path = "../objects", version = "0.4" } -miden-tx = { path = "../miden-tx", version = "0.4", features = ["testing"] } +miden-lib = { workspace = true } +miden-objects = { workspace = true } +miden-tx = { workspace = true, features = ["testing"] } rand = { workspace = true } -serde = { package = "serde", version = "1.0", features = ["derive"]} +rand_chacha = { version = "0.3", default-features = false} +serde = { version = "1.0", features = ["derive"] } serde_json = { package = "serde_json", version = "1.0", features = ["preserve_order"] } vm-processor = { workspace = true } diff --git a/bench-tx/README.md b/bench-tx/README.md index 2925634db..c278e977e 100644 --- a/bench-tx/README.md +++ b/bench-tx/README.md @@ -1,8 +1,9 @@ # Miden transactions benchmark -This crate contains an executable used for benchmarking transactions. +This crate contains an executable used for benchmarking transactions. For each transaction, data is collected on the number of cycles required to complete: + - Prologue - All notes processing - Each note execution diff --git a/bench-tx/bench-tx.json b/bench-tx/bench-tx.json index 647eacdf8..353a35003 100644 --- a/bench-tx/bench-tx.json +++ b/bench-tx/bench-tx.json @@ -1,21 +1,21 @@ { "simple": { - "prologue": 3644, - "notes_processing": 1151, + "prologue": 3833, + "notes_processing": 2178, "note_execution": { - "0x47b2fbff8a3f09e40343238e7b15c8918d7c63e570fd1b3c904ada458c4d74bd": 392, - "0x8a55c3531cdd5725aa805475093ed3006c6773b71a008e8ca840da8364a67cd6": 715 + "0x8cb51db3dbec8fab6c0ce3ac3e332aaa28fc99de07ae5a52dd93047a1fe459c2": 1401, + "0xed95b651bebd558f84ece32c4e7b8ac7b0fff9d845d3bff86037b64e9f9c7245": 735 }, - "tx_script_processing": 32, - "epilogue": 2222 + "tx_script_processing": 44, + "epilogue": 2287 }, "p2id": { - "prologue": 2004, - "notes_processing": 920, + "prologue": 2141, + "notes_processing": 1012, "note_execution": { - "0xb9fa30eb43d80d579be02dc004338e06b5ad565e81e0bac11a94ab01abfdd40a": 883 + "0xc1299ce481078310e6b4cf09f005bd898468019bb4cf7a03f5ded4b9e518790f": 977 }, - "tx_script_processing": 88209, - "epilogue": 272 + "tx_script_processing": 88460, + "epilogue": 291 } } \ No newline at end of file diff --git a/bench-tx/src/main.rs b/bench-tx/src/main.rs index 934ca9832..4251e843d 100644 --- a/bench-tx/src/main.rs +++ b/bench-tx/src/main.rs @@ -3,31 +3,25 @@ use std::{ fs::{read_to_string, write, File}, io::Write, path::Path, - rc::Rc, }; -use miden_lib::{notes::create_p2id_note, transaction::ToTransactionKernelInputs}; +use miden_lib::{notes::create_p2id_note, transaction::TransactionKernel}; use miden_objects::{ - accounts::{AccountId, AuthSecretKey}, - assembly::ProgramAst, + accounts::AccountId, assets::{Asset, FungibleAsset}, - crypto::{dsa::rpo_falcon512::SecretKey, rand::RpoRandomCoin}, + crypto::rand::RpoRandomCoin, notes::NoteType, - transaction::TransactionArgs, + transaction::{TransactionArgs, TransactionMeasurements, TransactionScript}, Felt, }; -use miden_tx::{ - auth::BasicAuthenticator, testing::TransactionContextBuilder, utils::Serializable, - TransactionExecutor, TransactionHost, TransactionProgress, -}; -use rand::rngs::StdRng; -use vm_processor::{ExecutionOptions, RecAdviceProvider, Word, ONE}; +use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; +use vm_processor::ONE; mod utils; use utils::{ - get_account_with_default_account_code, write_bench_results_to_json, - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ACCOUNT_ID_SENDER, DEFAULT_AUTH_SCRIPT, + get_account_with_default_account_code, get_new_pk_and_authenticator, + write_bench_results_to_json, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, ACCOUNT_ID_SENDER, DEFAULT_AUTH_SCRIPT, }; pub enum Benchmark { Simple, @@ -51,8 +45,8 @@ fn main() -> Result<(), String> { // run all available benchmarks let benchmark_results = vec![ - (Benchmark::Simple, benchmark_default_tx()?), - (Benchmark::P2ID, benchmark_p2id()?), + (Benchmark::Simple, benchmark_default_tx()?.into()), + (Benchmark::P2ID, benchmark_p2id()?.into()), ]; // store benchmark results in the JSON file @@ -65,18 +59,12 @@ fn main() -> Result<(), String> { // ================================================================================================ /// Runs the default transaction with empty transaction script and two default notes. -pub fn benchmark_default_tx() -> Result { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); - let mut executor: TransactionExecutor<_, ()> = - TransactionExecutor::new(tx_context.clone(), None).with_tracing(); +pub fn benchmark_default_tx() -> Result { + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let account_id = tx_context.account().id(); - executor.load_account(account_id).map_err(|e| e.to_string())?; let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -86,28 +74,17 @@ pub fn benchmark_default_tx() -> Result { .map(|note| note.id()) .collect::>(); - let transaction = executor - .prepare_transaction(account_id, block_ref, ¬e_ids, tx_context.tx_args().clone()) + let executor: TransactionExecutor<_, ()> = + TransactionExecutor::new(tx_context.clone(), None).with_tracing(); + let executed_transaction = executor + .execute_transaction(account_id, block_ref, ¬e_ids, tx_context.tx_args().clone()) .map_err(|e| e.to_string())?; - let (stack_inputs, advice_inputs) = transaction.get_kernel_inputs(); - let advice_recorder: RecAdviceProvider = advice_inputs.into(); - let mut host: TransactionHost<_, ()> = - TransactionHost::new(transaction.account().into(), advice_recorder, None); - - vm_processor::execute( - transaction.program(), - stack_inputs, - &mut host, - ExecutionOptions::default().with_tracing(), - ) - .map_err(|e| e.to_string())?; - - Ok(host.tx_progress().clone()) + Ok(executed_transaction.into()) } /// Runs the transaction which consumes a P2ID note into a basic wallet. -pub fn benchmark_p2id() -> Result { +pub fn benchmark_p2id() -> Result { // Create assets let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); let fungible_asset: Asset = FungibleAsset::new(faucet_id, 100).unwrap().into(); @@ -117,12 +94,8 @@ pub fn benchmark_p2id() -> Result { let target_account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(); - let sec_key = SecretKey::new(); - let target_pub_key: Word = sec_key.public_key().into(); - let mut pk_sk_bytes = sec_key.to_bytes(); - pk_sk_bytes.append(&mut target_pub_key.to_bytes()); - let target_sk_pk_felt: Vec = - pk_sk_bytes.iter().map(|a| Felt::new(*a as u64)).collect::>(); + let (target_pub_key, falcon_auth) = get_new_pk_and_authenticator(); + let target_account = get_account_with_default_account_code(target_account_id, target_pub_key, None); @@ -141,9 +114,8 @@ pub fn benchmark_p2id() -> Result { .input_notes(vec![note.clone()]) .build(); - let mut executor: TransactionExecutor<_, ()> = - TransactionExecutor::new(tx_context.clone(), None).with_tracing(); - executor.load_account(target_account_id).unwrap(); + let executor = + TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())).with_tracing(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -153,39 +125,15 @@ pub fn benchmark_p2id() -> Result { .map(|note| note.id()) .collect::>(); - let tx_script_code = ProgramAst::parse(DEFAULT_AUTH_SCRIPT).unwrap(); - - let tx_script_target = executor - .compile_tx_script( - tx_script_code.clone(), - vec![(target_pub_key, target_sk_pk_felt)], - vec![], - ) - .unwrap(); + let tx_script_target = + TransactionScript::compile(DEFAULT_AUTH_SCRIPT, [], TransactionKernel::assembler()) + .unwrap(); let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); // execute transaction - let transaction = executor - .prepare_transaction(target_account_id, block_ref, ¬e_ids, tx_args_target) - .map_err(|e| e.to_string())?; - - let (stack_inputs, advice_inputs) = transaction.get_kernel_inputs(); - let advice_recorder: RecAdviceProvider = advice_inputs.into(); - let authenticator = BasicAuthenticator::::new(&[( - sec_key.public_key().into(), - AuthSecretKey::RpoFalcon512(sec_key), - )]); - let authenticator = Some(Rc::new(authenticator)); - let mut host = - TransactionHost::new(transaction.account().into(), advice_recorder, authenticator); - - vm_processor::execute( - transaction.program(), - stack_inputs, - &mut host, - ExecutionOptions::default().with_tracing(), - ) - .map_err(|e| e.to_string())?; + let executed_transaction = executor + .execute_transaction(target_account_id, block_ref, ¬e_ids, tx_args_target) + .unwrap(); - Ok(host.tx_progress().clone()) + Ok(executed_transaction.into()) } diff --git a/bench-tx/src/utils.rs b/bench-tx/src/utils.rs index 55de68051..6330a9e05 100644 --- a/bench-tx/src/utils.rs +++ b/bench-tx/src/utils.rs @@ -1,14 +1,18 @@ extern crate alloc; -pub use alloc::collections::BTreeMap; +pub use alloc::{collections::BTreeMap, string::String}; +use std::rc::Rc; use miden_lib::transaction::TransactionKernel; use miden_objects::{ - accounts::{Account, AccountCode, AccountId, AccountStorage, SlotItem}, - assembly::ModuleAst, + accounts::{Account, AccountCode, AccountId, AccountStorage, AuthSecretKey, SlotItem}, assets::{Asset, AssetVault}, + crypto::dsa::rpo_falcon512::SecretKey, + transaction::TransactionMeasurements, Felt, Word, }; -use miden_tx::TransactionProgress; +use miden_tx::auth::BasicAuthenticator; +use rand::rngs::StdRng; +use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use serde::Serialize; use serde_json::{from_str, to_string_pretty, Value}; @@ -17,60 +21,46 @@ use super::{read_to_string, write, Benchmark, Path}; // CONSTANTS // ================================================================================================ -pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN: u64 = 0x200000000000001F; // 2305843009213693983 -pub const ACCOUNT_ID_SENDER: u64 = 0x800000000000001F; // 9223372036854775839 -pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN: u64 = 0x900000000000003F; // 10376293541461622847 +pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN: u64 = 0x200000000000001f; // 2305843009213693983 +pub const ACCOUNT_ID_SENDER: u64 = 0x800000000000001f; // 9223372036854775839 +pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN: u64 = 0x900000000000003f; // 10376293541461622847 pub const DEFAULT_AUTH_SCRIPT: &str = " - use.miden::contracts::auth::basic->auth_tx - begin - call.auth_tx::auth_tx_rpo_falcon512 + call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 end "; pub const DEFAULT_ACCOUNT_CODE: &str = " - use.miden::contracts::wallets::basic->basic_wallet - use.miden::contracts::auth::basic->basic_eoa - - export.basic_wallet::receive_asset - export.basic_wallet::send_asset - export.basic_eoa::auth_tx_rpo_falcon512 + export.::miden::contracts::wallets::basic::receive_asset + export.::miden::contracts::wallets::basic::create_note + export.::miden::contracts::wallets::basic::move_asset_to_note + export.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 "; -// TRANSACTION BENCHMARK +// MEASUREMENTS PRINTER // ================================================================================================ -#[derive(Serialize)] -pub struct TransactionBenchmark { - prologue: Option, - notes_processing: Option, - note_execution: BTreeMap>, - tx_script_processing: Option, - epilogue: Option, +#[derive(Debug, Clone, Serialize)] +pub struct MeasurementsPrinter { + prologue: usize, + notes_processing: usize, + note_execution: BTreeMap, + tx_script_processing: usize, + epilogue: usize, } -impl From for TransactionBenchmark { - fn from(tx_progress: TransactionProgress) -> Self { - let prologue = tx_progress.prologue().len(); - - let notes_processing = tx_progress.notes_processing().len(); - - let mut note_execution = BTreeMap::new(); - tx_progress.note_execution().iter().for_each(|(note_id, interval)| { - note_execution.insert(note_id.to_hex(), interval.len()); - }); - - let tx_script_processing = tx_progress.tx_script_processing().len(); - - let epilogue = tx_progress.epilogue().len(); - - Self { - prologue, - notes_processing, - note_execution, - tx_script_processing, - epilogue, +impl From for MeasurementsPrinter { + fn from(value: TransactionMeasurements) -> Self { + let note_execution_map = + value.note_execution.iter().map(|(id, len)| (id.to_hex(), *len)).collect(); + + MeasurementsPrinter { + prologue: value.prologue, + notes_processing: value.notes_processing, + note_execution: note_execution_map, + tx_script_processing: value.tx_script_processing, + epilogue: value.epilogue, } } } @@ -84,10 +74,9 @@ pub fn get_account_with_default_account_code( assets: Option, ) -> Account { let account_code_src = DEFAULT_ACCOUNT_CODE; - let account_code_ast = ModuleAst::parse(account_code_src).unwrap(); - let account_assembler = TransactionKernel::assembler(); + let assembler = TransactionKernel::assembler(); - let account_code = AccountCode::new(account_code_ast.clone(), &account_assembler).unwrap(); + let account_code = AccountCode::compile(account_code_src, assembler).unwrap(); let account_storage = AccountStorage::new(vec![SlotItem::new_value(0, 0, public_key)], BTreeMap::new()).unwrap(); @@ -99,9 +88,22 @@ pub fn get_account_with_default_account_code( Account::from_parts(account_id, account_vault, account_storage, account_code, Felt::new(1)) } +pub fn get_new_pk_and_authenticator() -> (Word, Rc>) { + let seed = [0_u8; 32]; + let mut rng = ChaCha20Rng::from_seed(seed); + + let sec_key = SecretKey::with_rng(&mut rng); + let pub_key: Word = sec_key.public_key().into(); + + let authenticator = + BasicAuthenticator::::new(&[(pub_key, AuthSecretKey::RpoFalcon512(sec_key))]); + + (pub_key, Rc::new(authenticator)) +} + pub fn write_bench_results_to_json( path: &Path, - tx_benchmarks: Vec<(Benchmark, TransactionProgress)>, + tx_benchmarks: Vec<(Benchmark, MeasurementsPrinter)>, ) -> Result<(), String> { // convert benchmark file internals to the JSON Value let benchmark_file = read_to_string(path).map_err(|e| e.to_string())?; @@ -109,8 +111,7 @@ pub fn write_bench_results_to_json( // fill becnhmarks JSON with results of each benchmark for (bench_type, tx_progress) in tx_benchmarks { - let tx_benchmark = TransactionBenchmark::from(tx_progress); - let tx_benchmark_json = serde_json::to_value(tx_benchmark).map_err(|e| e.to_string())?; + let tx_benchmark_json = serde_json::to_value(tx_progress).map_err(|e| e.to_string())?; benchmark_json[bench_type.to_string()] = tx_benchmark_json; } diff --git a/docs/architecture/state.md b/docs/architecture/state.md index bbb55217b..eb6fec7fa 100644 --- a/docs/architecture/state.md +++ b/docs/architecture/state.md @@ -85,9 +85,9 @@ Nullifiers are stored in a sparse Merkle tree, which maps [note nullifiers](note ![Architecture core concepts](../img/architecture/state/nullifier-db.png){ width="80%" } -To prove that a note has not been consumed previously, the operator needs to provide a Merkle path to its node, and then show that the value in that node is `0`. In our case nullifiers are $32$ bytes each, and thus, the height of the Sparse Merkle Tree need to be $256$. +To prove that a note has not been consumed previously, the operator needs to provide a Merkle path to its node, and then show that the value in that node is `0`. In our case nullifiers are $32$ bytes each, and thus, the height of the Sparse Merkle Tree needs to be $256$. -To add new nullifiers to the database, operators needs to maintain the entire nullifier set. Otherwise, they would not be able to compute the new root of the tree. +To add new nullifiers to the database, operators need to maintain the entire nullifier set. Otherwise, they would not be able to compute the new root of the tree. !!! note Nullifiers as constructed in Miden break linkability of privately stored notes and the information about the note's consumption. To know the [note's nullifier](notes.md#note-nullifier-to-ensure-private-consumption) one must know the note's data. diff --git a/docs/architecture/transactions/contexts.md b/docs/architecture/transactions/contexts.md index aaf834c9d..2deca6b55 100644 --- a/docs/architecture/transactions/contexts.md +++ b/docs/architecture/transactions/contexts.md @@ -64,7 +64,7 @@ export.receive_asset end ``` -The [account API](https://github.com/0xPolygonMiden/miden-base/blob/main/miden-lib/asm/miden/account.masm#L162) exposes procedures to manage accounts. This particular procedure that was called by the wallet invokes a `syscall` to return back to the root context **(3)**, where the account vault is stored in memory (see prologue). `syscall` can incoke all procedures defined in the [Kernel API](https://github.com/0xPolygonMiden/miden-base/blob/main/miden-lib/asm/kernels/transaction/api.masm). +The [account API](https://github.com/0xPolygonMiden/miden-base/blob/main/miden-lib/asm/miden/account.masm#L162) exposes procedures to manage accounts. This particular procedure that was called by the wallet invokes a `syscall` to return back to the root context **(3)**, where the account vault is stored in memory (see prologue). `syscall` can invoke all procedures defined in the [Kernel API](https://github.com/0xPolygonMiden/miden-base/blob/main/miden-lib/asm/kernels/transaction/api.masm). ```arduino #! Add the specified asset to the vault. @@ -76,4 +76,4 @@ end Now, the asset can be safely added to the vault within the kernel context, and the note can be successfully processed. -
\ No newline at end of file +
diff --git a/docs/architecture/transactions/execution.md b/docs/architecture/transactions/execution.md index 4db815c78..b3c11c277 100644 --- a/docs/architecture/transactions/execution.md +++ b/docs/architecture/transactions/execution.md @@ -4,7 +4,7 @@ comments: true The Miden transaction executor is the component that executes transactions. -Transaction execution consists of the following steps and results in a `ExecutedTransaction` object: +Transaction execution consists of the following steps and results in an `ExecutedTransaction` object: 1. Fetch the data required to execute a transaction from the data store. 2. Compile the transaction into an executable [MASM](https://0xpolygonmiden.github.io/miden-vm/user_docs/assembly/main.html) program using the transaction compiler. @@ -23,7 +23,7 @@ The data store defines the interface that transaction objects use to fetch the d - `Account` data which includes the [AccountID](../accounts.md#account-id) and the [AccountCode](../accounts.md#code) that is executed during the transaction. - A `BlockHeader` which contains metadata about the block, commitments to the current state of the chain, and the hash of the proof that attests to the integrity of the chain. -- A `ChainMmr` which authenticates consumed notes during transaction execution. Authentication is achieved by providing an inclusion-proof for the transaction's consumed notes against the `ChainMmr`-root associated with the latest block known at the time of transaction execution. +- A `ChainMmr` which authenticates input notes during transaction execution. Authentication is achieved by providing an inclusion proof for the transaction's input notes against the `ChainMmr`-root associated with the latest block known at the time of transaction execution. - `InputNotes` consumed by the transaction that include the corresponding note data, e.g. the [note script](../notes.md#the-note-script) and serial number. !!! note @@ -49,4 +49,4 @@ A successfully executed transaction results in a new account state which is a ve The transaction prover proves the inputted `ExecutedTransaction` and returns a `ProvenTransaction` object. The Miden node verifies the `ProvenTransaction` object using the transaction verifier and, if valid, updates the [state](../state.md) databases. -
\ No newline at end of file +
diff --git a/docs/architecture/transactions/kernel.md b/docs/architecture/transactions/kernel.md index 8c8578bc7..f1ebbd602 100644 --- a/docs/architecture/transactions/kernel.md +++ b/docs/architecture/transactions/kernel.md @@ -16,7 +16,7 @@ The kernel has a well-defined structure which does the following: 1. The [prologue](#prologue) prepares the transaction for processing by parsing the transaction data and setting up the root context. 2. Note processing executes the note processing loop which consumes each `InputNote` and invokes the note script of each note. 3. Transaction script processing executes the optional transaction script. -4. The [epilogue](#epilogue) finalizes the transaction by computing the created notes commitment, the final account hash, asserting asset invariant conditions, and asserting the nonce rules are upheld. +4. The [epilogue](#epilogue) finalizes the transaction by computing the output notes commitment, the final account hash, asserting asset invariant conditions, and asserting the nonce rules are upheld.
![Transaction program](../../img/architecture/transaction/transaction-program.png) @@ -28,7 +28,7 @@ The transaction kernel program receives two types of inputs, public inputs via t The operand stack holds the global inputs which serve as a commitment to the data being provided via the advice provider. -The advice provider holds data of the last known block, account and input note data. The details are layed out in the next paragraph. +The advice provider holds data of the last known block, account and input note data. The details are laid out in the next paragraph. ## Prologue @@ -76,11 +76,11 @@ As the account data is read from the advice provider, the account hash is comput Input note processing involves the kernel reading the data from each note and storing it at the appropriate memory addresses. All the data (note, account, and blockchain data) comes from the advice provider and global inputs. -Next to the total number of consumed notes, input note data consists of a serial number, the roots of the script, the inputs and asset vault, its metadata, and all its assets. +Next to the total number of input notes, input note data consists of a serial number, the roots of the script, the inputs and asset vault, its metadata, and all its assets. As each note is consumed, its hash and nullifier are computed. -The transaction nullifier commitment is computed via a sequential hash of `(nullifier, ZERO)` pairs for all consumed notes. This step involves authentication such that the input note data provided via the advice provider is consistent with the chain history. +The transaction nullifier commitment is computed via a sequential hash of `(nullifier, ZERO)` pairs for all input notes. This step involves authentication such that the input note data provided via the advice provider is consistent with the chain history. !!! info - Note data is required for computing the nullifier, e.g. the [note script](../notes.md#main-script) and the serial number. @@ -111,7 +111,7 @@ For every note, the [MAST root](https://0xpolygonmiden.github.io/miden-vm/design # => [] # check if we have more notes to consume and should loop again - exec.note::increment_current_consumed_note_ptr + exec.note::increment_current_input_note_ptr loc_load.0 neq # => [should_loop] @@ -147,7 +147,7 @@ The epilogue finalizes the transaction. It does the following: 1. Computes the final account hash. 2. If the account has changed, it asserts that the final account nonce is greater than the initial account nonce. -3. Computes the created notes commitment. +3. Computes the output notes commitment. 4. Asserts that the input and output vault roots are equal. There is an exception for special accounts, called faucets, which can mint or burn assets. In these cases, input and output vault roots are not equal. @@ -156,4 +156,4 @@ There is an exception for special accounts, called faucets, which can mint or bu The transaction kernel program outputs the transaction script root, a commitment of all newly created outputs notes, and the account hash in its new state. -
\ No newline at end of file +
diff --git a/docs/architecture/transactions/procedures.md b/docs/architecture/transactions/procedures.md index bde62bf0c..87f807e9c 100644 --- a/docs/architecture/transactions/procedures.md +++ b/docs/architecture/transactions/procedures.md @@ -8,73 +8,70 @@ There are user-facing procedures and kernel procedures. Users don't directly inv These procedures can be used to create smart contract/account code, note scripts, or account scripts. They basically serve as an API for the underlying kernel procedures. If a procedure can be called in the current context, an `exec` is sufficient. Otherwise the context procedures must be invoked by `call`. Users never need to invoke `syscall` procedures themselves. -!!! tip - - If capitalized, a variable representing a `word`, e.g., `ACCT_HASH` consists of four `felts`. If lowercase, the variable is represented by a single `felt`. +!!! tip - If capitalized, a variable representing a `word`, e.g., `ACCT_HASH` consists of four `felts`. If lowercase, the variable is represented by a single `felt`. ### Account -To import the account procedures, set `use.miden::account` at the beginning of the file. +To import the account procedures, set `use.miden::account` at the beginning of the file. Any procedure that changes the account state must be invoked in the account context and not by note or transaction scripts. All procedures invoke `syscall` to the kernel API and some are restricted by the kernel procedure `exec.authenticate_account_origin`, which fails if the parent context is not the executing account. -| Procedure name | Stack | Output | Context | Description | -|---------------------------|------------|--------------|---------|---------------------------------------------------------------------| -| `get_id` | `[]` | `[acct_id]` | account, note |
  • Returns the account id.
| -| `get_nonce` | `[]` | `[nonce]` | account, note |
  • Returns the account nonce.
| -| `get_initial_hash` | `[]` | `[H]` | account, note |
  • Returns the initial account hash.
| -| `get_current_hash` | `[]` | `[ACCT_HASH]`| account, note |
  • Computes and returns the account hash from account data stored in memory.
-| `incr_nonce` | `[value]` | `[]` | account |
  • Increments the account nonce by the provided `value` which can be at most `2^32 - 1` otherwise the procedure panics.
| -| `get_item` | `[index]` | `[VALUE]` | account, note |
  • Gets an item `VALUE` by `index` from the account storage.
  • Panics if the index is out of bounds.
| -| `set_item` | `[index, V']` | `[R', V]` | account |
  • Sets an index/value pair in the account storage.
  • Panics if the index is out of bounds. `R` is the new storage root.
| -| `set_code` | `[CODE_ROOT]`| `[]` | account |
  • Sets the code (`CODE_ROOT`) of the account the transaction is being executed against.
  • This procedure can only be executed on regular accounts with updatable code. Otherwise, the procedure fails.
| -| `get_balance` | `[faucet_id]`| `[balance]`| account, note |
  • Returns the `balance` of a fungible asset associated with a `faucet_id`.
  • Panics if the asset is not a fungible asset.
| -| `has_non_fungible_asset` | `[ASSET]` | `[has_asset]`| account, note |
  • Returns a boolean `has_asset` indicating whether the non-fungible asset is present in the vault.
  • Panics if the `ASSET` is a fungible asset.
| -| `add_asset` | `[ASSET]` | `[ASSET']` | account |
  • Adds the specified asset `ASSET` to the vault. Panics under various conditions.
  • If `ASSET` is a non-fungible asset, then `ASSET'` is the same as `ASSET`.
  • If `ASSET` is a fungible asset, then `ASSET'` is the total fungible asset in the account vault after `ASSET` was added to it.
| -| `remove_asset` | `[ASSET]` | `[ASSET]` | account |
  • Removes the specified `ASSET` from the vault.
  • Panics under various conditions.
| -| `get_vault_commitment` | `[]` | `[COM]` | account, note |
  • Returns a commitment `COM` to the account vault.
| +| Procedure name | Stack | Output | Context | Description | +| ------------------------ | ------------------- | ------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `get_id` | `[]` | `[acct_id]` | account, note |
  • Returns the account id.
| +| `get_nonce` | `[]` | `[nonce]` | account, note |
  • Returns the account nonce.
| +| `get_initial_hash` | `[]` | `[H]` | account, note |
  • Returns the initial account hash.
| +| `get_current_hash` | `[]` | `[ACCT_HASH]` | account, note |
  • Computes and returns the account hash from account data stored in memory.
| +| `incr_nonce` | `[value]` | `[]` | account |
  • Increments the account nonce by the provided `value` which can be at most `2^32 - 1` otherwise the procedure panics.
| +| `get_item` | `[index]` | `[VALUE]` | account, note |
  • Gets an item `VALUE` by `index` from the account storage.
  • Panics if the index is out of bounds.
| +| `set_item` | `[index, V']` | `[R', V]` | account |
  • Sets an index/value pair in the account storage.
  • Panics if the index is out of bounds. `R` is the new storage root.
| +| `set_code` | `[CODE_COMMITMENT]` | `[]` | account |
  • Sets the code (`CODE_COMMITMENT`) of the account the transaction is being executed against.
  • This procedure can only be executed on regular accounts with updatable code. Otherwise, the procedure fails.
| +| `get_balance` | `[faucet_id]` | `[balance]` | account, note |
  • Returns the `balance` of a fungible asset associated with a `faucet_id`.
  • Panics if the asset is not a fungible asset.
| +| `has_non_fungible_asset` | `[ASSET]` | `[has_asset]` | account, note |
  • Returns a boolean `has_asset` indicating whether the non-fungible asset is present in the vault.
  • Panics if the `ASSET` is a fungible asset.
| +| `add_asset` | `[ASSET]` | `[ASSET']` | account |
  • Adds the specified asset `ASSET` to the vault. Panics under various conditions.
  • If `ASSET` is a non-fungible asset, then `ASSET'` is the same as `ASSET`.
  • If `ASSET` is a fungible asset, then `ASSET'` is the total fungible asset in the account vault after `ASSET` was added to it.
| +| `remove_asset` | `[ASSET]` | `[ASSET]` | account |
  • Removes the specified `ASSET` from the vault.
  • Panics under various conditions.
| +| `get_vault_commitment` | `[]` | `[COM]` | account, note |
  • Returns a commitment `COM` to the account vault.
| ### Note To import the note procedures, set `use.miden::note` at the beginning of the file. All procedures are restricted to the note context. -| Procedure name | Inputs | Outputs | Context | Description | -|--------------------------|---------------------|-----------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------| -| `get_assets` | `[dest_ptr]` | `[num_assets, dest_ptr]` | note |
  • Writes the assets of the currently executing note into memory starting at the specified address `dest_ptr `.
  • `num_assets` is the number of assets in the currently executing note.
| -| `get_inputs` | `[dest_ptr]` | `[dest_ptr]` | note |
  • Writes the inputs of the currently executed note into memory starting at the specified address, `dest_ptr`.
| -| `get_sender` | `[]` | `[sender]` | note |
  • Returns the `sender` of the note currently being processed. Panics if a note is not being processed.
| -| `compute_inputs_hash` | `[inputs_ptr, num_inputs]` | `[HASH]` | note |
  • Computes hash of note inputs starting at the specified memory address.
| - +| Procedure name | Inputs | Outputs | Context | Description | +| --------------------- | -------------------------- | ------------------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `get_assets` | `[dest_ptr]` | `[num_assets, dest_ptr]` | note |
  • Writes the assets of the currently executing note into memory starting at the specified address `dest_ptr `.
  • `num_assets` is the number of assets in the currently executing note.
| +| `get_inputs` | `[dest_ptr]` | `[dest_ptr]` | note |
  • Writes the inputs of the currently executed note into memory starting at the specified address, `dest_ptr`.
| +| `get_sender` | `[]` | `[sender]` | note |
  • Returns the `sender` of the note currently being processed. Panics if a note is not being processed.
| +| `compute_inputs_hash` | `[inputs_ptr, num_inputs]` | `[HASH]` | note |
  • Computes hash of note inputs starting at the specified memory address.
| ### Tx -To import the transaction procedures set `use.miden::tx` at the beginning of the file. Only the `create_note` procedure is restricted to the account context. -| Procedure name | Inputs | Outputs | Context | Description | -|--------------------------|------------------|-------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `get_block_number` | `[]` | `[num]` | account, note |
  • Returns the block number `num` of the last known block at the time of transaction execution. | -| `get_block_hash` | `[]` | `[H]` | account, note |
    • Returns the block hash `H` of the last known block at the time of transaction execution.
    | -| `get_input_notes_hash` | `[]` | `[COM]` | account, note |
    • Returns the input notes hash `COM`.
    • This is computed as a sequential hash of (nullifier, empty_word_or_note_hash) tuples over all input notes. The `empty_word_or_notes_hash` functions as a flag, if the value is set to zero, then the notes are authenticated by the transaction kernel. If the value is non-zero, then note authentication will be delayed to the batch/block kernel. The delayed authentication allows a transaction to consume a public note that is not yet included to a block.
    | -| `get_output_notes_hash` | `[0, 0, 0, 0]` | `[COM]` | account, note |
    • Returns the output notes hash `COM`.
    • This is computed as a sequential hash of (note_id, note_metadata) tuples over all output notes.
    | -| `create_note` | `[ASSET, tag, RECIPIENT]` | `[ptr]` | account |
    • Creates a new note and returns a pointer to the memory address at which the note is stored.
    • `ASSET` is the asset to be included in the note.
    • `tag` is the tag to be included in the note. `RECIPIENT` is the recipient of the note.
    • `ptr` is the pointer to the memory address at which the note is stored.
    | +To import the transaction procedures set `use.miden::tx` at the beginning of the file. Only the `create_note` procedure is restricted to the account context. +| Procedure name | Inputs | Outputs | Context | Description | +| ----------------------- | ------------------------- | ------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `get_block_number` | `[]` | `[num]` | account, note |
    • Returns the block number `num` of the last known block at the time of transaction execution. | +| `get_block_hash` | `[]` | `[H]` | account, note |
      • Returns the block hash `H` of the last known block at the time of transaction execution.
      | +| `get_input_notes_hash` | `[]` | `[COM]` | account, note |
      • Returns the input notes hash `COM`.
      • This is computed as a sequential hash of (nullifier, empty_word_or_note_hash) tuples over all input notes. The `empty_word_or_notes_hash` functions as a flag, if the value is set to zero, then the notes are authenticated by the transaction kernel. If the value is non-zero, then note authentication will be delayed to the batch/block kernel. The delayed authentication allows a transaction to consume a public note that is not yet included to a block.
      | +| `get_output_notes_hash` | `[0, 0, 0, 0]` | `[COM]` | account, note |
      • Returns the output notes hash `COM`.
      • This is computed as a sequential hash of (note_id, note_metadata) tuples over all output notes.
      | +| `create_note` | `[ASSET, tag, RECIPIENT]` | `[ptr]` | account |
      • Creates a new note and returns a pointer to the memory address at which the note is stored.
      • `ASSET` is the asset to be included in the note.
      • `tag` is the tag to be included in the note. `RECIPIENT` is the recipient of the note.
      • `ptr` is the pointer to the memory address at which the note is stored.
      | ### Asset + To import the asset procedures set `use.miden::asset` at the beginning of the file. These procedures can only be called by faucet accounts. -| Procedure name | Stack | Output | Context | Description | -|------------------------------|---------------------|-----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `build_fungible_asset` | `[faucet_id, amount]` | `[ASSET]` | faucet |
      • Builds a fungible asset `ASSET` for the specified fungible faucet `faucet_id`, and `amount` of asset to create.
      | -| `create_fungible_asset` | `[amount]` | `[ASSET]` | faucet |
      • Creates a fungible asset `ASSET` for the faucet the transaction is being executed against and `amount` of the asset to create.
      | -| `build_non_fungible_asset` | `[faucet_id, DATA_HASH]` | `[ASSET]` | faucet |
      • Builds a non-fungible asset `ASSET` for the specified non-fungible faucet.
      • `faucet_id` is the faucet to create the asset for.
      • `DATA_HASH` is the data hash of the non-fungible asset to build.
      | -| `create_non_fungible_asset` | `[DATA_HASH]` | `[ASSET]` | faucet |
      • Creates a non-fungible asset `ASSET` for the faucet the transaction is being executed against.
      • `DATA_HASH` is the data hash of the non-fungible asset to create.
      | +| Procedure name | Stack | Output | Context | Description | +| --------------------------- | ------------------------ | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `build_fungible_asset` | `[faucet_id, amount]` | `[ASSET]` | faucet |
      • Builds a fungible asset `ASSET` for the specified fungible faucet `faucet_id`, and `amount` of asset to create.
      | +| `create_fungible_asset` | `[amount]` | `[ASSET]` | faucet |
      • Creates a fungible asset `ASSET` for the faucet the transaction is being executed against and `amount` of the asset to create.
      | +| `build_non_fungible_asset` | `[faucet_id, DATA_HASH]` | `[ASSET]` | faucet |
      • Builds a non-fungible asset `ASSET` for the specified non-fungible faucet.
      • `faucet_id` is the faucet to create the asset for.
      • `DATA_HASH` is the data hash of the non-fungible asset to build.
      | +| `create_non_fungible_asset` | `[DATA_HASH]` | `[ASSET]` | faucet |
      • Creates a non-fungible asset `ASSET` for the faucet the transaction is being executed against.
      • `DATA_HASH` is the data hash of the non-fungible asset to create.
      | ### Faucet To import the faucet procedures, set `use.miden::faucet` at the beginning of the file. -| Procedure name | Stack | Outputs | Context | Description | -|--------------------------|------------|-------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `mint` | `[ASSET]` | `[ASSET]` | faucet |
      • Mint an asset `ASSET` from the faucet the transaction is being executed against.
      • Panics under various conditions.
      | -| `burn` | `[ASSET]` | `[ASSET]` | faucet |
      • Burn an asset `ASSET` from the faucet the transaction is being executed against.
      • Panics under various conditions.
      | -| `get_total_issuance` | `[]` | `[total_issuance]`| faucet |
      • Returns the `total_issuance` of the fungible faucet the transaction is being executed against.
      • Panics if the transaction is not being executed against a fungible faucet.
      | - - +| Procedure name | Stack | Outputs | Context | Description | +| -------------------- | --------- | ------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `mint` | `[ASSET]` | `[ASSET]` | faucet |
      • Mint an asset `ASSET` from the faucet the transaction is being executed against.
      • Panics under various conditions.
      | +| `burn` | `[ASSET]` | `[ASSET]` | faucet |
      • Burn an asset `ASSET` from the faucet the transaction is being executed against.
      • Panics under various conditions.
      | +| `get_total_issuance` | `[]` | `[total_issuance]` | faucet |
      • Returns the `total_issuance` of the fungible faucet the transaction is being executed against.
      • Panics if the transaction is not being executed against a fungible faucet.
      | diff --git a/docs/index.md b/docs/index.md index 140f8217c..ee76103fe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,16 +1,12 @@ # Polygon Miden -Polygon Miden is a zero-knowledge rollup for private, high-throughput applications. +A rollup for high-throughput, private applications. -It is a modular execution layer that extends Ethereum's capabilities using powerful features such as parallel transaction execution and client-side proving. - -Miden allows users to prove state changes locally while the network only tracks a commitment, leading to privacy and high-throughput. Users can also let the operator prove public state changes like other rollups. - -With Miden, developers can create novel, high-throughput, privacy-preserving dApps for DeFi, RWA, and on-chain games with languages such as Rust and TypeScript. +Using Polygon Miden, builders can create novel, high-throughput, private applications for payments, DeFi, digital assets and gaming. Applications and users are secured by Ethereum and AggLayer. If you want to join the technical discussion, please check out the following: -* [Discord](https://discord.gg/0xpolygondevs) +* [Discord](https://discord.gg/0xpolygonRnD) * [Miden repo](https://github.com/0xPolygonMiden) * [Roadmap](introduction/roadmap.md) @@ -20,7 +16,7 @@ If you want to join the technical discussion, please check out the following: ## Status and features -Polygon Miden is currently on release v0.4. This is an early version of the protocol and its components. +Polygon Miden is currently on release v0.5. This is an early version of the protocol and its components. !!! important We expect breaking changes on all components. diff --git a/docs/introduction/get-started/create-account-use-faucet.md b/docs/introduction/get-started/create-account-use-faucet.md index daa070c73..7be2e2faf 100644 --- a/docs/introduction/get-started/create-account-use-faucet.md +++ b/docs/introduction/get-started/create-account-use-faucet.md @@ -23,7 +23,7 @@ The Miden client facilitates interaction with the Miden rollup and provides a wa ```shell cargo install miden-cli --features testing,concurrent ``` - You can now use the `miden --version` command, and you should see `Miden 0.4.0`. + You can now use the `miden --version` command, and you should see `Miden 0.5.0`. 3. Initialize the client. This creates the `miden-client.toml` file. @@ -77,7 +77,7 @@ Save the account ID for a future step. 2. You should see something like this: ```sh - Succesfully imported note 0x0ff340133840d35e95e0dc2e62c88ed75ab2e383dc6673ce0341bd486fed8cb6 + Successfully imported note 0x0ff340133840d35e95e0dc2e62c88ed75ab2e383dc6673ce0341bd486fed8cb6 ``` 3. Now that the note has been successfully imported, you can view the note's information using the following command: diff --git a/miden-lib/Cargo.toml b/miden-lib/Cargo.toml index b12c92095..27400b325 100644 --- a/miden-lib/Cargo.toml +++ b/miden-lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-lib" -version = "0.4.0" +version = "0.5.0" description = "Standard library of the Miden rollup" readme = "README.md" categories = ["no-std"] @@ -20,14 +20,16 @@ default = ["std"] std = ["assembly/std", "miden-objects/std", "miden-stdlib/std", "vm-processor/std"] # the testing feature is required to enable the account creation pow patch testing = ["miden-objects/testing"] +with-debug-info = ["miden-stdlib/with-debug-info"] [dependencies] -miden-objects = { path = "../objects", version = "0.4", default-features = false } +miden-objects = { workspace = true } miden-stdlib = { workspace = true } [dev-dependencies] -miden-objects = { path = "../objects", version = "0.4", default-features = false, features = ["testing"] } -vm-processor = { workspace = true, features = ["internals"] } +miden-objects = { workspace = true, features = ["testing"] } +vm-processor = { workspace = true, features = ["testing"] } [build-dependencies] assembly = { workspace = true } +miden-stdlib = { workspace = true } diff --git a/miden-lib/asm/kernels/transaction/api.masm b/miden-lib/asm/kernels/transaction/api.masm index ab6a89e9c..1bd8c6e5d 100644 --- a/miden-lib/asm/kernels/transaction/api.masm +++ b/miden-lib/asm/kernels/transaction/api.masm @@ -1,12 +1,19 @@ use.std::collections::smt -use.miden::kernels::tx::account -use.miden::kernels::tx::asset_vault -use.miden::kernels::tx::constants -use.miden::kernels::tx::faucet -use.miden::kernels::tx::memory -use.miden::kernels::tx::note -use.miden::kernels::tx::tx +use.kernel::account +use.kernel::asset_vault +use.kernel::constants +use.kernel::faucet +use.kernel::memory +use.kernel::note +use.kernel::tx + +# NOTE +# ================================================================================================= +# Procedures in this module are expected to be invoked using a `syscall` instruction. It makes no # +# guarantees about the contents of the `PAD` elements shown in the inputs and outputs. It is the # +# caller's responsibility to make sure these elements do not contain any meaningful data. # +# ================================================================================================= # ERRORS # ================================================================================================= @@ -50,10 +57,11 @@ proc.authenticate_account_origin # assert that the caller is from the user context exec.account::authenticate_procedure - # => [CALLER, ...] + # => [storage_offset, ...] - # drop the caller - dropw + # TODO: use the storage_offset for storage access + # drop the storage_offset + drop # => [...] end @@ -260,18 +268,18 @@ end #! Sets the code of the account the transaction is being executed against. This procedure can only #! executed on regular accounts with updatable code. Otherwise, this procedure fails. #! -#! Stack: [CODE_ROOT] +#! Stack: [CODE_COMMITMENT] #! Output: [0, 0, 0, 0] #! -#! - CODE_ROOT is the hash of the code to set. +#! - CODE_COMMITMENT is the hash of the code to set. export.set_account_code # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [CODE_ROOT] + # => [CODE_COMMITMENT] # arrange stack padw swapw - # => [CODE_ROOT, 0, 0, 0, 0] + # => [CODE_COMMITMENT, 0, 0, 0, 0] # set the account code exec.account::set_code @@ -334,7 +342,7 @@ export.account_vault_add_asset exec.authenticate_account_origin # => [ASSET] - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.19891 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_VAULT_BEFORE_ADD_ASSET_EVENT # => [ASSET] @@ -351,7 +359,9 @@ export.account_vault_add_asset # => [ASSET', ASSET] # emit event to signal that an asset is being added to the account vault - swapw emit.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT dropw + swapw + push.21383 drop # TODO: remove line, see miden-vm/#1122 + emit.ACCOUNT_VAULT_AFTER_ADD_ASSET_EVENT dropw # => [ASSET'] end @@ -371,7 +381,7 @@ export.account_vault_remove_asset exec.authenticate_account_origin # => [ASSET] - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.20071 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_VAULT_BEFORE_REMOVE_ASSET_EVENT # => [ASSET] @@ -383,28 +393,28 @@ export.account_vault_remove_asset exec.asset_vault::remove_asset # => [ASSET] - push.0 drop # TODO: remove line, see miden-vm/#1122 # emit event to signal that an asset is being removed from the account vault + push.20149 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT # => [ASSET] end -#! Returns the number of assets and vault hash of the note currently being processed. Panics if a -#! note is not being processed. +#! Returns the number of assets and the assets hash of the note currently being processed. Panics +#! if a note is not being processed. #! #! Inputs: [0, 0, 0, 0, 0] -#! Outputs: [VAULT_HASH, num_assets] +#! Outputs: [ASSETS_HASH, num_assets] #! #! - num_assets is the number of assets in the note currently being processed. -#! - VAULT_HASH is the vault hash of the note currently being processed. -export.get_note_vault_info - # get the vault info - exec.note::get_vault_info - # => [VAULT_HASH, num_assets, 0, 0, 0, 0, 0] +#! - ASSETS_HASH is the assets hash of the note currently being processed. +export.get_note_assets_info + # get the assets info + exec.note::get_assets_info + # => [ASSETS_HASH, num_assets, 0, 0, 0, 0, 0] # organize the stack for return movup.5 drop movup.5 drop movup.5 drop movup.5 drop movup.5 drop - # => [VAULT_HASH, num_assets] + # => [ASSETS_HASH, num_assets] end #! Returns the current note's inputs hash. @@ -503,35 +513,28 @@ end #! Creates a new note and returns the index of the note. #! -#! Inputs: [tag, aux, note_type, RECIPIENT] -#! Outputs: [note_idx, 0, 0, 0, 0, 0] +#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT, PAD(8)] +#! Outputs: [note_idx, PAD(15)] #! #! tag is the tag to be included in the note. #! aux is the auxiliary metadata to be included in the note. -#! note_type is the note storage type +#! note_type is the note storage type. +#! execution_hint is the note execution hint tag and payload. #! RECIPIENT is the recipient of the note. #! note_idx is the index of the crated note. export.create_note # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [tag, aux, note_type, RECIPIENT] - + # => [tag, aux, note_type, execution_hint, RECIPIENT, PAD(8)] + exec.tx::create_note - # => [note_idx] - - # prepare stack for return. Note: when create_note is called, the stack looks - # like [tag, aux, note_type, RECIPIENT, x, X, X], with 16 elements and X might be important data - # for the user. Without padding the kernel returns [note_idx, x, X, X, 0, 0, EMPTY_WORD] adding 0's. - # To keep the data in position we move 0's to the left between note_idx and the potentially - # important first element x. - movupw.3 movup.15 movup.15 movup.6 - # => [note_idx, 0, 0, 0, 0, 0, 0] + # => [note_idx, PAD(15)] end #! Adds the ASSET to the note specified by the index. #! -#! Inputs: [note_idx, ASSET] -#! Outputs: [note_idx, 0, 0, 0, 0] +#! Inputs: [note_idx, ASSET, PAD(11)] +#! Outputs: [note_idx, ASSET, PAD(11)] #! #! note_idx is the index of the the note to which the asset is added. #! ASSET can be a fungible or non-fungible asset. @@ -540,16 +543,12 @@ export.add_asset_to_note exec.authenticate_account_origin # => [note_idx, ASSET] + # duplicate the asset word to be able to return it + movdn.4 dupw movup.8 + # => [note_idx, ASSET, ASSET] + exec.tx::add_asset_to_note - # => [note_idx] - - # prepare stack for return. Note: when add_asset_to_note is called, the stack looks - # like [idx, ASSET, X, X, x, x, x], with 16 elements and X might be important data - # for the user. Without padding the kernel returns [idx, X, X, x, x, x, EMPTY_WORD] adding 0's. - # To keep the data in position we insert 0's between idx and the potentially important - # first element x. - movupw.3 movup.4 - # => [note_idx, 0, 0, 0, 0] + # => [note_idx, ASSET] end #! Returns a commitment to the account vault the transaction is being executed against. diff --git a/miden-lib/asm/miden/kernels/tx/account.masm b/miden-lib/asm/kernels/transaction/lib/account.masm similarity index 79% rename from miden-lib/asm/miden/kernels/tx/account.masm rename to miden-lib/asm/kernels/transaction/lib/account.masm index e0eff818b..999ed7551 100644 --- a/miden-lib/asm/miden/kernels/tx/account.masm +++ b/miden-lib/asm/kernels/transaction/lib/account.masm @@ -1,8 +1,8 @@ use.std::collections::smt use.std::crypto::hashes::native -use.miden::kernels::tx::constants -use.miden::kernels::tx::memory +use.kernel::constants +use.kernel::memory # ERRORS # ================================================================================================= @@ -28,6 +28,12 @@ const.ERR_SETTING_NON_VALUE_ITEM_ON_VALUE_SLOT=0x00020047 # Setting a map item on a non-map slot const.ERR_SETTING_MAP_ITEM_ON_NON_MAP_SLOT=0x00020048 +# Account procedure is not part of the account code +const.ERR_PROC_NOT_PART_OF_ACCOUNT_CODE=0x0002004A + +# Provided index is out of bounds +const.ERR_PROC_INDEX_OUT_OF_BOUNDS=0x0002004B + # CONSTANTS # ================================================================================================= @@ -69,6 +75,9 @@ const.SLOT_TYPES_COMMITMENT_STORAGE_SLOT=255 # The maximum value a slot type can take (An array of depth 64). const.MAX_SLOT_TYPE=64 +# The maximum number of account interface procedures. +const.MAX_NUM_PROCEDURES=256 + # EVENTS # ================================================================================================= @@ -127,6 +136,16 @@ export.get_max_slot_type push.MAX_SLOT_TYPE end +#! Returns the maximum number of account interface procedures. +#! +#! Stack: [] +#! Output: [max_num_procedures] +#! +#! - max_num_procedures is the maximum number of account interface procedures. +export.get_max_num_procedures + push.MAX_NUM_PROCEDURES +end + # PROCEDURES # ================================================================================================= @@ -162,12 +181,13 @@ export.incr_nonce u32assert.err=ERR_ACCOUNT_NONCE_INCR_MUST_BE_U32 # emit event to signal that account nonce is being incremented + push.20261 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_BEFORE_INCREMENT_NONCE_EVENT exec.memory::get_acct_nonce add exec.memory::set_acct_nonce - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.20357 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_AFTER_INCREMENT_NONCE_EVENT end @@ -197,7 +217,7 @@ export.memory::get_init_acct_hash->get_initial_hash #! Returns the most significant half with the account type bits masked out. #! -#! The accout type can be defined by comparing this value with the following constants: +#! The account type can be defined by comparing this value with the following constants: #! #! - REGULAR_ACCOUNT_UPDATABLE_CODE #! - REGULAR_ACCOUNT_IMMUTABLE_CODE @@ -299,21 +319,21 @@ end #! Sets the code of the account the transaction is being executed against. This procedure can only #! executed on regular accounts with updatable code. Otherwise, this procedure fails. #! -#! Stack: [CODE_ROOT] +#! Stack: [CODE_COMMITMENT] #! Output: [] #! -#! - CODE_ROOT is the hash of the code to set. +#! - CODE_COMMITMENT is the hash of the code to set. export.set_code # get the account id exec.memory::get_acct_id - # => [acct_id, CODE_ROOT] + # => [acct_id, CODE_COMMITMENT] # assert the account is an updatable regular account exec.is_updatable_account assert.err=ERR_ACCOUNT_SET_CODE_ACCOUNT_MUST_BE_UPDATABLE - # => [CODE_ROOT] + # => [CODE_COMMITMENT] - # set the code root - exec.memory::set_new_acct_code_root + # set the code commitment + exec.memory::set_new_acct_code_commitment dropw # => [] end @@ -370,11 +390,11 @@ proc.set_item_raw # => [OLD_VALUE, NEW_ROOT] # set the new storage root - swapw exec.memory::set_acct_storage_root + swapw exec.memory::set_acct_storage_root dropw # => [OLD_VALUE] end -#! Sets an item in the account storage. Panics if +#! Sets an item in the account storage. Panics if #! - the index is out of bounds #! - the slot type is not value #! @@ -385,7 +405,7 @@ end #! - V' is the value to set. #! - V is the previous value of the item. export.set_item - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.20443 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_STORAGE_BEFORE_SET_ITEM_EVENT # => [index, V'] @@ -407,7 +427,9 @@ export.set_item # => [V, V', index] # emit event to signal that an account storage item is being updated - swapw movup.8 emit.ACCOUNT_STORAGE_AFTER_SET_ITEM_EVENT drop dropw + swapw movup.8 + push.20551 drop # TODO: remove line, see miden-vm/#1122 + emit.ACCOUNT_STORAGE_AFTER_SET_ITEM_EVENT drop dropw # => [V] end @@ -438,7 +460,7 @@ export.set_map_item.3 exec.constants::get_storage_slot_type_map eq assert.err=ERR_SETTING_MAP_ITEM_ON_NON_MAP_SLOT # => [index, KEY, NEW_VALUE, OLD_ROOT] - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.20693 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_STORAGE_BEFORE_SET_MAP_ITEM_EVENT # => [index, KEY, NEW_VALUE, OLD_ROOT] @@ -457,7 +479,9 @@ export.set_map_item.3 # => [KEY, NEW_VALUE, index, ...] # emit event to signal that an account storage item is being updated - movup.8 emit.ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM_EVENT drop + movup.8 + push.20771 drop # TODO: remove line, see miden-vm/#1122 + emit.ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM_EVENT drop # => [KEY, NEW_VALUE, ...] # load OLD_VALUE and NEW_ROOT on the top of the stack @@ -469,39 +493,80 @@ export.set_map_item.3 # => [OLD_MAP_ROOT, OLD_VALUE, ...] end -#! Verifies that the procedure root is part of the account code Merkle tree. Panics if the -#! procedure root is not part of the account code Merkle tree. +#! Returns the procedure information +#! +#! Stack: [index, ...] +#! Output: [PROC_ROOT, storage_offset, ...] +#! +#! - PROC_ROOT is the hash of the procedure. +#! - storage_offset is the procedure storage offset. +#! +#! Panics if +#! - index is out of bounds +export.get_procedure_info + # TODO: Fix VM caller == [0,0,0,0] bug and remove this check + # check if index == 255 + dup push.255 neq + # => [is_255, index] + + # load procedure information from memory + if.true + # check that index < number of procedures contained in the account code + dup exec.memory::get_num_account_procedures lt assert.err=ERR_PROC_INDEX_OUT_OF_BOUNDS + # => [index] + + # get procedure section ptr + push.2 mul exec.memory::get_account_procedures_section_offset add dup push.1 add + # => [proc_ptr, offset_ptr] + + # load procedure information from memory + mem_load swap padw movup.4 mem_loadw + # => [PROC_ROOT, storage_offset] + end + end + +#! Verifies that the procedure root is part of the account code #! #! Stack: [PROC_ROOT] -#! Output: [PROC_ROOT] +#! Output: [storage_offset] #! #! - PROC_ROOT is the hash of the procedure to authenticate. -export.authenticate_procedure.1 - # load the account code root onto the stack - exec.memory::get_acct_code_root swapw - # => [PROC_ROOT, CODE_ROOT] - - # load the index of the procedure root onto the advice stack, and move it to the operand stack - emit.ACCOUNT_PUSH_PROCEDURE_INDEX_EVENT adv_push.1 movdn.4 - # => [PROC_ROOT, index, CODE_ROOT] - - # push the depth of the code Merkle tree onto the stack - push.ACCOUNT_CODE_TREE_DEPTH movdn.4 - # => [PROC_ROOT, depth, index, CODE_ROOT] - - # verify the procedure exists in the account code Merkle tree - mtree_verify - # => [PROC_ROOT, depth, index, CODE_ROOT] - - # drop accessory variables - movup.4 drop movup.4 drop swapw dropw - # => [PROC_ROOT] +#! +#! Panics if +#! - procedure root is not part of the account code. +export.authenticate_procedure + # load procedure index + push.20897 drop # TODO: remove line, see miden-vm/#1122 + emit.ACCOUNT_PUSH_PROCEDURE_INDEX_EVENT adv_push.1 + # => [index, PROC_ROOT] + + # get procedure info (PROC_ELEMENTS, storage_offset) from memory stored at index + exec.get_procedure_info + # In production: + # => [PROC_ELEMENTS, storage_offset, PROC_ROOT] + # During testing: + # => [255, PROC_ROOT] + + # TODO: Fix VM caller == [0,0,0,0] bug and remove this check + # check if get_procedure_info returned 255 + dup push.255 neq + # => [is_255, index, PROC_ROOT] + + # verify that PROC_ROOT matches returned procedure information + if.true + # verify that PROC_ROOT exists in memory at index + movup.4 movdn.8 assert_eqw.err=ERR_PROC_NOT_PART_OF_ACCOUNT_CODE + # => [storage_offset] + else + # drop PROC_ROOT + movdn.4 dropw + end end #! Validates that the account seed, provided via the advice map, satisfies the seed requirements. #! #! Validation is performed via the following steps: -#! 1. compute the hash of (SEED, CODE_ROOT, STORAGE_ROOT, 0, 0, 0, 0) +#! 1. compute the hash of (SEED, CODE_commitment, STORAGE_ROOT, 0, 0, 0, 0) #! 2. Assert the least significant element of the digest is equal to the account id of the account #! the transaction is being executed against. #! 3. Assert the most significant element has sufficient proof of work (trailing zeros) for the account @@ -511,14 +576,16 @@ end #! Output: [] export.validate_seed # pad capacity elements of hasher and populate first four elements of the rate with the account id seed - padw exec.memory::get_acct_id push.0.0.0 adv.push_mapval adv_loadw + padw exec.memory::get_acct_id push.0.0.0 + adv.push_mapval push.15263 drop # FIX: wrap the decorator to ensure MAST uniqueness + adv_loadw # => [SEED, 0, 0, 0, 0] - # populate last four elements of the hasher rate with the code root - exec.memory::get_acct_code_root - # => [CODE_ROOT, SEED, 0, 0, 0, 0] + # populate last four elements of the hasher rate with the code commitment + exec.memory::get_acct_code_commitment + # => [CODE_COMMITMENT, SEED, 0, 0, 0, 0] - # perform first permutation of seed and code_root (from advice stack) perm(seed, code_root) + # perform first permutation of seed and code_commitment (from advice stack) perm(seed, code_commitment) hperm # => [RATE, RATE, PERM] diff --git a/miden-lib/asm/miden/kernels/tx/asset.masm b/miden-lib/asm/kernels/transaction/lib/asset.masm similarity index 98% rename from miden-lib/asm/miden/kernels/tx/asset.masm rename to miden-lib/asm/kernels/transaction/lib/asset.masm index b7ac3bb68..f27e083ba 100644 --- a/miden-lib/asm/miden/kernels/tx/asset.masm +++ b/miden-lib/asm/kernels/transaction/lib/asset.masm @@ -1,4 +1,4 @@ -use.miden::kernels::tx::account +use.kernel::account # ERRORS # ================================================================================================= @@ -57,11 +57,11 @@ end #! #! ASSET is the asset to validate. export.validate_fungible_asset - # assert that ASSET[1] == EMPTY_WORD + # assert that ASSET[1] == ZERO dup.1 not assert.err=ERR_FUNGIBLE_ASSET_FORMAT_POSITION_ONE_MUST_BE_ZERO # => [ASSET] - # assert that ASSET[2] == EMPTY_WORD + # assert that ASSET[2] == ZERO dup.2 not assert.err=ERR_FUNGIBLE_ASSET_FORMAT_POSITION_TWO_MUST_BE_ZERO # => [ASSET] diff --git a/miden-lib/asm/miden/kernels/tx/asset_vault.masm b/miden-lib/asm/kernels/transaction/lib/asset_vault.masm similarity index 96% rename from miden-lib/asm/miden/kernels/tx/asset_vault.masm rename to miden-lib/asm/kernels/transaction/lib/asset_vault.masm index d9bb4e594..251241b75 100644 --- a/miden-lib/asm/miden/kernels/tx/asset_vault.masm +++ b/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -1,8 +1,8 @@ use.std::collections::smt -use.miden::kernels::tx::account -use.miden::kernels::tx::asset -use.miden::kernels::tx::memory +use.kernel::account +use.kernel::asset +use.kernel::memory # ERRORS # ================================================================================================= @@ -118,7 +118,9 @@ export.add_fungible_asset # get the asset vault root and read the vault asset value using the `push_smtpeek` decorator. # To account for the edge case in which CUR_VAULT_VALUE is an EMPTY_WORD, we replace the most # significant element with the faucet_id to construct the CUR_ASSET. - padw dup.10 mem_loadw swapw adv.push_smtpeek adv_loadw swapw dupw.1 drop movup.11 + padw dup.10 mem_loadw swapw + adv.push_smtpeek push.15329 drop # FIX: wrap the decorator to ensure MAST uniqueness + adv_loadw swapw dupw.1 drop movup.11 # => [CUR_ASSET, VAULT_ROOT, CUR_VAULT_VALUE, amount, vault_root_ptr] # arrange elements @@ -238,7 +240,9 @@ export.remove_fungible_asset # get the asset vault root and read the vault asset value using the `push_smtpeek` decorator # To account for the edge case in which CUR_VAULT_VALUE is an EMPTY_WORD, we replace the most # significant element with the faucet_id to construct the CUR_ASSET. - padw dup.14 mem_loadw swapw adv.push_smtpeek adv_loadw dupw movdnw.2 drop movup.11 + padw dup.14 mem_loadw swapw + adv.push_smtpeek push.15413 drop # FIX: wrap the decorator to ensure MAST uniqueness + adv_loadw dupw movdnw.2 drop movup.11 # => [CUR_ASSET, VAULT_ROOT, CUR_VAULT_VALUE, amount, ASSET, vault_root_ptr] # arrange elements @@ -323,7 +327,7 @@ export.remove_asset exec.asset::is_fungible_asset # => [is_fungible_asset, ASSET, vault_root_ptr] - # add the asset to the asset vault + # remove the asset from the asset vault if.true exec.remove_fungible_asset # => [ASSET] diff --git a/miden-lib/asm/miden/kernels/tx/constants.masm b/miden-lib/asm/kernels/transaction/lib/constants.masm similarity index 91% rename from miden-lib/asm/miden/kernels/tx/constants.masm rename to miden-lib/asm/kernels/transaction/lib/constants.masm index 508517e5c..c830d5aef 100644 --- a/miden-lib/asm/miden/kernels/tx/constants.masm +++ b/miden-lib/asm/kernels/transaction/lib/constants.masm @@ -71,13 +71,13 @@ export.get_max_assets_per_note push.MAX_ASSETS_PER_NOTE end -#! Returns the max allow number of consumed notes. +#! Returns the maximum number of notes that can be consumed in a single transaction. #! #! Stack: [] -#! Output: [max_num_consumed_notes] +#! Output: [max_num_input_notes] #! -#! - max_num_consumed_notes is the max number of consumed notes. -export.get_max_num_consumed_notes +#! - max_num_input_notes is the max number of input notes. +export.get_max_num_input_notes push.MAX_INPUT_NOTES_PER_TX end @@ -101,13 +101,13 @@ export.get_note_tree_depth push.NOTE_TREE_DEPTH end -#! Returns the max number of notes that can be created in a single transaction. +#! Returns the maximum number of notes that can be created in a single transaction. #! #! Stack: [] -#! Output: [max_num_created_notes] +#! Output: [max_num_output_notes] #! -#! - max_num_created_notes is the max number of notes that can be created in a single transaction. -export.get_max_num_created_notes +#! - max_num_output_notes is the max number of notes that can be created in a single transaction. +export.get_max_num_output_notes push.MAX_OUTPUT_NOTES_PER_TX end diff --git a/miden-lib/asm/miden/kernels/tx/epilogue.masm b/miden-lib/asm/kernels/transaction/lib/epilogue.masm similarity index 63% rename from miden-lib/asm/miden/kernels/tx/epilogue.masm rename to miden-lib/asm/kernels/transaction/lib/epilogue.masm index e291a1730..4a68103fb 100644 --- a/miden-lib/asm/miden/kernels/tx/epilogue.masm +++ b/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -1,8 +1,8 @@ -use.miden::kernels::tx::account -use.miden::kernels::tx::asset_vault -use.miden::kernels::tx::constants -use.miden::kernels::tx::memory -use.miden::kernels::tx::note +use.kernel::account +use.kernel::asset_vault +use.kernel::constants +use.kernel::memory +use.kernel::note # ERRORS # ================================================================================================= @@ -23,28 +23,28 @@ const.ERR_EPILOGUE_ASSETS_DONT_ADD_UP=0x0002000A #! Output: [OUTPUT_NOTES_COMMITMENT, ...] proc.copy_output_notes_to_advice_map # get the number of notes created by the transaction - exec.memory::get_num_created_notes + exec.memory::get_num_output_notes # => [num_notes, OUTPUT_NOTES_COMMITMENT, ...] - # if there are created notes, add them to the advice map + # if there are output notes, add them to the advice map dup eq.0 if.true # drop num_notes drop else - # compute the end boundary of the created notes section - exec.memory::get_created_note_ptr movdn.4 - # => [OUTPUT_NOTES_COMMITMENT, created_notes_end_ptr, ...] + # compute the end boundary of the output notes section + exec.memory::get_output_note_ptr movdn.4 + # => [OUTPUT_NOTES_COMMITMENT, output_notes_end_ptr, ...] - # compute the start boundary of the created notes section - exec.memory::get_created_note_data_offset movdn.4 - # => [OUTPUT_NOTES_COMMITMENT, created_note_ptr, created_notes_end_ptr, ...] + # compute the start boundary of the output notes section + exec.memory::get_output_note_data_offset movdn.4 + # => [OUTPUT_NOTES_COMMITMENT, output_note_ptr, output_notes_end_ptr, ...] # insert created data into the advice map - adv.insert_mem - # => [OUTPUT_NOTES_COMMITMENT, created_note_ptr, created_notes_end_ptr, ...] + adv.insert_mem push.15511 drop # FIX: wrap the decorator to ensure MAST uniqueness + # => [OUTPUT_NOTES_COMMITMENT, output_note_ptr, output_notes_end_ptr, ...] - # drop created note pointers + # drop output note pointers movup.4 drop movup.4 drop end # => [OUTPUT_NOTES_COMMITMENT, ...] @@ -54,86 +54,86 @@ end # ================================================================================================= #! Builds the output vault which is combination of the assets in the account vault at the end of -#! the transaction and all the assets from the created notes. +#! the transaction and all the assets in the output notes. #! #! The output vault is built as follows: #! - we first copy the account vault root to the output vault root. -#! - we then loop over the created notes and insert the assets into the output vault. +#! - we then loop over the output notes and insert their assets into the output vault. #! #! Stack: [] #! Output: [] proc.build_output_vault # copy final account vault root to output account vault root - exec.memory::get_acct_vault_root exec.memory::set_output_vault_root + exec.memory::get_acct_vault_root exec.memory::set_output_vault_root dropw # => [] - # get the number of created notes from memory - exec.memory::get_num_created_notes - # => [num_created_notes] + # get the number of output notes from memory + exec.memory::get_num_output_notes + # => [num_output_notes] # calculate the address at which we should stop looping - exec.memory::get_created_note_ptr - # => [created_notes_end_ptr] + exec.memory::get_output_note_ptr + # => [output_notes_end_ptr] - # compute pointer for the first created note - push.0 exec.memory::get_created_note_ptr - # => [created_note_ptr, created_notes_end_ptr] + # compute pointer for the first output note + push.0 exec.memory::get_output_note_ptr + # => [output_note_ptr, output_notes_end_ptr] - # check if the number of created notes is greater then 0. Conditional for the while loop. + # check if the number of output notes is greater then 0. Conditional for the while loop. dup.1 dup.1 neq - # => [should_loop, created_note_ptr, created_notes_end_ptr] + # => [should_loop, output_note_ptr, output_notes_end_ptr] - # loop over created notes and add assets to output vault + # loop over output notes and add assets to output vault while.true - # get the number of assets for the created note from memory - dup exec.memory::get_created_note_num_assets - # => [num_assets, note_data_ptr, created_notes_end_ptr] + # get the number of assets for the output note from memory + dup exec.memory::get_output_note_num_assets + # => [num_assets, note_data_ptr, output_notes_end_ptr] - # prepare stack for reading created note assets - exec.memory::get_output_vault_root_ptr dup.2 exec.memory::get_created_note_asset_data_ptr dup - # => [assets_start_ptr, assets_start_ptr, output_vault_root_ptr, num_assets, note_data_ptr. - # created_notes_end_ptr] + # prepare stack for reading output note assets + exec.memory::get_output_vault_root_ptr dup.2 exec.memory::get_output_note_asset_data_ptr dup + # => [assets_start_ptr, assets_start_ptr, output_vault_root_ptr, num_assets, note_data_ptr, + # output_notes_end_ptr] - # compute the end pointer for created note asset looping + # compute the end pointer for output note asset looping dup.3 add swap # => [assets_start_ptr, assets_end_ptr, output_vault_root_ptr, num_assets, note_data_ptr, - # created_notes_end_ptr] + # output_notes_end_ptr] # assess if we should loop dup.1 dup.1 neq # => [should_loop, assets_start_ptr, assets_end_ptr, output_vault_root_ptr, num_assets, - # note_data_ptr, created_notes_end_ptr] + # note_data_ptr, output_notes_end_ptr] - # loop over created note assets and insert them into the output vault + # loop over output note assets and insert them into the output vault while.true # duplicate output_vault_root_ptr dup.2 # => [output_vault_root_ptr, assets_start_ptr, assets_end_ptr, output_vault_root_ptr, - # num_assets, note_data_ptr, created_notes_end_ptr] + # num_assets, note_data_ptr, output_notes_end_ptr] - # read the created note asset from memory + # read the output note asset from memory padw dup.5 mem_loadw # => [ASSET, output_vault_root_ptr, assets_start_ptr, assets_end_ptr, output_vault_root_ptr, - # num_assets, note_data_ptr, created_notes_end_ptr] + # num_assets, note_data_ptr, output_notes_end_ptr] - # insert created note asset into output vault + # insert output note asset into output vault exec.asset_vault::add_asset dropw # => [assets_start_ptr, assets_end_ptr, output_vault_root_ptr, num_assets, note_data_ptr, - # created_notes_end_ptr] + # output_notes_end_ptr] # increment assets_start_ptr and asses if we should loop again add.1 dup.1 dup.1 neq # => [should_loop, assets_start_ptr, assets_end_ptr, output_vault_root_ptr, num_assets, - # note_data_ptr, created_notes_end_ptr] + # note_data_ptr, output_notes_end_ptr] end # clean stack drop drop drop drop - # => [note_data_ptr, created_note_end_ptr] + # => [note_data_ptr, output_note_end_ptr] - # increment created note pointer and check if we should loop again + # increment output note pointer and check if we should loop again exec.constants::get_note_mem_size add dup.1 dup.1 neq - # => [should_loop, created_note_ptr, created_notes_end_ptr] + # => [should_loop, output_note_ptr, output_notes_end_ptr] end # clean stack @@ -144,20 +144,20 @@ end # ACCOUNT CODE UPDATE # ================================================================================================= -#! Updates the account code root if the account code has changed. `NEW_ACCT_CODE_ROOT` is set to -#! the initial account code root in the prologue and as such this procedure will not result in a -#! change to the account code root if the `account::set_code` procedure has not been invoked in -#! this transaction. +#! Updates the account code commitment if the account code has changed. `NEW_ACCT_CODE_COMMITMENT` +#! is set to the initial account code commitment in the prologue and as such this procedure will +#! not result in a change to the account code commitment if the `account::set_code` procedure has +#! not been invoked in this transaction. #! #! Stack: [] #! Output: [] proc.update_account_code - # check if the account code root has been updated - exec.memory::get_new_acct_code_root - # => [NEW_ACCT_CODE_ROOT] + # check if the account code commitment has been updated + exec.memory::get_new_acct_code_commitment + # => [NEW_ACCT_CODE_COMMITMENT] - # set the account code root to the new account code root (may not have changed) - exec.memory::set_acct_code_root + # set the account code commitment to the new account code commitment (may not have changed) + exec.memory::set_acct_code_commitment dropw # => [] end @@ -168,13 +168,13 @@ end #! - computes the final account hash #! - if the account has changed, assert that the final account nonce is greater than the initial #! account nonce -#! - computes the created notes commitment +#! - computes the output notes commitment #! - asserts that the input and output vault roots are equal #! #! Stack: [] #! Output: [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] #! -#! - OUTPUT_NOTES_COMMITMENT is the commitment of the created notes +#! - OUTPUT_NOTES_COMMITMENT is the commitment of the output notes #! - FINAL_ACCOUNT_HASH is the final account hash export.finalize_transaction # update account code @@ -198,7 +198,7 @@ export.finalize_transaction # => [FINAL_ACCOUNT_HASH, acct_data_ptr, acct_data_end_ptr, INIT_ACCT_HASH] # insert final account data into the advice map - adv.insert_mem + adv.insert_mem push.15619 drop # FIX: wrap the decorator to ensure MAST uniqueness # => [FINAL_ACCOUNT_HASH, acct_data_ptr, acct_data_end_ptr, INIT_ACCT_HASH] # drop account data section pointers @@ -232,7 +232,7 @@ export.finalize_transaction exec.build_output_vault # => [FINAL_ACCOUNT_HASH] - # compute created note hash + # compute output notes commitment exec.note::compute_output_notes_commitment # => [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] diff --git a/miden-lib/asm/miden/kernels/tx/faucet.masm b/miden-lib/asm/kernels/transaction/lib/faucet.masm similarity index 98% rename from miden-lib/asm/miden/kernels/tx/faucet.masm rename to miden-lib/asm/kernels/transaction/lib/faucet.masm index 176fe1929..6547060ae 100644 --- a/miden-lib/asm/miden/kernels/tx/faucet.masm +++ b/miden-lib/asm/kernels/transaction/lib/faucet.masm @@ -1,9 +1,9 @@ use.std::collections::smt -use.miden::kernels::tx::account -use.miden::kernels::tx::asset -use.miden::kernels::tx::asset_vault -use.miden::kernels::tx::memory +use.kernel::account +use.kernel::asset +use.kernel::asset_vault +use.kernel::memory # ERRORS # ================================================================================================= diff --git a/miden-lib/asm/miden/kernels/tx/memory.masm b/miden-lib/asm/kernels/transaction/lib/memory.masm similarity index 61% rename from miden-lib/asm/miden/kernels/tx/memory.masm rename to miden-lib/asm/kernels/transaction/lib/memory.masm index 2019d7a68..ae8a4f000 100644 --- a/miden-lib/asm/miden/kernels/tx/memory.masm +++ b/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -1,4 +1,4 @@ -use.miden::kernels::tx::constants +use.kernel::constants # ERRORS # ================================================================================================= @@ -13,11 +13,11 @@ const.ERR_NOTE_TOO_MANY_ASSETS=0x0002002A # The memory address at which the transaction vault root is stored const.TX_VAULT_ROOT_PTR=0 -# The memory address at which a pointer to the consumed note being executed is stored. -const.CURRENT_CONSUMED_NOTE_PTR=1 +# The memory address at which a pointer to the input note being executed is stored. +const.CURRENT_INPUT_NOTE_PTR=1 -# The memory address at which the number of created notes is stored. -const.NUM_CREATED_NOTES_PTR=2 +# The memory address at which the number of output notes is stored. +const.NUM_OUTPUT_NOTES_PTR=2 # The memory address at which the input vault root is stored const.INPUT_VAULT_ROOT_PTR=3 @@ -108,55 +108,61 @@ const.ACCT_VAULT_ROOT_PTR=401 # The memory address at which the account storage root is stored const.ACCT_STORAGE_ROOT_PTR=402 -# The memory address at which the account code root is stored -const.ACCT_CODE_ROOT_PTR=403 +# The memory address at which the account code commitment is stored +const.ACCT_CODE_COMMITMENT_PTR=403 -# The memory address at which the new account code root is stored -const.ACCT_NEW_CODE_ROOT_PTR=404 +# The memory address at which the new account code commitment is stored +const.ACCT_NEW_CODE_COMMITMENT_PTR=404 # The memory offset at which the account data section ends (exclusive) const.ACCT_CORE_DATA_SECTION_END_OFFSET=404 -# The memory address at which the account storage slot type data beings +# The memory address at which the account storage slot type data begins const.ACCT_STORAGE_SLOT_TYPE_DATA_OFFSET=405 -# CONSUMED NOTES DATA +# The memory address at which the number of procedures contained in the account code is stored +const.NUM_ACCT_PROCEDURES_PTR=999 + +# The memory address at which the account procedures section begins +const.ACCT_PROCEDURES_SECTION_OFFSET=1000 + +# INPUT NOTES DATA # ------------------------------------------------------------------------------------------------- -# The memory address at which the consumed note section begins. -const.CONSUMED_NOTE_SECTION_OFFSET=1048576 +# The memory address at which the input note section begins. +const.INPUT_NOTE_SECTION_OFFSET=1048576 -# The memory address at which the consumed note data section begins. -const.CONSUMED_NOTE_DATA_SECTION_OFFSET=1064960 +# The memory address at which the input note data section begins. +const.INPUT_NOTE_DATA_SECTION_OFFSET=1064960 -# The memory address at which the number of consumed notes is stored. -const.CONSUMED_NOTE_NUM_PTR=1048576 +# The memory address at which the number of input notes is stored. +const.NUM_INPUT_NOTES_PTR=1048576 -# The offsets at which data of a consumed note is stored relative to the start of its data segment -const.CONSUMED_NOTE_ID_OFFSET=0 -const.CONSUMED_NOTE_CORE_DATA_OFFSET=1 -const.CONSUMED_NOTE_SERIAL_NUM_OFFSET=1 -const.CONSUMED_NOTE_SCRIPT_ROOT_OFFSET=2 -const.CONSUMED_NOTE_INPUTS_HASH_OFFSET=3 -const.CONSUMED_NOTE_ASSETS_HASH_OFFSET=4 -const.CONSUMED_NOTE_METADATA_OFFSET=5 -const.CONSUMED_NOTE_ARGS_OFFSET=6 -const.CONSUMED_NOTE_NUM_ASSETS_OFFSET=7 -const.CONSUMED_NOTE_ASSETS_OFFSET=8 +# The offsets at which data of a input note is stored relative to the start of its data segment +const.INPUT_NOTE_ID_OFFSET=0 +const.INPUT_NOTE_CORE_DATA_OFFSET=1 +const.INPUT_NOTE_SERIAL_NUM_OFFSET=1 +const.INPUT_NOTE_SCRIPT_ROOT_OFFSET=2 +const.INPUT_NOTE_INPUTS_HASH_OFFSET=3 +const.INPUT_NOTE_ASSETS_HASH_OFFSET=4 +const.INPUT_NOTE_METADATA_OFFSET=5 +const.INPUT_NOTE_ARGS_OFFSET=6 +const.INPUT_NOTE_NUM_ASSETS_OFFSET=7 +const.INPUT_NOTE_ASSETS_OFFSET=8 -# CREATED NOTES +# OUTPUT NOTES # ------------------------------------------------------------------------------------------------- -# The memory address at which the created notes section begins. -const.CREATED_NOTE_SECTION_OFFSET=4194304 +# The memory address at which the output notes section begins. +const.OUTPUT_NOTE_SECTION_OFFSET=4194304 -# The offsets at which data of a created note is stored relative to the start of its data segment. -const.CREATED_NOTE_ID_OFFSET=0 -const.CREATED_NOTE_METADATA_OFFSET=1 -const.CREATED_NOTE_RECIPIENT_OFFSET=2 -const.CREATED_NOTE_ASSETS_HASH_OFFSET=3 -const.CREATED_NOTE_NUM_ASSETS_OFFSET=4 -const.CREATED_NOTE_ASSETS_OFFSET=5 +# The offsets at which data of a output note is stored relative to the start of its data segment. +const.OUTPUT_NOTE_ID_OFFSET=0 +const.OUTPUT_NOTE_METADATA_OFFSET=1 +const.OUTPUT_NOTE_RECIPIENT_OFFSET=2 +const.OUTPUT_NOTE_ASSETS_HASH_OFFSET=3 +const.OUTPUT_NOTE_NUM_ASSETS_OFFSET=4 +const.OUTPUT_NOTE_ASSETS_OFFSET=5 # MEMORY PROCEDURES # ================================================================================================= @@ -164,42 +170,42 @@ const.CREATED_NOTE_ASSETS_OFFSET=5 # BOOK KEEPING # ------------------------------------------------------------------------------------------------- -#! Returns the number of created notes. +#! Returns the number of output notes. #! #! Stack: [] -#! Output: [num_created_notes] -export.get_num_created_notes - push.NUM_CREATED_NOTES_PTR mem_load +#! Output: [num_output_notes] +export.get_num_output_notes + push.NUM_OUTPUT_NOTES_PTR mem_load end -#! Sets the number of created notes. +#! Sets the number of output notes. #! -#! Stack: [num_created_notes] +#! Stack: [num_output_notes] #! Output: [] -export.set_num_created_notes - push.NUM_CREATED_NOTES_PTR mem_store +export.set_num_output_notes + push.NUM_OUTPUT_NOTES_PTR mem_store end -#! Returns a pointer to the consumed note being executed. +#! Returns a pointer to the input note being executed. #! #! Stack: [] #! Output: [note_ptr] #! #! Where: -#! - note_ptr, the memory address of the data segment for the current consumed note. -export.get_current_consumed_note_ptr - push.CURRENT_CONSUMED_NOTE_PTR mem_load +#! - note_ptr, the memory address of the data segment for the current input note. +export.get_current_input_note_ptr + push.CURRENT_INPUT_NOTE_PTR mem_load end -#! Sets the current consumed note pointer to the consumed note being executed. +#! Sets the current input note pointer to the input note being executed. #! #! Stack: [note_ptr] #! Output: [] #! #! Where: -#! - note_ptr, the new memory address of the data segment for the consumed note. -export.set_current_consumed_note_ptr - push.CURRENT_CONSUMED_NOTE_PTR mem_store +#! - note_ptr, the new memory address of the data segment for the input note. +export.set_current_input_note_ptr + push.CURRENT_INPUT_NOTE_PTR mem_store end #! Returns a pointer to the memory address at which the input vault root is stored @@ -227,12 +233,13 @@ end #! Sets the input vault root. #! #! Stack: [INPUT_VAULT_ROOT] -#! Output: [] +#! Output: [INPUT_VAULT_ROOT] #! #! Where: #! - INPUT_VAULT_ROOT is the input vault root. export.set_input_vault_root - push.INPUT_VAULT_ROOT_PTR mem_storew dropw + push.INPUT_VAULT_ROOT_PTR + mem_storew end #! Returns a pointer to the memory address at which the output vault root is stored. @@ -260,12 +267,13 @@ end #! Sets the output vault root. #! #! Stack: [OUTPUT_VAULT_ROOT] -#! Output: [] +#! Output: [OUTPUT_VAULT_ROOT] #! #! Where: #! - OUTPUT_VAULT_ROOT is the output vault root. export.set_output_vault_root - push.OUTPUT_VAULT_ROOT_PTR mem_storew dropw + push.OUTPUT_VAULT_ROOT_PTR + mem_storew end @@ -275,12 +283,13 @@ end #! Saves the hash of the reference block to memory. #! #! Stack: [BLOCK_HASH] -#! Output: [] +#! Output: [BLOCK_HASH] #! #! Where: #! - BLOCK_HASH, reference block for the transaction execution. export.set_block_hash - push.BLK_HASH_PTR mem_storew dropw + push.BLK_HASH_PTR + mem_storew end #! Returns the block hash of the reference block. @@ -318,12 +327,13 @@ end #! Sets the initial account hash. #! #! Stack: [INIT_ACCT_HASH] -#! Output: [] +#! Output: [INIT_ACCT_HASH] #! #! Where: #! - INIT_ACCT_HASH is the initial account hash. export.set_init_acct_hash - push.INIT_ACCT_HASH_PTR mem_storew dropw + push.INIT_ACCT_HASH_PTR + mem_storew end #! Returns the initial account hash. @@ -353,12 +363,13 @@ end #! Sets the input notes' commitment. #! #! Stack: [INPUT_NOTES_COMMITMENT] -#! Output: [] +#! Output: [INPUT_NOTES_COMMITMENT] #! #! Where: #! - INPUT_NOTES_COMMITMENT is the notes' commitment. export.set_nullifier_commitment - push.INPUT_NOTES_COMMITMENT_PTR mem_storew dropw + push.INPUT_NOTES_COMMITMENT_PTR + mem_storew end #! Returns the initial account nonce. @@ -397,12 +408,13 @@ end #! Sets the transaction script root. #! #! Stack: [TX_SCRIPT_ROOT] -#! Output: [] +#! Output: [TX_SCRIPT_ROOT] #! #! Where: #! - TX_SCRIPT_ROOT is the transaction script root. export.set_tx_script_root - push.TX_SCRIPT_ROOT_PTR mem_storew dropw + push.TX_SCRIPT_ROOT_PTR + mem_storew end # BLOCK DATA @@ -533,12 +545,13 @@ end #! Sets the note root of the last known block. #! #! Stack: [NOTE_ROOT] -#! Output: [] +#! Output: [NOTE_ROOT] #! #! Where: #! - NOTE_ROOT is the note root of the last known block. export.set_note_root - push.NOTE_ROOT_PTR mem_storew dropw + push.NOTE_ROOT_PTR + mem_storew end # CHAIN DATA @@ -637,48 +650,83 @@ export.set_acct_nonce drop movup.3 push.ACCT_ID_AND_NONCE_PTR mem_storew dropw end -#! Sets the code root of the account. +#! Sets the number of procedures contained in the account code. #! -#! Stack: [CODE_ROOT] +#! Stack: [num_procedures] #! Output: [] #! #! Where: -#! - CODE_ROOT is the code root to be set. -export.set_acct_code_root - push.ACCT_CODE_ROOT_PTR mem_storew dropw +#! - num_procedures is the number of procedures contained in the account code. +export.set_num_account_procedures + push.NUM_ACCT_PROCEDURES_PTR mem_store end -#! Returns the code root of the account. +#! Returns the number of procedures contained in the account code. #! #! Stack: [] -#! Output: [CODE_ROOT] +#! Output: [num_procedures] #! #! Where: -#! - CODE_ROOT is the code root of the account. -export.get_acct_code_root - padw push.ACCT_CODE_ROOT_PTR mem_loadw +#! - num_procedures is the number of procedures contained in the account code. +export.get_num_account_procedures + push.NUM_ACCT_PROCEDURES_PTR mem_load end -#! Stores the new account code root in memory. +#! Returns the account procedures section offset. #! -#! Stack: [CODE_ROOT] -#! Output: [] +#! Stack: [] +#! Output: [section_offset] +#! +#! Where: +#! - section_offset is the account procedures section offset. +export.get_account_procedures_section_offset + push.ACCT_PROCEDURES_SECTION_OFFSET +end + +#! Sets the code commitment of the account. +#! +#! Stack: [CODE_COMMITMENT] +#! Output: [CODE_COMMITMENT] #! #! Where: -#! - CODE_ROOT is the new account code root. -export.set_new_acct_code_root - push.ACCT_NEW_CODE_ROOT_PTR mem_storew dropw +#! - CODE_COMMITMENT is the code commitment to be set. +export.set_acct_code_commitment + push.ACCT_CODE_COMMITMENT_PTR + mem_storew +end + +#! Returns the code commitment of the account. +#! +#! Stack: [] +#! Output: [CODE_COMMITMENT] +#! +#! Where: +#! - CODE_COMMITMENT is the code commitment of the account. +export.get_acct_code_commitment + padw push.ACCT_CODE_COMMITMENT_PTR mem_loadw +end + +#! Stores the new account code commitment in memory. +#! +#! Stack: [CODE_COMMITMENT] +#! Output: [CODE_COMMITMENT] +#! +#! Where: +#! - CODE_COMMITMENT is the new account code commitment. +export.set_new_acct_code_commitment + push.ACCT_NEW_CODE_COMMITMENT_PTR + mem_storew end -#! Returns the new account code root. +#! Returns the new account code commitment. #! #! Stack: [] -#! Output: [CODE_ROOT] +#! Output: [CODE_COMMITMENT] #! #! Where: -#! - CODE_ROOT is the new account code root. -export.get_new_acct_code_root - padw push.ACCT_NEW_CODE_ROOT_PTR mem_loadw +#! - CODE_COMMITMENT is the new account code commitment. +export.get_new_acct_code_commitment + padw push.ACCT_NEW_CODE_COMMITMENT_PTR mem_loadw end #! Returns the account storage root. @@ -695,12 +743,13 @@ end #! Sets the account storage root. #! #! Stack: [STORAGE_ROOT] -#! Output: [] +#! Output: [STORAGE_ROOT] #! #! Where: #! - STORAGE_ROOT is the account storage root. export.set_acct_storage_root - push.ACCT_STORAGE_ROOT_PTR mem_storew dropw + push.ACCT_STORAGE_ROOT_PTR + mem_storew end #! Returns a pointer to the memory address at which the account vault root is stored. @@ -728,12 +777,13 @@ end #! Sets the account vault root. #! #! Stack: [ACCT_VAULT_ROOT] -#! Output: [] +#! Output: [ACCT_VAULT_ROOT] #! #! Where: #! - ACCT_VAULT_ROOT is the account vault root to be set. export.set_acct_vault_root - push.ACCT_VAULT_ROOT_PTR mem_storew dropw + push.ACCT_VAULT_ROOT_PTR + mem_storew end #! Returns a pointer to the memory address at which the account storage slot type data begins. @@ -786,53 +836,53 @@ export.get_acct_storage_slot_type_data # => [slot_type_info] end -# CONSUMED NOTES +# INPUT NOTES # ------------------------------------------------------------------------------------------------- -#! Gets the total number of consumed notes in the transaction. +#! Gets the total number of input notes in the transaction. #! #! Stack: [] -#! Output: [num_consumed_notes] +#! Output: [num_input_notes] #! #! Where: -#! - num_consumed_notes is the total number of consumed notes in the transaction. -export.get_total_num_consumed_notes - push.CONSUMED_NOTE_NUM_PTR mem_load +#! - num_input_notes is the total number of input notes in the transaction. +export.get_num_input_notes + push.NUM_INPUT_NOTES_PTR mem_load end -#! Sets the total number of consumed notes in the transaction. +#! Sets the total number of input notes in the transaction. #! -#! Stack: [num_consumed_notes] +#! Stack: [num_input_notes] #! Output: [] #! #! Where: -#! - num_consumed_notes is the total number of consumed notes in the transaction. -export.set_total_num_consumed_notes - push.CONSUMED_NOTE_NUM_PTR mem_store +#! - num_input_notes is the total number of input notes in the transaction. +export.set_num_input_notes + push.NUM_INPUT_NOTES_PTR mem_store end -#! Computes a pointer to the memory address at which the data associated with a consumed note with +#! Computes a pointer to the memory address at which the data associated with a input note with #! index `idx` is stored. #! #! Stack: [idx] #! Output: [note_ptr] #! #! Where: -#! - idx, the index of the consumed note. -#! - note_ptr, the memory address of the data segment for the consumed note with idx. -export.get_consumed_note_ptr - exec.constants::get_note_mem_size mul push.CONSUMED_NOTE_DATA_SECTION_OFFSET add +#! - idx, the index of the input note. +#! - note_ptr, the memory address of the data segment for the input note with `idx`. +export.get_input_note_ptr + exec.constants::get_note_mem_size mul push.INPUT_NOTE_DATA_SECTION_OFFSET add end -#! Set the note id of the consumed note. +#! Set the note id of the input note. #! #! Stack: [note_ptr, NOTE_ID] #! Output: [NOTE_ID] #! #! Where: -#! - note_ptr, the consumed note's the memory address. +#! - note_ptr, the input note's the memory address. #! - NOTE_ID, the note's id. -export.set_consumed_note_id +export.set_input_note_id mem_storew end @@ -843,89 +893,89 @@ end #! Output: [nullifier_ptr] #! #! Where: -#! - idx, the index of the consumed note. +#! - idx, the index of the input note. #! - nullifier_ptr, the memory address of the nullifier for note idx. -export.get_consumed_note_nullifier_ptr - push.CONSUMED_NOTE_SECTION_OFFSET.1 add add +export.get_input_note_nullifier_ptr + push.INPUT_NOTE_SECTION_OFFSET.1 add add end -#! Returns the nullifier of a consumed note with `idx`. +#! Returns the nullifier of a input note with `idx`. #! #! Stack: [idx] #! Output: [nullifier] #! #! Where: -#! - idx, the index of the consumed note. -#! - nullifier, the nullifier of the consumed note. -export.get_consumed_note_nullifier - padw movup.4 push.CONSUMED_NOTE_SECTION_OFFSET.1 add add mem_loadw +#! - idx, the index of the input note. +#! - nullifier, the nullifier of the input note. +export.get_input_note_nullifier + padw movup.4 push.INPUT_NOTE_SECTION_OFFSET.1 add add mem_loadw end -#! Returns a pointer to the start of the consumed note core data segment for the note located at +#! Returns a pointer to the start of the input note core data segment for the note located at #! the specified memory address. #! #! Stack: [note_ptr] #! Output: [note_data_ptr] #! #! Where: -#! - note_ptr, the memory address at which the consumed note data begins. -#! - note_data_ptr, the memory address at which the consumed note core data begins. -export.get_consumed_note_core_ptr - push.CONSUMED_NOTE_CORE_DATA_OFFSET add +#! - note_ptr, the memory address at which the input note data begins. +#! - note_data_ptr, the memory address at which the input note core data begins. +export.get_input_note_core_ptr + push.INPUT_NOTE_CORE_DATA_OFFSET add end -#! Returns the script root of a consumed note located at the specified memory address. +#! Returns the script root of a input note located at the specified memory address. #! #! Stack: [note_ptr] #! Output: [SCRIPT_HASH] #! #! Where: -#! - note_ptr, the memory address at which the consumed note data begins. -#! - SCRIPT_HASH, the script root of the consumed note. -export.get_consumed_note_script_root +#! - note_ptr, the memory address at which the input note data begins. +#! - SCRIPT_HASH, the script root of the input note. +export.get_input_note_script_root padw - movup.4 push.CONSUMED_NOTE_SCRIPT_ROOT_OFFSET add + movup.4 push.INPUT_NOTE_SCRIPT_ROOT_OFFSET add mem_loadw end -#! Returns the inputs hash of a consumed note located at the specified memory address. +#! Returns the inputs hash of a input note located at the specified memory address. #! #! Stack: [note_ptr] #! Output: [INPUTS_HASH] #! #! Where: -#! - note_ptr, the memory address at which the consumed note data begins. -#! - INPUTS_HASH, the inputs hash of the consumed note. -export.get_consumed_note_inputs_hash +#! - note_ptr, the memory address at which the input note data begins. +#! - INPUTS_HASH, the inputs hash of the input note. +export.get_input_note_inputs_hash padw - movup.4 push.CONSUMED_NOTE_INPUTS_HASH_OFFSET add + movup.4 push.INPUT_NOTE_INPUTS_HASH_OFFSET add mem_loadw end -#! Returns the metadata of a consumed note located at the specified memory address. +#! Returns the metadata of a input note located at the specified memory address. #! #! Stack: [note_ptr] #! Output: [METADATA] #! #! Where: -#! - note_ptr, the memory address at which the consumed note data begins. -#! - METADATA, the metadata of the consumed note. -export.get_consumed_note_metadata +#! - note_ptr, the memory address at which the input note data begins. +#! - METADATA, the metadata of the input note. +export.get_input_note_metadata padw - movup.4 push.CONSUMED_NOTE_METADATA_OFFSET add + movup.4 push.INPUT_NOTE_METADATA_OFFSET add mem_loadw end -#! Sets the metadata for a consumed note located at the specified memory address. +#! Sets the metadata for a input note located at the specified memory address. #! #! Stack: [note_ptr, NOTE_METADATA] #! Output: [NOTE_METADATA] #! #! Where: -#! - note_ptr, the memory address at which the consumed note data begins. -#! - NOTE_METADATA, the metadata of the consumed note. -export.set_consumed_note_metadata - push.CONSUMED_NOTE_METADATA_OFFSET add +#! - note_ptr, the memory address at which the input note data begins. +#! - NOTE_METADATA, the metadata of the input note. +export.set_input_note_metadata + push.INPUT_NOTE_METADATA_OFFSET add mem_storew end @@ -937,188 +987,190 @@ end #! Where: #! - note_ptr, the start memory address of the note. #! - NOTE_ARGS, the note's args. -export.get_consumed_note_args +export.get_input_note_args padw - movup.4 push.CONSUMED_NOTE_ARGS_OFFSET add + movup.4 push.INPUT_NOTE_ARGS_OFFSET add mem_loadw end -#! Sets the note args for a consumed note located at the specified memory address. +#! Sets the note args for a input note located at the specified memory address. #! #! Stack: [note_ptr, NOTE_ARGS] -#! Output: [] +#! Output: [NOTE_ARGS] #! #! Where: -#! - note_ptr is the memory address at which the consumed note data begins. -#! - NOTE_ARGS are optional note args of the consumed note. -export.set_consumed_note_args - push.CONSUMED_NOTE_ARGS_OFFSET add - mem_storew dropw +#! - note_ptr is the memory address at which the input note data begins. +#! - NOTE_ARGS are optional note args of the input note. +export.set_input_note_args + push.INPUT_NOTE_ARGS_OFFSET add + mem_storew end -#! Returns the number of assets in the consumed note located at the specified memory address. +#! Returns the number of assets in the input note located at the specified memory address. #! #! Stack: [note_ptr] #! Output: [num_assets] #! #! Where: -#! - note_ptr is the memory address at which the consumed note data begins. -#! - num_assets is the number of assets in the consumed note. -export.get_consumed_note_num_assets - push.CONSUMED_NOTE_NUM_ASSETS_OFFSET add +#! - note_ptr is the memory address at which the input note data begins. +#! - num_assets is the number of assets in the input note. +export.get_input_note_num_assets + push.INPUT_NOTE_NUM_ASSETS_OFFSET add mem_load end -#! Sets the number of assets for a consumed note located at the specified memory address. +#! Sets the number of assets for a input note located at the specified memory address. #! #! Stack: [note_ptr, num_assets] #! Output: [] #! #! Where: -#! - note_ptr is the memory address at which the consumed note data begins. -#! - num_assets is the number of assets in the consumed note. -export.set_consumed_note_num_assets - push.CONSUMED_NOTE_NUM_ASSETS_OFFSET add +#! - note_ptr is the memory address at which the input note data begins. +#! - num_assets is the number of assets in the input note. +export.set_input_note_num_assets + push.INPUT_NOTE_NUM_ASSETS_OFFSET add mem_store end -#! Returns a pointer to the start of the assets segment for the consumed note located at +#! Returns a pointer to the start of the assets segment for the input note located at #! the specified memory address. #! #! Stack: [note_ptr] #! Output: [assets_ptr] #! #! Where: -#! - note_ptr is the memory address at which the consumed note data begins. -#! - assets_ptr is the memory address at which the assets segment for the consumed note begins. -export.get_consumed_note_assets_ptr - push.CONSUMED_NOTE_ASSETS_OFFSET add +#! - note_ptr is the memory address at which the input note data begins. +#! - assets_ptr is the memory address at which the assets segment for the input note begins. +export.get_input_note_assets_ptr + push.INPUT_NOTE_ASSETS_OFFSET add end -#! Returns the assets hash for the consumed note located at the specified memory address. +#! Returns the assets hash for the input note located at the specified memory address. #! #! Stack: [note_ptr] #! Output: [ASSET_HASH] #! #! Where: -#! - note_ptr is the memory address at which the consumed note data begins. -#! - ASSET_HASH, sequential hash of the padded assets of a consumed note. -export.get_consumed_note_assets_hash +#! - note_ptr is the memory address at which the input note data begins. +#! - ASSET_HASH, sequential hash of the padded assets of a input note. +export.get_input_note_assets_hash padw - movup.4 push.CONSUMED_NOTE_ASSETS_HASH_OFFSET add + movup.4 push.INPUT_NOTE_ASSETS_HASH_OFFSET add mem_loadw end -#! Returns the serial number for the consumed note located at the specified memory address. +#! Returns the serial number for the input note located at the specified memory address. #! #! Stack: [note_ptr] #! Output: [SERIAL_NUMBER] #! #! Where: -#! - note_ptr is the memory address at which the consumed note data begins. +#! - note_ptr is the memory address at which the input note data begins. #! - SERIAL_NUMBER, the input note's serial number. -export.get_consumed_note_serial_num +export.get_input_note_serial_num padw - movup.4 push.CONSUMED_NOTE_SERIAL_NUM_OFFSET add + movup.4 push.INPUT_NOTE_SERIAL_NUM_OFFSET add mem_loadw end -#! Returns the sender for the consumed note located at the specified memory address. +#! Returns the sender for the input note located at the specified memory address. #! #! Stack: [note_ptr] #! Output: [sender] #! #! Where: -#! - note_ptr is the memory address at which the consumed note data begins. -#! - sender is the sender for the consumed note. -export.get_consumed_note_sender +#! - note_ptr is the memory address at which the input note data begins. +#! - sender is the sender for the input note. +export.get_input_note_sender padw - movup.4 push.CONSUMED_NOTE_METADATA_OFFSET add + movup.4 push.INPUT_NOTE_METADATA_OFFSET add mem_loadw - # => [0, 0, sender, tag] + # => [aux, encoded_type_and_ex_hint, sender, tag] drop drop swap drop # => [sender] end -# CREATED NOTES +# OUTPUT NOTES # ------------------------------------------------------------------------------------------------- -#! Returns the offset of the created note data segment. +#! Returns the offset of the output note data segment. #! #! Stack: [] #! Output: [offset] #! #! Where: -#! - offset is the offset of the created note data segment. -export.get_created_note_data_offset - push.CREATED_NOTE_SECTION_OFFSET +#! - offset is the offset of the output note data segment. +export.get_output_note_data_offset + push.OUTPUT_NOTE_SECTION_OFFSET end -#! Computes a pointer to the memory address at which the data associated with a created note with -#! index i is stored. +#! Computes a pointer to the memory address at which the data associated with a output note with +#! index `i` is stored. #! #! Stack: [i] #! Output: [ptr] #! #! Where: -#! - i is the index of the created note. -#! - ptr is the memory address of the data segment for created note i. -export.get_created_note_ptr - exec.constants::get_note_mem_size mul push.CREATED_NOTE_SECTION_OFFSET add +#! - i is the index of the output note. +#! - ptr is the memory address of the data segment for output note i. +export.get_output_note_ptr + exec.constants::get_note_mem_size mul push.OUTPUT_NOTE_SECTION_OFFSET add end -#! Returns the created note recipient +#! Returns the output note recipient #! -#! Stack: [created_note_data_ptr] +#! Stack: [output_note_data_ptr] #! Output: [R] #! #! Where: -#! - created_note_data_ptr is the memory address at which the created note data begins. -#! - R is the recipient of the created note. -export.get_created_note_recipient +#! - output_note_data_ptr is the memory address at which the output note data begins. +#! - R is the recipient of the output note. +export.get_output_note_recipient padw - movup.4 push.CREATED_NOTE_RECIPIENT_OFFSET add + movup.4 push.OUTPUT_NOTE_RECIPIENT_OFFSET add mem_loadw end -#! Sets the created note's recipient +#! Sets the output note's recipient #! #! Stack: [note_ptr, RECIPIENT] -#! Output: [] +#! Output: [RECIPIENT] #! #! Where: #! - recipient is the recipient of the note -#! - note_ptr is the memory address at which the created note data begins. -export.set_created_note_recipient - push.CREATED_NOTE_RECIPIENT_OFFSET add mem_storew dropw +#! - note_ptr is the memory address at which the output note data begins. +export.set_output_note_recipient + push.OUTPUT_NOTE_RECIPIENT_OFFSET add + mem_storew end -#! Sets the created note's metadata +#! Sets the output note's metadata #! #! Stack: [note_ptr, METADATA] -#! Output: [] +#! Output: [METADATA] #! #! Where: #! - METADATA is the note metadata -#! - note_ptr is the memory address at which the created note data begins. -export.set_created_note_metadata - push.CREATED_NOTE_METADATA_OFFSET add mem_storew dropw +#! - note_ptr is the memory address at which the output note data begins. +export.set_output_note_metadata + push.OUTPUT_NOTE_METADATA_OFFSET add + mem_storew end -#! Returns the number of assets in the created note +#! Returns the number of assets in the output note #! #! Stack: [note_ptr] #! Output: [num_assets] #! #! Where: -#! - note_ptr is a pointer to the memory address at which the created note is stored. -#! - num_assets is the number of assets in the created note. -export.get_created_note_num_assets - push.CREATED_NOTE_NUM_ASSETS_OFFSET add mem_load +#! - note_ptr is a pointer to the memory address at which the output note is stored. +#! - num_assets is the number of assets in the output note. +export.get_output_note_num_assets + push.OUTPUT_NOTE_NUM_ASSETS_OFFSET add mem_load end -#! Sets the number of assets in the created note +#! Sets the number of assets in the output note #! #! Stack: [note_ptr, num_assets] #! Output: [] @@ -1126,10 +1178,10 @@ end #! Panics: if the number of assets exceeds the maximum allowed number of assets per note. #! #! Where: -#! - note_ptr is the memory address at which the created note data begins. -#! - num_assets is the number of assets in the created note. -export.set_created_note_num_assets - push.CREATED_NOTE_NUM_ASSETS_OFFSET add +#! - note_ptr is the memory address at which the output note data begins. +#! - num_assets is the number of assets in the output note. +export.set_output_note_num_assets + push.OUTPUT_NOTE_NUM_ASSETS_OFFSET add # => [note_ptr + offset, num_assets] # check note number of assets limit @@ -1138,26 +1190,27 @@ export.set_created_note_num_assets mem_store end -#! Returns a pointer to the created note asset data +#! Returns a pointer to the output note asset data #! -#! Stack: [created_note_data_ptr] +#! Stack: [output_note_data_ptr] #! Output: [asset_data_ptr] #! #! Where: -#! - created_note_data_ptr is the memory address at which the created note data begins. -#! - asset_data_ptr is the memory address at which the created note asset data begins. -export.get_created_note_asset_data_ptr - push.CREATED_NOTE_ASSETS_OFFSET add +#! - output_note_data_ptr is the memory address at which the output note data begins. +#! - asset_data_ptr is the memory address at which the output note asset data begins. +export.get_output_note_asset_data_ptr + push.OUTPUT_NOTE_ASSETS_OFFSET add end -#! Sets the created note assets hash. +#! Sets the output note assets hash. #! -#! Stack: [created_note_data_ptr, ASSET_HASH] -#! Output: [] +#! Stack: [output_note_data_ptr, ASSET_HASH] +#! Output: [ASSET_HASH] #! #! Where: -#! - created_note_data_ptr is the memory address at which the created note data begins. -#! - ASSET_HASH, sequential hash of the padded assets of a created note. -export.set_created_note_assets_hash - push.CREATED_NOTE_ASSETS_HASH_OFFSET add mem_storew +#! - output_note_data_ptr is the memory address at which the output note data begins. +#! - ASSET_HASH, sequential hash of the padded assets of an output note. +export.set_output_note_assets_hash + push.OUTPUT_NOTE_ASSETS_HASH_OFFSET add + mem_storew end diff --git a/miden-lib/asm/miden/kernels/tx/note.masm b/miden-lib/asm/kernels/transaction/lib/note.masm similarity index 78% rename from miden-lib/asm/miden/kernels/tx/note.masm rename to miden-lib/asm/kernels/transaction/lib/note.masm index 52caffdea..6e279dc0c 100644 --- a/miden-lib/asm/miden/kernels/tx/note.masm +++ b/miden-lib/asm/kernels/transaction/lib/note.masm @@ -1,7 +1,7 @@ use.std::crypto::hashes::native -use.miden::kernels::tx::constants -use.miden::kernels::tx::memory +use.kernel::constants +use.kernel::memory # ERRORS # ================================================================================================= @@ -36,8 +36,8 @@ const.OUTPUT_NOTE_HASHING_MEM_DIFF=510 #! #! - sender is the sender of the note currently being processed. export.get_sender - # get the current consumed note pointer - exec.memory::get_current_consumed_note_ptr + # get the current input note pointer + exec.memory::get_current_input_note_ptr # => [ptr] # assert the pointer is not zero - this would suggest the procedure has been called from an @@ -46,21 +46,21 @@ export.get_sender # => [ptr] # get the sender from the note pointer - exec.memory::get_consumed_note_sender + exec.memory::get_input_note_sender # => [sender] end -#! Returns the number of assets and vault hash of the note currently being processed. Panics if a -#! note is not being processed. +#! Returns the number of assets and the assets hash of the note currently being processed. Panics +#! if a note is not being processed. #! #! Inputs: [] -#! Outputs: [VAULT_HASH, num_assets] +#! Outputs: [ASSETS_HASH, num_assets] #! #! - num_assets is the number of assets in the note currently being processed. -#! - VAULT_HASH is the vault hash of the note currently being processed. -export.get_vault_info - # get the current consumed note pointer - exec.memory::get_current_consumed_note_ptr +#! - ASSETS_HASH is the vault hash of the note currently being processed. +export.get_assets_info + # get the current input note pointer + exec.memory::get_current_input_note_ptr # => [ptr] # assert the pointer is not zero - this would suggest the procedure has been called from an @@ -69,12 +69,12 @@ export.get_vault_info # => [ptr] # get the number of assets in the note - dup exec.memory::get_consumed_note_num_assets + dup exec.memory::get_input_note_num_assets # => [num_assets, ptr] - # get the vault hash from the note pointer - swap exec.memory::get_consumed_note_assets_hash - # => [VAULT_HASH, num_assets] + # get the assets hash from the note pointer + swap exec.memory::get_input_note_assets_hash + # => [ASSETS_HASH, num_assets] end #! Returns the commitment to the note's inputs. @@ -87,7 +87,7 @@ end #! Where: #! - NOTE_INPUTS_HASH is the note inputs hash of the note currently being processed. export.get_note_inputs_hash - exec.memory::get_current_consumed_note_ptr + exec.memory::get_current_input_note_ptr # => [ptr] # The kernel memory is initialized by prologue::process_input_notes_data, and reset by @@ -96,39 +96,39 @@ export.get_note_inputs_hash dup neq.0 assert.err=ERR_NOTE_INVALID_INPUTS # => [ptr] - exec.memory::get_consumed_note_inputs_hash + exec.memory::get_input_note_inputs_hash # => [NOTE_INPUTS_HASH] end -#! Increment current consumed note pointer to the next note and returns the pointer value. +#! Move the current input note pointer to the next note and returns the pointer value. #! #! Inputs: [] -#! Outputs: [current_consumed_note_ptr] +#! Outputs: [current_input_note_ptr] #! #! Where: -#! - current_consumed_note_ptr is the pointer to the next note to be processed. -export.increment_current_consumed_note_ptr - # get the current consumed note pointer - exec.memory::get_current_consumed_note_ptr - # => [orig_consumed_note_ptr] +#! - current_input_note_ptr is the pointer to the next note to be processed. +export.increment_current_input_note_ptr + # get the current input note pointer + exec.memory::get_current_input_note_ptr + # => [orig_input_note_ptr] # increment the pointer exec.constants::get_note_mem_size add - # => [current_consumed_note_ptr] + # => [current_input_note_ptr] - # set the current consumed note pointer to the incremented value - dup exec.memory::set_current_consumed_note_ptr - # => [current_consumed_note_ptr] + # set the current input note pointer to the incremented value + dup exec.memory::set_current_input_note_ptr + # => [current_input_note_ptr] end -#! Sets the current consumed note pointer to 0. This should be called after all consumed notes have +#! Sets the current input note pointer to 0. This should be called after all input notes have #! been processed. #! #! Inputs: [] #! Outputs: [] export.note_processing_teardown - # set the current consumed note pointer to 0 - push.0 exec.memory::set_current_consumed_note_ptr + # set the current input note pointer to 0 + push.0 exec.memory::set_current_input_note_ptr # => [] end @@ -143,13 +143,13 @@ end #! - NOTE_SCRIPT_ROOT, the note's script root. #! - NOTE_ARGS, the note's arguments. export.prepare_note - exec.memory::get_current_consumed_note_ptr + exec.memory::get_current_input_note_ptr # => [note_ptr] - dup exec.memory::get_consumed_note_args movup.4 + dup exec.memory::get_input_note_args movup.4 # => [note_ptr, NOTE_ARGS] - exec.memory::get_consumed_note_script_root + exec.memory::get_input_note_script_root # => [NOTE_SCRIPT_ROOT, NOTE_ARGS] end @@ -169,7 +169,7 @@ end #! - ASSETS_HASH is the hash of the assets of the output note located at note_data_ptr. proc.compute_output_note_assets_hash # duplicate note pointer and fetch num_assets - dup dup exec.memory::get_created_note_num_assets + dup dup exec.memory::get_output_note_num_assets # => [num_assets, note_data_ptr, note_data_ptr] # calculate the number of pairs of assets (takes ceiling if we have an odd number) @@ -183,7 +183,7 @@ proc.compute_output_note_assets_hash # => [asset_counter, num_asset_pairs, note_data_ptr, note_data_ptr] # prepare address and stack for reading assets - movup.2 exec.memory::get_created_note_asset_data_ptr padw padw padw + movup.2 exec.memory::get_output_note_asset_data_ptr padw padw padw # => [PAD, PAD, PAD, asset_data_ptr, asset_counter, num_asset_pairs, note_data_ptr] # check if we should loop @@ -215,7 +215,7 @@ proc.compute_output_note_assets_hash # => [ASSETS_HASH, note_data_ptr] # save vault hash to memory - movup.4 exec.memory::set_created_note_assets_hash + movup.4 exec.memory::set_output_note_assets_hash # => [] end @@ -237,15 +237,15 @@ proc.compute_output_note_id padw # insert output note recipient into the first four elements of the hasher rate - dup.4 exec.memory::get_created_note_recipient + dup.4 exec.memory::get_output_note_recipient # populate the last four elements of the hasher rate with the output note's asset hash dup.8 exec.compute_output_note_assets_hash - # compute output note hash and extract digest + # compute output note commitment and extract digest hperm exec.native::state_to_digest - # save output note hash to memory + # save the output notes commitment to memory movup.4 mem_storew end @@ -255,18 +255,18 @@ end #! Stack: [] #! Output: [OUTPUT_NOTES_COMMITMENT] #! -#! - OUTPUT_NOTES_COMMITMENT is the commitment to the notes created by the transaction. +#! - OUTPUT_NOTES_COMMITMENT is the commitment to the notes output by the transaction. export.compute_output_notes_commitment # get the number of output notes from memory - exec.memory::get_num_created_notes + exec.memory::get_num_output_notes # => [num_notes, ...] # calculate the address at which we should stop looping - exec.memory::get_created_note_ptr + exec.memory::get_output_note_ptr # => [end_ptr, ...] # compute pointer for first address - push.0 exec.memory::get_created_note_ptr + push.0 exec.memory::get_output_note_ptr # => [first_note_ptr, end_ptr, ...] # prepare stack for hashing @@ -318,9 +318,9 @@ end #! #! - SERIAL_NUMBER is the serial number of the note currently being processed. export.get_serial_number - exec.memory::get_current_consumed_note_ptr + exec.memory::get_current_input_note_ptr # => [note_ptr, ...] - exec.memory::get_consumed_note_serial_num + exec.memory::get_input_note_serial_num # => [SERIAL_NUMBER, ...] end diff --git a/miden-lib/asm/miden/kernels/tx/prologue.masm b/miden-lib/asm/kernels/transaction/lib/prologue.masm similarity index 87% rename from miden-lib/asm/miden/kernels/tx/prologue.masm rename to miden-lib/asm/kernels/transaction/lib/prologue.masm index 0851a68d3..7a60a3e39 100644 --- a/miden-lib/asm/miden/kernels/tx/prologue.masm +++ b/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -1,12 +1,13 @@ -use.std::collections::smt +use.std::mem use.std::collections::mmr use.std::crypto::hashes::native +use.std::collections::smt -use.miden::kernels::tx::account -use.miden::kernels::tx::asset_vault -use.miden::kernels::tx::constants -use.miden::kernels::tx::memory -use.miden::kernels::tx::utils +use.kernel::account +use.kernel::asset_vault +use.kernel::constants +use.kernel::memory +use.kernel::utils # ERRORS # ================================================================================================= @@ -71,6 +72,12 @@ const.ERR_PROLOGUE_TOO_MANY_INPUT_NOTES=0x0002001E # Cannot compute matching nullifier commitment using the provided input note data const.ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH=0x0002001F +# Computed account code hash does not match recorded account code hash +const.ERR_ACCT_CODE_HASH_MISMATCH=0x0002004C + +# Number of account procedures exceeded the maximum limit of 256 +const.ERR_ACCT_TOO_MANY_PROCEDURES=0x0002004D + # PUBLIC INPUTS # ================================================================================================= @@ -85,10 +92,10 @@ const.ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH=0x0002001F #! - INITIAL_ACCOUNT_HASH, account state prior to the transaction, EMPTY_WORD for new accounts. #! - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. proc.process_global_inputs - exec.memory::set_block_hash + exec.memory::set_block_hash dropw exec.memory::set_global_acct_id - exec.memory::set_init_acct_hash - exec.memory::set_nullifier_commitment + exec.memory::set_init_acct_hash dropw + exec.memory::set_nullifier_commitment dropw end # BLOCK DATA @@ -113,14 +120,14 @@ end #! Where: #! - PREVIOUS_BLOCK_HASH, hash of the previous block. #! - CHAIN_MMR_HASH, sequential hash of the reference MMR. -#! - ACCOUNT_ROOT, rollup tree with latest account states. -#! - NULLIFIER_ROOT, epoch tree with the nullifiers of consumed notes. +#! - ACCOUNT_ROOT, root of the tree with latest account states for all accounts. +#! - NULLIFIER_ROOT, root of the tree with nullifiers of all notes that have ever been consumed. #! - TX_HASH, commitment to a set of IDs of transactions which affected accounts in the block. #! - PROOF_HASH, hash of the block's stark proof. #! - block_num, the reference block number. #! - version, current protocol version. #! - timestamp, current timestamp. -#! - NOTE_ROOT, epoch tree with created notes. +#! - NOTE_ROOT, root of the tree with all notes created in the block. proc.process_block_data exec.memory::get_block_data_ptr # => [block_data_ptr] @@ -136,7 +143,7 @@ proc.process_block_data # store the note root in memory padw adv_loadw - dupw exec.memory::set_note_root + dupw exec.memory::set_note_root dropw # => [NOTE_ROOT, DIG, block_data_ptr'] # merge the note root with the block data digest @@ -204,8 +211,7 @@ proc.ingest_acct_storage_types exec.account::get_slot_types_commitment_storage_slot exec.account::get_item # => [TYPES_COM] - push.0 drop # TODO: remove line, see miden-vm/#1122 - adv.push_mapval + adv.push_mapval push.15683 drop # FIX: wrap the decorator to ensure MAST uniqueness # => [TYPES_COM] # get the storage slot types data pointer @@ -384,6 +390,59 @@ proc.validate_new_account # => [] end +#! Validates that account procedures match account code commitment and saves procedure information +#! into memory. +#! +#! This is achieved by reading account procedure information from the advice map, saving it into +#! memory, and sequentially hashing them before comparing the final hash to the account code +#! commitment. +#! +#! Information for each saved procedure consists of the procedure's MAST root and the storage +#! offset associated with this procedure. +#! +#! Stack: [CODE_COMMITMENT] +#! Output: [] +proc.validate_account_procedures + # move procedure data from the advice map to the advice stack + adv.push_mapval push.15161 drop # FIX: wrap the decorator to ensure MAST uniqueness + + # push the number of procedures onto the operand stack before storing it in memory + adv_push.1 + # => [num_procs, CODE_COMMITMENT] + + # assert that account does not exceed allowed maximum number of procedures + dup exec.account::get_max_num_procedures lt assert.err=ERR_ACCT_TOO_MANY_PROCEDURES + # => [num_procs, CODE_COMMITMENT] + + # store number of procedures in memory + dup exec.memory::set_num_account_procedures + # => [num_procs, CODE_COMMITMENT] + + # setup acct_proc_offset and end_ptr for reading from advice stack + mul.2 exec.memory::get_account_procedures_section_offset dup movdn.2 add swap + # => [acct_proc_offset, end_ptr, CODE_COMMITMENT] + + # pad stack before reading from advice stack + padw padw padw + # => [PAD, PAD, PAD, acct_proc_offset, end_ptr, CODE_COMMITMENT] + + # read the data from advice stack to memory and hash + exec.mem::pipe_double_words_to_memory + # => [PERM, PERM, PERM, end_ptr, CODE_COMMITMENT] + + # extract the digest + exec.native::state_to_digest + # => [DIGEST, end_ptr, CODE_COMMITMENT] + + # drop end_ptr + movup.4 drop + # => [DIGEST, CODE_COMMITMENT] + + # verify hashed account procedures match account code commitment + assert_eqw.err=ERR_ACCT_CODE_HASH_MISMATCH + # => [] +end + #! Saves the account data to memory and validates it. #! #! This procedure will: @@ -396,7 +455,7 @@ end #! and the account nonce is not zero #! #! Stack: [] -#! Advice stack: [[account_id, 0, 0, account_nonce], ACCOUNT_VAULT_ROOT, ACCOUNT_STORAGE_ROOT, ACCOUNT_CODE_ROOT] +#! Advice stack: [[account_id, 0, 0, account_nonce], ACCOUNT_VAULT_ROOT, ACCOUNT_STORAGE_ROOT, ACCOUNT_CODE_COMMITMENT] #! Output: [] #! #! Where: @@ -404,7 +463,7 @@ end #! - account_nonce, account's nonce. #! - ACCOUNT_VAULT_ROOT, account's vault root. #! - ACCOUNT_STORAGE_ROOT, account's storage root. -#! - ACCOUNT_CODE_ROOT, account's code root. +#! - ACCOUNT_CODE_COMMITMENT, account's code commitment. proc.process_account_data # Copy the account data from the advice stack to memory and hash it # --------------------------------------------------------------------------------------------- @@ -440,7 +499,7 @@ proc.process_account_data # process conditional logic depending on whether the account is new or existing if.true # set the initial account hash - exec.memory::set_init_acct_hash + exec.memory::set_init_acct_hash dropw # => [] # validate the new account @@ -472,15 +531,18 @@ proc.process_account_data exec.memory::set_init_nonce # => [] - # set the new account code root to the initial account code root this is used for managing - # code root updates - exec.memory::get_acct_code_root - exec.memory::set_new_acct_code_root + # set the new account code commitment to the initial account code root this is used for managing + # code commitment updates + exec.memory::get_acct_code_commitment + exec.memory::set_new_acct_code_commitment + + # validates and stores account procedures in memory. + exec.validate_account_procedures # => [] # copy the initial account vault hash to the input vault hash to support transaction asset # invariant checking - exec.memory::get_acct_vault_root exec.memory::set_input_vault_root + exec.memory::get_acct_vault_root exec.memory::set_input_vault_root dropw # => [] end @@ -578,7 +640,7 @@ end #! - ASSETS_HASH, sequential hash of the padded note's assets. #! - NULLIFIER result of `hash(SERIAL_NUMBER || SCRIPT_ROOT || INPUTS_HASH || ASSETS_HASH)`. proc.process_input_note_details - exec.memory::get_consumed_note_core_ptr + exec.memory::get_input_note_core_ptr # => [note_data_ptr] # read input note's data and compute its digest. See `Advice stack` above for details. @@ -615,10 +677,10 @@ end #! - NOTE_ARGS, user arguments passed to the note. #! - NOTE_METADATA, note's metadata. proc.process_note_args_and_metadata - padw adv_loadw dup.4 exec.memory::set_consumed_note_args + padw adv_loadw dup.4 exec.memory::set_input_note_args dropw # => [note_ptr] - padw adv_loadw movup.4 exec.memory::set_consumed_note_metadata + padw adv_loadw movup.4 exec.memory::set_input_note_metadata # => [NOTE_METADATA] end @@ -645,7 +707,7 @@ proc.process_note_assets dup exec.constants::get_max_assets_per_note lte assert.err=ERR_PROLOGUE_NOTE_TOO_MANY_ASSETS # => [assets_count, note_ptr] - dup dup.2 exec.memory::set_consumed_note_num_assets + dup dup.2 exec.memory::set_input_note_num_assets # => [assets_count, note_ptr] # round up the number of assets, to the its padded length @@ -667,7 +729,7 @@ proc.process_note_assets push.0 movup.2 # => [note_ptr, counter, rounded_num_assets] - dup exec.memory::get_consumed_note_assets_ptr + dup exec.memory::get_input_note_assets_ptr # => [assets_ptr, note_ptr, counter, rounded_num_assets] padw padw padw @@ -700,7 +762,7 @@ proc.process_note_assets # => [note_ptr, ASSET_HASH_COMPUTED] # VERIFY: computed ASSET_HASH matches the provided hash - exec.memory::get_consumed_note_assets_hash + exec.memory::get_input_note_assets_hash assert_eqw.err=ERR_PROLOGUE_NOTE_CONSUMED_ASSETS_MISMATCH # => [] end @@ -719,10 +781,10 @@ proc.add_input_note_assets_to_vault exec.memory::get_input_vault_root_ptr # => [input_vault_root_ptr, note_ptr] - dup.1 exec.memory::get_consumed_note_assets_ptr + dup.1 exec.memory::get_input_note_assets_ptr # => [assets_start_ptr, input_vault_root_ptr, note_ptr] - dup movup.3 exec.memory::get_consumed_note_num_assets add swap + dup movup.3 exec.memory::get_input_note_num_assets add swap # => [assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # add input note's assets to input vault @@ -754,7 +816,7 @@ proc.add_input_note_assets_to_vault # => [] end -#! Computes the inpute note's id. +#! Computes an input note's id. #! #! Stack: [note_ptr] #! Output: [NOTE_ID] @@ -762,21 +824,21 @@ end #! Where: #! - note_ptr, memory location for the input note. #! - NOTE_ID, the note's id, i.e. `hash(RECIPIENT || ASSET_HASH)`. -proc.compute_note_id +proc.compute_input_note_id # compute SERIAL_HASH: hash(SERIAL_NUMBER || EMPTY_WORD) - dup exec.memory::get_consumed_note_serial_num padw hmerge + dup exec.memory::get_input_note_serial_num padw hmerge # => [SERIAL_HASH, note_ptr] # compute MERGE_SCRIPT: hash(SERIAL_HASH || SCRIPT_HASH) - dup.4 exec.memory::get_consumed_note_script_root hmerge + dup.4 exec.memory::get_input_note_script_root hmerge # => [MERGE_SCRIPT, note_ptr] # compute RECIPIENT: hash(MERGE_SCRIPT || INPUT_HASH) - dup.4 exec.memory::get_consumed_note_inputs_hash hmerge + dup.4 exec.memory::get_input_note_inputs_hash hmerge # => [RECIPIENT, note_ptr] # compute NOTE_ID: hash(RECIPIENT || ASSET_HASH) - movup.4 exec.memory::get_consumed_note_assets_hash hmerge + movup.4 exec.memory::get_input_note_assets_hash hmerge # => [NOTE_ID] end @@ -829,14 +891,14 @@ proc.process_input_note # note details # --------------------------------------------------------------------------------------------- - dup exec.memory::get_consumed_note_ptr dup + dup exec.memory::get_input_note_ptr dup # => [note_ptr, note_ptr, idx, HASHER_CAPACITY] exec.process_input_note_details # => [NULLIFIER, note_ptr, idx, HASHER_CAPACITY] # save NULLIFIER to memory - movup.5 exec.memory::get_consumed_note_nullifier_ptr mem_storew + movup.5 exec.memory::get_input_note_nullifier_ptr mem_storew # => [NULLIFIER, note_ptr, HASHER_CAPACITY] # note metadata & args @@ -861,11 +923,11 @@ proc.process_input_note # note id # --------------------------------------------------------------------------------------------- - dup exec.compute_note_id + dup exec.compute_input_note_id # => [NOTE_ID, note_ptr, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] # save note id to memory - movup.4 exec.memory::set_consumed_note_id + movup.4 exec.memory::set_input_note_id # => [NOTE_ID, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] # note authentication @@ -917,19 +979,21 @@ proc.process_input_notes_data # assert the number of input notes is within limits; since max number of input notes is # expected to be smaller than 2^32, we can use a more efficient u32 comparison dup - exec.constants::get_max_num_consumed_notes u32assert2.err=ERR_PROLOGUE_TOO_MANY_INPUT_NOTES + exec.constants::get_max_num_input_notes u32assert2.err=ERR_PROLOGUE_TOO_MANY_INPUT_NOTES u32lte assert.err=ERR_PROLOGUE_TOO_MANY_INPUT_NOTES # => [num_notes] # if there are input notes, load input notes data from the advice map onto the advice stack dup neq.0 if.true - exec.memory::get_input_notes_commitment adv.push_mapval dropw + exec.memory::get_input_notes_commitment + adv.push_mapval push.15787 drop # FIX: wrap the decorator to ensure MAST uniqueness + dropw end # => [num_notes] # store the number of input notes into kernel memory - dup exec.memory::set_total_num_consumed_notes + dup exec.memory::set_num_input_notes # => [num_notes] # loop over input notes and read data @@ -983,8 +1047,8 @@ proc.process_input_notes_data # set the current input note ptr to the address of the first input note push.0 - exec.memory::get_consumed_note_ptr - exec.memory::set_current_consumed_note_ptr + exec.memory::get_input_note_ptr + exec.memory::set_current_input_note_ptr # => [idx+1, num_notes] drop drop @@ -1008,7 +1072,7 @@ proc.process_tx_script_root # => [TX_SCRIPT_ROOT] # store the transaction script root in memory - exec.memory::set_tx_script_root + exec.memory::set_tx_script_root dropw # => [] end @@ -1040,7 +1104,7 @@ end #! [account_id, 0, 0, account_nonce], #! ACCOUNT_VAULT_ROOT, #! ACCOUNT_STORAGE_ROOT, -#! ACCOUNT_CODE_ROOT, +#! ACCOUNT_CODE_COMMITMENT, #! number_of_input_notes, #! TX_SCRIPT_ROOT, #! ] @@ -1057,18 +1121,18 @@ end #! - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. #! - PREVIOUS_BLOCK_HASH, hash of the previous block. #! - CHAIN_MMR_HASH, sequential hash of the reference MMR. -#! - ACCOUNT_ROOT, tree with latest account states. -#! - NULLIFIER_ROOT, epoch tree with nullifiers of consumed notes. +#! - ACCOUNT_ROOT, root of the tree with latest account states for all accounts. +#! - NULLIFIER_ROOT, root of the tree with nullifiers of all notes that have ever been consumed. #! - TX_HASH, commitment to a set of IDs of transactions which affected accounts in the block. #! - PROOF_HASH, hash of the block's stark proof. #! - block_num, the reference block number. #! - version, the current protocol version. #! - timestamp, the current timestamp. -#! - NOTE_ROOT, tree with created notes. +#! - NOTE_ROOT, root of the tree with all notes created in the block. #! - account_nonce, account's nonce. #! - ACCOUNT_VAULT_ROOT, account's vault root. #! - ACCOUNT_STORAGE_ROOT, account's storage root. -#! - ACCOUNT_CODE_ROOT, account's code root. +#! - ACCOUNT_CODE_COMMITMENT, account's code commitment. #! - number_of_input_notes, number of input notes. #! - TX_SCRIPT_ROOT, the transaction's script root. #! - MMR_PEAKS, is the MMR peak data, see process_chain_data diff --git a/miden-lib/asm/miden/kernels/tx/tx.masm b/miden-lib/asm/kernels/transaction/lib/tx.masm similarity index 80% rename from miden-lib/asm/miden/kernels/tx/tx.masm rename to miden-lib/asm/kernels/transaction/lib/tx.masm index 07227529e..19805aaa5 100644 --- a/miden-lib/asm/miden/kernels/tx/tx.masm +++ b/miden-lib/asm/kernels/transaction/lib/tx.masm @@ -1,17 +1,20 @@ -use.miden::kernels::tx::account -use.miden::kernels::tx::asset -use.miden::kernels::tx::constants -use.miden::kernels::tx::memory -use.miden::kernels::tx::note +use.kernel::account +use.kernel::asset +use.kernel::constants +use.kernel::memory +use.kernel::note # CONSTANTS # ================================================================================================= # Constants for different note types const.PUBLIC_NOTE=1 # 0b01 -const.OFFCHAIN_NOTE=2 # 0b10 +const.PRIVATE_NOTE=2 # 0b10 const.ENCRYPTED_NOTE=3 # 0b11 +# Two raised to the power of 38 (2^38), used for shifting the note type value +const.TWO_POW_38=274877906944 + # ERRORS # ================================================================================================= @@ -108,21 +111,21 @@ export.memory::get_input_notes_commitment #! COM is the output notes hash. export.note::compute_output_notes_commitment->get_output_notes_hash -#! Increments the number of created notes by one. Returns the index of the next note to be created. +#! Increments the number of output notes by one. Returns the index of the next note to be created. #! #! Inputs: [] #! Outputs: [note_idx] -proc.increment_num_created_notes - # get the current number of created notes - exec.memory::get_num_created_notes +proc.increment_num_output_notes + # get the current number of output notes + exec.memory::get_num_output_notes # => [note_idx] # assert that there is space for a new note - dup exec.constants::get_max_num_created_notes lt assert.err=ERR_TX_OUTPUT_NOTES_OVERFLOW + dup exec.constants::get_max_num_output_notes lt assert.err=ERR_TX_OUTPUT_NOTES_OVERFLOW # => [note_idx] - # increment the number of created notes - dup add.1 exec.memory::set_num_created_notes + # increment the number of output notes + dup add.1 exec.memory::set_num_output_notes # => [note_idx] end @@ -133,7 +136,7 @@ end #! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] #! Outputs: [note_ptr, note_idx] proc.add_non_fungible_asset_to_note - dup.4 exec.memory::get_created_note_asset_data_ptr + dup.4 exec.memory::get_output_note_asset_data_ptr # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] # compute the pointer at which we should stop iterating @@ -171,7 +174,7 @@ proc.add_non_fungible_asset_to_note # => [note_ptr, num_of_assets, note_idx] # increase the number of assets in the note - swap add.1 dup.1 exec.memory::set_created_note_num_assets + swap add.1 dup.1 exec.memory::set_output_note_num_assets # => [note_ptr, note_idx] end @@ -184,7 +187,7 @@ end #! Inputs: [ASSET, note_ptr, num_of_assets, note_idx] #! Outputs: [note_ptr] proc.add_fungible_asset_to_note - dup.4 exec.memory::get_created_note_asset_data_ptr + dup.4 exec.memory::get_output_note_asset_data_ptr # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx] # compute the pointer at which we should stop iterating @@ -247,18 +250,53 @@ proc.add_fungible_asset_to_note # => [note_ptr, num_of_assets, note_idx] # increase the number of assets in the note - swap add.1 dup.1 exec.memory::set_created_note_num_assets + swap add.1 dup.1 exec.memory::set_output_note_num_assets # => [note_ptr, note_idx] end +#! Builds the stack into the NOTE_METADATA word, encoding the note type and execution hint +#! into a single element. +#! +#! Inputs: [tag, aux, note_type, execution_hint] +#! Outputs: [NOTE_METADATA] +proc.build_note_metadata + # validate the note type + # NOTE: encrypted notes are currently unsupported + dup.2 push.PRIVATE_NOTE eq dup.3 push.PUBLIC_NOTE eq or assert.err=ERR_INVALID_NOTE_TYPE + # => [tag, aux, note_type, execution_hint] + + # copy data to validate the tag + dup.2 push.PUBLIC_NOTE dup.1 dup.3 + # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint] + + u32assert.err=ERR_NOTE_TAG_MUST_BE_U32 + # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint] + + # enforce the note type depending on the tag' bits + u32shr.30 push.ALL_NOTE_TYPES_ALLOWED eq cdrop assert_eq.err=ERR_NOTE_INVALID_TYPE_FOR_TAG + # => [tag, aux, note_type, execution_hint] + + # encode note_type and execution_hint into a single element + movup.3 movup.3 push.TWO_POW_38 mul add movup.2 + # => [aux, encoded_type_and_ex_hint, tag] + + # add sender account ID to metadata + exec.account::get_id + # => [sender_acct_id, aux, encoded_type_and_ex_hint, tag] + + movdn.2 + # => [NOTE_METADATA] +end + #! Creates a new note and returns the index of the note. #! -#! Inputs: [tag, aux, note_type, RECIPIENT] +#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] #! Outputs: [note_idx] #! #! - tag is the tag to be included in the note. #! - aux is the auxiliary metadata to be included in the note. #! - note_type is the type of the note. +#! - execution_hint is the execution hint of the note. #! - RECIPIENT defines spend conditions for the note. #! - note_idx is the index of the crated note. #! @@ -266,48 +304,35 @@ end #! - the note_type is not valid. #! - the note_tag is not an u32. #! - if note_tag starts with anything but 0b11 and note_type is not public. -#! - the number of created notes exceeds the maximum limit of 4096. +#! - the number of output notes exceeds the maximum limit of 4096. export.create_note - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.20983 drop # TODO: remove line, see miden-vm/#1122 emit.NOTE_BEFORE_CREATED_EVENT - # validate the note type - # NOTE: encrypted notes are currently unsupported - dup.2 push.OFFCHAIN_NOTE eq dup.3 push.PUBLIC_NOTE eq or assert.err=ERR_INVALID_NOTE_TYPE - # => [tag, aux, note_type, RECIPIENT] - - # copy data to validate the tag - dup.2 push.PUBLIC_NOTE dup.1 dup.3 - # => [tag, note_type, public_note, note_type, tag, aux, note_type, RECIPIENT] - - u32assert.err=ERR_NOTE_TAG_MUST_BE_U32 - # => [tag, note_type, public_note, note_type, tag, aux, note_type, RECIPIENT] - - # enforce the note type depending on the tag's bits - u32shr.30 push.ALL_NOTE_TYPES_ALLOWED eq cdrop assert_eq.err=ERR_NOTE_INVALID_TYPE_FOR_TAG - # => [tag, aux, note_type, RECIPIENT] + exec.build_note_metadata + # => [NOTE_METADATA, RECIPIENT] # get the index for the next note to be created and increment counter - exec.increment_num_created_notes dup movdn.8 - # => [note_idx, tag, aux, note_type, RECIPIENT, note_idx] + exec.increment_num_output_notes dup movdn.9 + # => [note_idx, NOTE_METADATA, RECIPIENT, note_idx] # get a pointer to the memory address at which the note will be stored - exec.memory::get_created_note_ptr - # => [note_ptr, tag, aux, note_type, RECIPIENT, note_idx] + exec.memory::get_output_note_ptr + # => [note_ptr, NOTE_METADATA, RECIPIENT, note_idx] - # populate the metadata - swap exec.account::get_id movup.4 movup.4 - # => [aux, note_type, sender_acct_id, tag, note_ptr, RECIPIENT, note_idx] + movdn.4 + # => [NOTE_METADATA, note_ptr, RECIPIENT, note_idx] # emit event to signal that a new note is created + push.21067 drop # TODO: remove line, see miden-vm/#1122 emit.NOTE_AFTER_CREATED_EVENT - # set the metadata for the created note - dup.4 exec.memory::set_created_note_metadata + # set the metadata for the output note + dup.4 exec.memory::set_output_note_metadata dropw # => [note_ptr, RECIPIENT, note_idx] - # set the RECIPIENT for the created note - exec.memory::set_created_note_recipient + # set the RECIPIENT for the output note + exec.memory::set_output_note_recipient dropw # => [note_idx] end @@ -326,23 +351,23 @@ end #! - the total number of ASSETs exceeds the maximum of 256. export.add_asset_to_note # check if the note exists, it must be within [0, num_of_notes] - dup exec.memory::get_num_created_notes lte assert.err=ERR_INVALID_NOTE_IDX + dup exec.memory::get_num_output_notes lte assert.err=ERR_INVALID_NOTE_IDX # => [note_idx, ASSET] # get a pointer to the memory address of the note at which the asset will be stored - dup movdn.5 exec.memory::get_created_note_ptr + dup movdn.5 exec.memory::get_output_note_ptr # => [note_ptr, ASSET, note_idx] # get current num of assets - dup exec.memory::get_created_note_num_assets movdn.5 + dup exec.memory::get_output_note_num_assets movdn.5 # => [note_ptr, ASSET, num_of_assets, note_idx] # validate the ASSET movdn.4 exec.asset::validate_asset # => [ASSET, note_ptr, num_of_assets, note_idx] - push.0 drop # TODO: remove line, see miden-vm/#1122 # emit event to signal that a new asset is going to be added to the note. + push.21169 drop # TODO: remove line, see miden-vm/#1122 emit.NOTE_BEFORE_ADD_ASSET_EVENT # => [ASSET, note_ptr] @@ -361,8 +386,8 @@ export.add_asset_to_note end # => [note_ptr, note_idx] - push.0 drop # TODO: remove line, see miden-vm/#1122 # emit event to signal that a new asset was added to the note. + push.21277 drop # TODO: remove line, see miden-vm/#1122 emit.NOTE_AFTER_ADD_ASSET_EVENT drop diff --git a/miden-lib/asm/kernels/transaction/main.masm b/miden-lib/asm/kernels/transaction/main.masm index c9b9e2698..91f5a3e2f 100644 --- a/miden-lib/asm/kernels/transaction/main.masm +++ b/miden-lib/asm/kernels/transaction/main.masm @@ -1,9 +1,9 @@ use.std::utils -use.miden::kernels::tx::epilogue -use.miden::kernels::tx::memory -use.miden::kernels::tx::note -use.miden::kernels::tx::prologue +use.kernel::epilogue +use.kernel::memory +use.kernel::note +use.kernel::prologue # TRACES # ================================================================================================= @@ -50,14 +50,14 @@ const.EPILOGUE_END=131081 #! advice provider. #! #! Stack: [BLOCK_HASH, account_id, INITIAL_ACCOUNT_HASH, INPUT_NOTES_COMMITMENT] -#! Output: [CREATED_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] +#! Output: [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] #! #! Where: #! - BLOCK_HASH, reference block for the transaction execution. #! - account_id, the account that the transaction is being executed against. #! - INITIAL_ACCOUNT_HASH, account state prior to the transaction, EMPTY_WORD for new accounts. #! - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. -#! - CREATED_NOTES_COMMITMENT, commitment to the notes created by the transaction. +#! - OUTPUT_NOTES_COMMITMENT, commitment to the notes created by the transaction. #! - FINAL_ACCOUNT_HASH, account's hash after execution the transaction. proc.main.1 # Prologue @@ -68,27 +68,27 @@ proc.main.1 exec.prologue::prepare_transaction # => [] - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.1 drop # TODO: remove line, see miden-vm/#1122 trace.PROLOGUE_END # Note Processing # --------------------------------------------------------------------------------------------- - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.2 drop # TODO: remove line, see miden-vm/#1122 trace.NOTES_PROCESSING_START - exec.memory::get_total_num_consumed_notes - # => [num_consumed_notes] + exec.memory::get_num_input_notes + # => [num_input_notes] # compute the memory location after all input notes, i.e. the exit condition - dup exec.memory::get_consumed_note_ptr loc_store.0 - # => [num_consumed_notes] + dup exec.memory::get_input_note_ptr loc_store.0 + # => [num_input_notes] eq.0 not # => [should_loop] while.true - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.4 drop # TODO: remove line, see miden-vm/#1122 trace.NOTE_EXECUTION_START # => [] @@ -103,27 +103,27 @@ proc.main.1 dropw dropw dropw dropw # => [] - exec.note::increment_current_consumed_note_ptr - # => [current_consumed_note_ptr] + exec.note::increment_current_input_note_ptr + # => [current_input_note_ptr] # loop condition, exit when the memory ptr is after all input notes loc_load.0 neq # => [should_loop] - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.5 drop # TODO: remove line, see miden-vm/#1122 trace.NOTE_EXECUTION_END end exec.note::note_processing_teardown # => [] - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.3 drop # TODO: remove line, see miden-vm/#1122 trace.NOTES_PROCESSING_END # Transaction Script Processing # --------------------------------------------------------------------------------------------- - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.6 drop # TODO: remove line, see miden-vm/#1122 trace.TX_SCRIPT_PROCESSING_START # execute the transaction script @@ -147,22 +147,22 @@ proc.main.1 # => [] end - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.7 drop # TODO: remove line, see miden-vm/#1122 trace.TX_SCRIPT_PROCESSING_END # Epilogue # --------------------------------------------------------------------------------------------- - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.8 drop # TODO: remove line, see miden-vm/#1122 trace.EPILOGUE_START # execute the transaction epilogue exec.epilogue::finalize_transaction - # => [CREATED_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] + # => [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] - push.0 drop # TODO: remove line, see miden-vm/#1122 + push.9 drop # TODO: remove line, see miden-vm/#1122 trace.EPILOGUE_END - # => [CREATED_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] + # => [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] end begin diff --git a/miden-lib/asm/miden/account.masm b/miden-lib/asm/miden/account.masm index 5020ef9a1..60709883a 100644 --- a/miden-lib/asm/miden/account.masm +++ b/miden-lib/asm/miden/account.masm @@ -143,10 +143,10 @@ end #! Sets the code of the account the transaction is being executed against. This procedure can only #! executed on regular accounts with updatable code. Otherwise, this procedure fails. #! -#! Stack: [CODE_ROOT] +#! Stack: [CODE_COMMITMENT] #! Output: [] #! -#! - CODE_ROOT is the hash of the code to set. +#! - CODE_COMMITMENT is the hash of the code to set. export.set_code syscall.set_account_code # => [0, 0, 0, 0] @@ -232,3 +232,58 @@ export.get_vault_commitment syscall.get_account_vault_commitment # => [COM] end + +# PROCEDURES COPIED FROM KERNEL (TODO: get rid of this duplication) +# ================================================================================================= + +# Given the most significant half of an account id, this mask defines the bits used to determine the account type. +const.ACCOUNT_TYPE_U32MASK=805306368 # 0b00110000_00000000_00000000_00000000 + +# Bit pattern for a fungible faucet w/ immutable code, after the account type mask has been applied. +const.FUNGIBLE_FAUCET_ACCOUNT=536870912 # 0b00100000_00000000_00000000_00000000 + +# Bit pattern for a non-fungible faucet w/ immutable code, after the account type mask has been applied. +const.NON_FUNGIBLE_FAUCET_ACCOUNT=805306368 # 0b00110000_00000000_00000000_00000000 + +#! Returns the most significant half with the account type bits masked out. +#! +#! The account type can be defined by comparing this value with the following constants: +#! +#! - REGULAR_ACCOUNT_UPDATABLE_CODE +#! - REGULAR_ACCOUNT_IMMUTABLE_CODE +#! - FUNGIBLE_FAUCET_ACCOUNT +#! - NON_FUNGIBLE_FAUCET_ACCOUNT +#! +#! Stack: [acct_id] +#! Output: [acct_type] +#! +#! - acct_id is the account id. +#! - acct_type is the account type. +proc.type + u32split swap drop push.ACCOUNT_TYPE_U32MASK u32and + # => [acct_type] +end + +#! Returns a boolean indicating whether the account is a fungible faucet. +#! +#! Stack: [acct_id] +#! Output: [is_fungible_faucet] +#! +#! - acct_id is the account id. +#! - is_fungible_faucet is a boolean indicating whether the account is a fungible faucet. +export.is_fungible_faucet + exec.type push.FUNGIBLE_FAUCET_ACCOUNT eq + # => [is_fungible_faucet] +end + +#! Returns a boolean indicating whether the account is a non-fungible faucet. +#! +#! Stack: [acct_id] +#! Output: [is_non_fungible_faucet] +#! +#! - acct_id is the account id. +#! - is_non_fungible_faucet is a boolean indicating whether the account is a non-fungible faucet. +export.is_non_fungible_faucet + exec.type push.NON_FUNGIBLE_FAUCET_ACCOUNT eq + # => [is_non_fungible_faucet] +end diff --git a/miden-lib/asm/miden/asset.masm b/miden-lib/asm/miden/asset.masm index 1c4af00c6..501e39efb 100644 --- a/miden-lib/asm/miden/asset.masm +++ b/miden-lib/asm/miden/asset.masm @@ -1,5 +1,3 @@ -use.miden::kernels::tx::account->internal_account -use.miden::kernels::tx::asset use.miden::account # ERRORS @@ -36,11 +34,11 @@ const.FUNGIBLE_BITMASK_U32=0x20000000 #! - ASSET is the built fungible asset. export.build_fungible_asset # assert the faucet is a fungible faucet - dup exec.internal_account::is_fungible_faucet assert.err=ERR_ASSET_NOT_FUNGIBLE_ID + dup exec.account::is_fungible_faucet assert.err=ERR_ASSET_NOT_FUNGIBLE_ID # => [faucet_id, amount] # assert the amount is valid - dup.1 exec.asset::get_fungible_asset_max_amount lte assert.err=ERR_ASSET_INVALID_AMOUNT + dup.1 exec.get_fungible_asset_max_amount lte assert.err=ERR_ASSET_INVALID_AMOUNT # => [faucet_id, amount] # create the asset @@ -75,7 +73,7 @@ end #! - ASSET is the built non-fungible asset. export.build_non_fungible_asset # assert the faucet is a non-fungible faucet - dup exec.internal_account::is_non_fungible_faucet assert.err=ERR_ASSET_NOT_NON_FUNGIBLE_ID + dup exec.account::is_non_fungible_faucet assert.err=ERR_ASSET_NOT_NON_FUNGIBLE_ID # => [faucet_id, DATA_HASH] # build the asset @@ -103,3 +101,19 @@ export.create_non_fungible_asset exec.build_non_fungible_asset # => [ASSET] end + +# PROCEDURES COPIED FROM KERNEL (TODO: get rid of this duplication) +# ================================================================================================= + +const.FUNGIBLE_ASSET_MAX_AMOUNT=9223372036854775807 + +#! Returns the maximum amount of a fungible asset. +#! +#! Stack: [] +#! Outputs: [fungible_asset_max_amount] +#! +#! fungible_asset_max_amount is the maximum amount of a fungible asset. +export.get_fungible_asset_max_amount + push.FUNGIBLE_ASSET_MAX_AMOUNT + # => [fungible_asset_max_amount] +end \ No newline at end of file diff --git a/miden-lib/asm/miden/contracts/auth/basic.masm b/miden-lib/asm/miden/contracts/auth/basic.masm index 40d8356a3..337f238c0 100644 --- a/miden-lib/asm/miden/contracts/auth/basic.masm +++ b/miden-lib/asm/miden/contracts/auth/basic.masm @@ -13,7 +13,7 @@ const.PUBLIC_KEY_SLOT=0 #! Output: [] #! export.auth_tx_rpo_falcon512 - # Get commitments to created notes + # Get commitments to output notes exec.tx::get_output_notes_hash # => [OUTPUT_NOTES_HASH, ...] diff --git a/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm b/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm index e97f9a7ef..aa6fe3589 100644 --- a/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm +++ b/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm @@ -15,7 +15,7 @@ use.miden::contracts::auth::basic # CONSTANTS # ================================================================================================= -const.OFFCHAIN_NOTE=2 +const.PRIVATE_NOTE=2 # ERRORS # ================================================================================================= @@ -33,16 +33,18 @@ const.METADATA_SLOT=1 export.basic::auth_tx_rpo_falcon512 #! Distributes freshly minted fungible assets to the provided recipient. -#! Inputs: [amount, tag, aux, note_type, RECIPIENT] -#! Outputs: [note_idx, 0, 0, 0, 0, 0, 0, 0, ...] +#! +#! Inputs: [amount, tag, aux, note_type, execution_hint, RECIPIENT] +#! Outputs: [note_idx, 0, 0, 0, 0, 0, 0, 0, 0, ...] #! #! - amount is the amount to be minted and sent. #! - tag is the tag to be included in the note. #! - aux is the auxiliary data to be included in the note. #! - note_type is the type of the note that holds the asset. +#! - execution_hint is the execution hint of the note that holds the asset. #! - RECIPIENT is the recipient of the asset, i.e., #! hash(hash(hash(serial_num, [0; 4]), script_hash), input_hash). -#! - note_idx is the index of the created note. +#! - note_idx is the index of the output note. #! This cannot directly be accessed from another context. #! #! FAILS if: @@ -51,38 +53,38 @@ export.basic::auth_tx_rpo_falcon512 export.distribute.1 # get max supply of this faucet. We assume it is stored at pos 3 of slot 1 push.METADATA_SLOT exec.account::get_item drop drop drop - # => [max_supply, amount, tag, aux, note_type, RECIPIENT, ...] + # => [max_supply, amount, tag, aux, note_type, execution_hint, RECIPIENT, ...] # get total issuance of this faucet so far and add amount to be minted exec.faucet::get_total_issuance - # => [total_issuance, max_supply, amount, tag, aux, note_type RECIPIENT, ...] + # => [total_issuance, max_supply, amount, tag, aux, note_type, execution_hint, RECIPIENT, ...] # compute maximum amount that can be minted, max_mint_amount = max_supply - total_issuance sub - # => [max_supply - total_issuance, amount, tag, aux, note_type, RECIPIENT, ...] + # => [max_supply - total_issuance, amount, tag, aux, note_type, execution_hint, RECIPIENT, ...] # check that amount =< max_supply - total_issuance, fails if otherwise dup.1 gte assert.err=ERR_BASIC_FUNGIBLE_MAX_SUPPLY_OVERFLOW - # => [asset, tag, aux, note_type, RECIPIENT, ...] + # => [asset, tag, aux, note_type, execution_hint, RECIPIENT, ...] # creating the asset exec.asset::create_fungible_asset - # => [ASSET, tag, aux, note_type, RECIPIENT, ...] + # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT, ...] # mint the asset; this is needed to satisfy asset preservation logic. exec.faucet::mint - # => [ASSET, tag, aux, note_type, RECIPIENT, ...] + # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT, ...] # store and drop the ASSET loc_storew.0 dropw - # => [tag, aux, note_type, RECIPIENT, ...] + # => [tag, aux, note_type, execution_hint, RECIPIENT, ...] # create a note exec.tx::create_note # => [note_idx, EMPTY_WORD, EMPTY_WORD, ...] # load the ASSET and add it to the note - padw loc_loadw.0 movup.4 exec.tx::add_asset_to_note + movdn.4 loc_loadw.0 exec.tx::add_asset_to_note movup.4 # => [note_idx, ASSET, EMPTY_WORD, ...] end diff --git a/miden-lib/asm/miden/contracts/wallets/basic.masm b/miden-lib/asm/miden/contracts/wallets/basic.masm index 033313e37..02ed3788b 100644 --- a/miden-lib/asm/miden/contracts/wallets/basic.masm +++ b/miden-lib/asm/miden/contracts/wallets/basic.masm @@ -24,16 +24,17 @@ end #! Creates a note which sends the specified asset out of the current account #! to the specified recipient. #! -#! Inputs: [ASSET, tag, aux, note_type, RECIPIENT, ...] -#! Outputs: [note_idx, EMPTY_WORD, EMPTY_WORD, 0, 0, ...] +#! Inputs: [ASSET, tag, aux, note_type, execution_hint, RECIPIENT, ...] +#! Outputs: [note_idx, EMPTY_WORD, EMPTY_WORD, 0, 0, 0, ...] #! #! - ASSET is the non-fungible asset of interest. #! - tag is the tag to be included in the note. #! - aux is the auxiliary data to be included in the note. #! - note_type is the note's storage type +#! - execution_hint is the note's execution hint #! - RECIPIENT is the recipient of the note, i.e., #! hash(hash(hash(serial_num, [0; 4]), script_hash), input_hash) -#! - note_idx is the index of the created note. +#! - note_idx is the index of the output note. #! This cannot directly be accessed from another context. #! #! Panics: @@ -42,34 +43,68 @@ end #! - The non-fungible asset is not found in the vault. export.send_asset.1 exec.account::remove_asset - # => [ASSET, tag, aux, note_type, RECIPIENT, ...] + # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT, PAD(4)] # Store the ASSET for later loc_storew.0 dropw - # => [tag, aux, note_type, RECIPIENT, ...] + # => [tag, aux, note_type, execution_hint, RECIPIENT, PAD(8)] - # This procedure is written to be executed with `exec` or `call`. When this - # procedure is `call`ed the stack has to be carefully manipulated to avoid - # inserting unwanted elements between the user data. The convention is to - # ensure the input&output data have the same length. This code pads the - # stack so the output stack will be the same length as the input. - # - # The padding must be added before calling `create_note`, not after. This - # is because the VM stack has a minimum size of 16 elements, trying to push - # elements after the call to `create_note` would increase the stack in - # addition to the minimum 16 elements. - push.0 movdn.7 padw movdnw.2 padw movdnw.2 - # => [tag, aux, note_type, RECIPIENT, 0, EMPTY_WORD, EMPTY_WORD, ...] + exec.tx::create_note + # => [note_idx, PAD(15)] + + movdn.4 loc_loadw.0 + # => [ASSET, note_idx, PAD(11)] + + exec.tx::add_asset_to_note movup.4 + # => [note_idx, PAD(15)] +end +#! Creates a new note. +#! +#! The created note will not have any assets attached to it. To add assets to this note use the +#! `move_asset_to_note` procedure. +#! +#! This procedure is expected to be invoked using a `call` instruction. It makes no guarantees about +#! the contents of the `PAD` elements shown below. It is the caller's responsibility to make sure +#! these elements do not contain any meaningful data. +#! +#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT, PAD(8)] +#! Outputs: [note_idx, PAD(15)] +#! +#! - tag is the tag to be included in the note. +#! - aux is the auxiliary data to be included in the note. +#! - note_type is the note's storage type +#! - execution_hint is the note's execution hint +#! - RECIPIENT is the recipient of the note, i.e., +#! hash(hash(hash(serial_num, [0; 4]), script_hash), input_hash) +export.create_note exec.tx::create_note - # => [note_idx, 0, EMPTY_WORD, EMPTY_WORD, ...] + # => [note_idx, PAD(15) ...] +end - padw loc_loadw.0 movup.4 - # => [note_idx, ASSET, 0, EMPTY_WORD, EMPTY_WORD, ...] +#! Removes the specified asset from the account and adds it to the output note with the specified +#! index. +#! +#! This procedure is expected to be invoked using a `call` instruction. It makes no guarantees about +#! the contents of the `PAD` elements shown below. It is the caller's responsibility to make sure +#! these elements do not contain any meaningful data. +#! +#! Inputs: [ASSET, note_idx, PAD(11)] +#! Outputs: [ASSET, note_idx, PAD(11)] +#! +#! - note_idx is the index of the output note. +#! - ASSET is the fungible or non-fungible asset of interest. +#! +#! Panics: +#! - The fungible asset is not found in the vault. +#! - The amount of the fungible asset in the vault is less than the amount to be removed. +#! - The non-fungible asset is not found in the vault. +export.move_asset_to_note + # remove the asset from the account + exec.account::remove_asset + # => [ASSET, note_idx, PAD(11)] + exec.tx::add_asset_to_note - # => [note_idx, 0, EMPTY_WORD, EMPTY_WORD, ...] - - # prepare the stack for return - stack has 6 elements too many - movupw.3 dropw swap drop swap drop + # => [ASSET, note_idx, PAD(11) ...] end diff --git a/miden-lib/asm/miden/note.masm b/miden-lib/asm/miden/note.masm index 658e8cbf8..0afb93de9 100644 --- a/miden-lib/asm/miden/note.masm +++ b/miden-lib/asm/miden/note.masm @@ -1,4 +1,3 @@ -use.miden::kernels::tx::constants use.std::crypto::hashes::native use.std::mem @@ -49,21 +48,21 @@ export.get_assets padw push.0 # => [0, 0, 0, 0, 0, dest_ptr] - # get the current consumed note vault hash - syscall.get_note_vault_info - # => [VAULT_HASH, num_assets, dest_ptr] + # get the current input note assets info + syscall.get_note_assets_info + # => [ASSETS_HASH, num_assets, dest_ptr] - # load the vault data from the advice map to the advice stack - adv.push_mapval - # => [VAULT_HASH, num_assets, dest_ptr] + # load the asset data from the advice map to the advice stack + adv.push_mapval push.15887 drop # FIX: wrap the decorator to ensure MAST uniqueness + # => [ASSETS_HASH, num_assets, dest_ptr] # calculate number of assets rounded up to an even number dup.4 dup is_odd add - # => [even_num_assets, VAULT_HASH, num_assets, dest_ptr] + # => [even_num_assets, ASSETS_HASH, num_assets, dest_ptr] # calculate the start and end pointer for reading to memory dup.6 add dup.6 - # => [start_ptr, end_ptr, VAULT_HASH, num_assets, dest_ptr] + # => [start_ptr, end_ptr, ASSETS_HASH, num_assets, dest_ptr] # write the data from the advice stack into memory exec.write_advice_data_to_memory @@ -88,14 +87,14 @@ export.get_inputs # => [INPUTS_HASH, dest_ptr] # load the inputs from the advice map to the advice stack - adv.push_mapval + adv.push_mapval push.15973 drop # FIX: wrap the decorator to ensure MAST uniqueness # => [INPUTS_HASH, dest_ptr] adv_push.1 # => [num_inputs, INPUTS_HASH, dest_ptr] # validate the input length - dup exec.constants::get_max_inputs_per_note lte assert.err=ERR_NOTE_TOO_MANY_INPUTS + dup exec.get_max_inputs_per_note lte assert.err=ERR_NOTE_TOO_MANY_INPUTS # => [num_inputs, INPUTS_HASH, dest_ptr] # calculate the number of words required to store the inputs @@ -147,14 +146,14 @@ end #! Computes hash of note inputs starting at the specified memory address. #! -#! This procedure divides the hashing process into two parts: hashing pairs of words using +#! This procedure divides the hashing process into two parts: hashing pairs of words using #! `hash_memory_even` procedure and hashing the remaining values using the `hperm` instruction. #! #! If the number if inputs is 0, procedure returns the empty word: [0, 0, 0, 0]. #! #! Inputs: [inputs_ptr, num_inputs] #! Outputs: [HASH] -#! Cycles: +#! Cycles: #! - If number of elements divides by 8: 56 cycles + 3 * words #! - Else: 189 cycles + 3 * words #! @@ -163,7 +162,7 @@ export.compute_inputs_hash # check that number of inputs is less than 128 dup.1 push.128 u32assert2 u32lte assert - # move number of inputs to the top of the stack + # move number of inputs to the top of the stack swap # => [num_inputs, inputs_ptr] @@ -178,9 +177,9 @@ export.compute_inputs_hash # get the padding flag to add it to the capacity part dup.2 eq.0 not # => [pad_flag, inputs_ptr, end_addr, num_inputs%8] - - # prepare hasher state for RPO permutation - push.0.0.0 padw padw + + # prepare hasher state for RPO permutation + push.0.0.0 padw padw # => [C, B, A, inputs_ptr, end_addr, num_inputs%8] # hash every pair of words @@ -210,7 +209,7 @@ export.compute_inputs_hash # => [E, D, A', drop_counter] ### 0th value ######################################################## - + # if current value is the last value to drop ("cycle" number equals to the number of values # to drop), push 1 instead of 0 to the stack dup.12 eq.1 swap @@ -220,9 +219,9 @@ export.compute_inputs_hash # => [e_2, e_1, e_0, d_3, d_2, d_1, 0/1, d_0, A', drop_counter] ### 1st value ######################################################## - + # prepare the second element of the E Word for cdrop instruction - # if current value is the last value to drop ("cycle" number equals to the number of values + # if current value is the last value to drop ("cycle" number equals to the number of values # to drop), push 1 instead of 0 to the stack dup.12 eq.2 swap # => [e_2, 0, e_1, e_0, d_3, d_2, d_1, 0/1, d_0, A', drop_counter] @@ -239,7 +238,7 @@ export.compute_inputs_hash or # => [latch', e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter] - # save the latch value + # save the latch value dup movdn.14 # => [latch', e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', latch', drop_counter] @@ -247,7 +246,7 @@ export.compute_inputs_hash cdrop # => [e_2_or_0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', latch', drop_counter] - # move the calculated value down the stack + # move the calculated value down the stack movdn.6 # => [e_1, e_0, d_3, d_2, d_1, 0, e_2_or_0, d_0, A', latch', drop_counter] @@ -296,8 +295,8 @@ export.compute_inputs_hash # => [0, e_2_or_0, e_1_or_0, e_0_or_0, d_3_or_0, d_2_or_0, d_1_or_0, d_0, A'] # or in other words # => [C, B, A', ... ] - # notice that we don't need to check the d_0 value: entering the else branch means that - # we have number of elements not divisible by 8, so we will have at least one element to + # notice that we don't need to check the d_0 value: entering the else branch means that + # we have number of elements not divisible by 8, so we will have at least one element to # hash here (which turns out to be d_0) hperm @@ -308,3 +307,18 @@ export.compute_inputs_hash end end +# PROCEDURES COPIED FROM KERNEL (TODO: get rid of this duplication) +# ================================================================================================= + +# The maximum number of input values associated with a single note. +const.MAX_INPUTS_PER_NOTE=128 + +#! Returns the max allowed number of input values per note. +#! +#! Stack: [] +#! Output: [max_inputs_per_note] +#! +#! - max_inputs_per_note is the max inputs per note. +export.get_max_inputs_per_note + push.MAX_INPUTS_PER_NOTE +end diff --git a/miden-lib/asm/miden/tx.masm b/miden-lib/asm/miden/tx.masm index ad5f8934d..8f1e61b41 100644 --- a/miden-lib/asm/miden/tx.masm +++ b/miden-lib/asm/miden/tx.masm @@ -55,37 +55,48 @@ end #! Creates a new note and returns the index of the note. #! -#! Inputs: [tag, aux, note_type, RECIPIENT] -#! Outputs: [note_idx] +#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT, ...] +#! Outputs: [note_idx, ...] #! #! tag is the tag to be included in the note. #! aux is the auxiliary metadata to be included in the note. #! note_type is the storage type of the note +#! execution_hint is the note's execution hint #! RECIPIENT is the recipient of the note. #! note_idx is the index of the crated note. export.create_note + # pad the stack before the syscall to prevent accidental modification of the deeper stack + # elements + padw padw swapdw + # => [tag, aux, note_type, execution_hint, RECIPIENT, PAD(8)] + syscall.create_note - # => [note_idx, EMPTY_WORD, 0] + # => [note_idx, PAD(15)] - # clear the padding from the kernel response - movdn.4 dropw swap drop + # remove excess PADs from the stack + swapdw dropw dropw movdn.7 dropw drop drop drop # => [note_idx] end #! Adds the ASSET to the note specified by the index. #! -#! Inputs: [note_idx, ASSET] -#! Outputs: [note_idx] +#! Inputs: [ASSET, note_idx, ...] +#! Outputs: [ASSET, note_idx, ...] #! #! note_idx is the index of the note to which the asset is added. #! ASSET can be a fungible or non-fungible asset. export.add_asset_to_note - syscall.add_asset_to_note - # => [note_idx, EMPTY_WORD] + # pad the stack before the syscall to prevent accidental modification of the deeper stack + # elements + push.0.0.0 padw padw swapdw movup.7 swapw movup.4 + # => [note_idx, ASSET, PAD(11)] - # clear the padding from the kernel response - movdn.4 dropw - # => [note_idx] + syscall.add_asset_to_note + # => [note_idx, ASSET, PAD(11)] + + # remove excess PADs from the stack + swapdw dropw dropw swapw movdn.7 drop drop drop movdn.4 + # => [ASSET, note_idx] end #! Returns the RECIPIENT for a specified SERIAL_NUM, SCRIPT_HASH, and inputs hash diff --git a/miden-lib/asm/note_scripts/SWAP.masm b/miden-lib/asm/note_scripts/SWAP.masm index 460b2eadb..2f0d17d1c 100644 --- a/miden-lib/asm/note_scripts/SWAP.masm +++ b/miden-lib/asm/note_scripts/SWAP.masm @@ -4,12 +4,12 @@ use.miden::contracts::wallets::basic->wallet # CONSTANTS # ================================================================================================= -const.OFFCHAIN_NOTE=2 +const.PRIVATE_NOTE=2 # ERRORS # ================================================================================================= -# SWAP script expects exactly 9 note inputs +# SWAP script expects exactly 10 note inputs const.ERR_SWAP_WRONG_NUMBER_OF_INPUTS=0x00020007 # SWAP script requires exactly 1 note asset @@ -51,8 +51,8 @@ begin push.0 exec.note::get_inputs # => [num_inputs, inputs_ptr] - # make sure the number of inputs is 9 - eq.9 assert.err=ERR_SWAP_WRONG_NUMBER_OF_INPUTS + # make sure the number of inputs is 10 + eq.10 assert.err=ERR_SWAP_WRONG_NUMBER_OF_INPUTS # => [inputs_ptr] # load RECIPIENT @@ -63,23 +63,26 @@ begin # => [ASSET, RECIPIENT] padw mem_loadw.2 - # => [0, 0, 0, tag, ASSET, RECIPIENT] + # => [0, 0, execution_hint, tag, ASSET, RECIPIENT] - drop drop drop movdn.4 - # => [ASSET, tag, RECIPIENT] + drop drop swap + # => [tag, execution_hint, ASSET, RECIPIENT] + + # we add aux = 0 to the note assuming we don't need it for the second leg of the SWAP + push.0 swap + # => [tag, aux, execution_hint, ASSET, RECIPIENT] - push.OFFCHAIN_NOTE movdn.5 - # => [ASSET, tag, note_type, RECIPIENT] + push.PRIVATE_NOTE movdn.2 + # => [tag, aux, note_type, execution_hint, ASSET, RECIPIENT] - # we add aux = 0 to the note assuming we don't need it for the second leg of the SWAP - push.0 movdn.5 - # => [ASSET, tag, aux, note_type, RECIPIENT] + swapw + # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT] # create a note using inputs call.wallet::send_asset # => [ptr, EMPTY_WORD, EMPTY_WORD, 0] # clean stack - dropw dropw drop drop + dropw dropw drop drop drop # => [] end diff --git a/miden-lib/build.rs b/miden-lib/build.rs index d07008735..77b72eefd 100644 --- a/miden-lib/build.rs +++ b/miden-lib/build.rs @@ -3,11 +3,13 @@ use std::{ fs::File, io::{self, BufRead, BufReader, Write}, path::{Path, PathBuf}, + sync::Arc, }; use assembly::{ - ast::{AstSerdeOptions, ProgramAst}, - LibraryNamespace, MaslLibrary, Version, + diagnostics::{IntoDiagnostic, Result}, + utils::Serializable, + Assembler, DefaultSourceManager, KernelLibrary, Library, LibraryNamespace, }; // CONSTANTS @@ -17,16 +19,16 @@ const ASSETS_DIR: &str = "assets"; const ASM_DIR: &str = "asm"; const ASM_MIDEN_DIR: &str = "miden"; const ASM_NOTE_SCRIPTS_DIR: &str = "note_scripts"; -const ASM_KERNELS_DIR: &str = "kernels/transaction"; +const ASM_TX_KERNEL_DIR: &str = "kernels/transaction"; // PRE-PROCESSING // ================================================================================================ /// Read and parse the contents from `./asm`. -/// - Compiles contents of asm/miden directory into a Miden library file (.masl) under -/// miden namespace. +/// - Compiles contents of asm/miden directory into a Miden library file (.masl) under miden +/// namespace. /// - Compiles contents of asm/scripts directory into individual .masb files. -fn main() -> io::Result<()> { +fn main() -> Result<()> { // re-build when the MASM code changes println!("cargo:rerun-if-changed=asm"); @@ -43,30 +45,51 @@ fn main() -> io::Result<()> { // set target directory to {OUT_DIR}/assets let target_dir = Path::new(&build_dir).join(ASSETS_DIR); + // compile transaction kernel + let mut assembler = + compile_tx_kernel(&source_dir.join(ASM_TX_KERNEL_DIR), &target_dir.join("kernels"))?; + // compile miden library - compile_miden_lib(&source_dir, &target_dir)?; + let miden_lib = compile_miden_lib(&source_dir, &target_dir, assembler.clone())?; + assembler.add_library(miden_lib)?; - // compile kernel and note scripts - compile_kernels(&source_dir.join(ASM_KERNELS_DIR), &target_dir.join("kernels"))?; + // compile note scripts compile_note_scripts( &source_dir.join(ASM_NOTE_SCRIPTS_DIR), &target_dir.join(ASM_NOTE_SCRIPTS_DIR), + assembler, )?; Ok(()) } -// COMPILE MIDEN LIB +// COMPILE TRANSACTION KERNEL // ================================================================================================ -fn compile_miden_lib(source_dir: &Path, target_dir: &Path) -> io::Result<()> { - let source_dir = source_dir.join(ASM_MIDEN_DIR); +/// Reads the transaction kernel MASM source from the `source_dir`, compiles it, saves the results +/// to the `target_dir`, and returns an [Assembler] instantiated with the compiled kernel. +/// +/// `source_dir` is expected to have the following structure: +/// +/// - {source_dir}/api.masm -> defines exported procedures from the transaction kernel. +/// - {source_dir}/main.masm -> defines the executable program of the transaction kernel. +/// - {source_dir}/lib -> contains common modules used by both api.masm and main.masm. +/// +/// The complied files are written as follows: +/// +/// - {target_dir}/tx_kernel.masl -> contains kernel library compiled from api.masm. +/// - {target_dir}/tx_kernel.masb -> contains the executable compiled from main.masm. +/// +/// When the `testing` feature is enabled, the POW requirements for account ID generation are +/// adjusted by modifying the corresponding constants in {source_dir}/lib/constants.masm file. +fn compile_tx_kernel(source_dir: &Path, target_dir: &Path) -> Result { + let assembler = build_assembler(None)?; // if this build has the testing flag set, modify the code and reduce the cost of proof-of-work match env::var("CARGO_FEATURE_TESTING") { Ok(ref s) if s == "1" => { - let constants = source_dir.join("kernels/tx/constants.masm"); - let patched = source_dir.join("kernels/tx/constants.masm.patched"); + let constants = source_dir.join("lib/constants.masm"); + let patched = source_dir.join("lib/constants.masm.patched"); // scope for file handlers { @@ -76,7 +99,7 @@ fn compile_miden_lib(source_dir: &Path, target_dir: &Path) -> io::Result<()> { for line in modified { write.write_all(line.unwrap().as_bytes()).unwrap(); - write.write_all(&[b'\n']).unwrap(); + write.write_all(b"\n").unwrap(); } write.flush().unwrap(); } @@ -87,13 +110,44 @@ fn compile_miden_lib(source_dir: &Path, target_dir: &Path) -> io::Result<()> { _ => (), } - let ns = LibraryNamespace::try_from("miden".to_string()).expect("invalid base namespace"); - let version = Version::try_from(env!("CARGO_PKG_VERSION")).expect("invalid cargo version"); - let miden_lib = MaslLibrary::read_from_dir(source_dir, ns, true, version)?; + // assemble the kernel library and write it to the "tx_kernel.masl" file + let kernel_lib = KernelLibrary::from_dir( + source_dir.join("api.masm"), + Some(source_dir.join("lib")), + assembler, + )?; - miden_lib.write_to_dir(target_dir)?; + let output_file = target_dir.join("tx_kernel").with_extension(Library::LIBRARY_EXTENSION); + kernel_lib.write_to_file(output_file).into_diagnostic()?; - Ok(()) + let assembler = build_assembler(Some(kernel_lib))?; + + // assemble the kernel program and write it the "tx_kernel.masb" file + let mut main_assembler = assembler.clone(); + let namespace = LibraryNamespace::new("kernel").expect("invalid namespace"); + main_assembler.add_modules_from_dir(namespace, &source_dir.join("lib"))?; + + let main_file_path = source_dir.join("main.masm").clone(); + let kernel_main = main_assembler.assemble_program(main_file_path)?; + + let masb_file_path = target_dir.join("tx_kernel.masb"); + kernel_main.write_to_file(masb_file_path).into_diagnostic()?; + + #[cfg(feature = "testing")] + { + // Build kernel as a library and save it to file. + // This is needed in test assemblers to access individual procedures which would otherwise + // be hidden when using KernelLibrary (api.masm) + let namespace = "kernel".parse::().expect("invalid base namespace"); + let test_lib = + Library::from_dir(source_dir.join("lib"), namespace, assembler.clone()).unwrap(); + + let masb_file_path = + target_dir.join("kernel_library").with_extension(Library::LIBRARY_EXTENSION); + test_lib.write_to_file(masb_file_path).into_diagnostic()?; + } + + Ok(assembler) } fn decrease_pow(line: io::Result) -> io::Result { @@ -110,18 +164,44 @@ fn decrease_pow(line: io::Result) -> io::Result { Ok(line) } +// COMPILE MIDEN LIB +// ================================================================================================ + +/// Reads the MASM files from "{source_dir}/miden" directory, compiles them into a Miden assembly +/// library, saves the library into "{target_dir}/miden.masl", and returns the complied library. +fn compile_miden_lib( + source_dir: &Path, + target_dir: &Path, + assembler: Assembler, +) -> Result { + let source_dir = source_dir.join(ASM_MIDEN_DIR); + + let namespace = "miden".parse::().expect("invalid base namespace"); + let miden_lib = Library::from_dir(source_dir, namespace, assembler)?; + + let output_file = target_dir.join("miden").with_extension(Library::LIBRARY_EXTENSION); + miden_lib.write_to_file(output_file).into_diagnostic()?; + + Ok(miden_lib) +} + // COMPILE EXECUTABLE MODULES // ================================================================================================ -fn compile_note_scripts(source_dir: &Path, target_dir: &Path) -> io::Result<()> { +/// Reads all MASM files from the "{source_dir}", complies each file individually into a MASB +/// file, and stores the complied files into the "{target_dir}". +/// +/// The source files are expected to contain executable programs. +fn compile_note_scripts(source_dir: &Path, target_dir: &Path, assembler: Assembler) -> Result<()> { if let Err(e) = fs::create_dir_all(target_dir) { println!("Failed to create note_scripts directory: {}", e); } - for masm_file_path in get_masm_files(source_dir)? { + for masm_file_path in get_masm_files(source_dir).unwrap() { // read the MASM file, parse it, and serialize the parsed AST to bytes - let ast = ProgramAst::parse(&fs::read_to_string(masm_file_path.clone())?)?; - let bytes = ast.to_bytes(AstSerdeOptions { serialize_imports: true }); + let code = assembler.clone().assemble_program(masm_file_path.clone())?; + + let bytes = code.to_bytes(); // TODO: get rid of unwraps let masb_file_name = masm_file_path.file_name().unwrap().to_str().unwrap(); @@ -129,42 +209,28 @@ fn compile_note_scripts(source_dir: &Path, target_dir: &Path) -> io::Result<()> // write the binary MASB to the output dir masb_file_path.set_extension("masb"); - fs::write(masb_file_path, bytes)?; + fs::write(masb_file_path, bytes).unwrap(); } Ok(()) } -// COMPILE KERNELS +// HELPER FUNCTIONS // ================================================================================================ -fn compile_kernels(source_dir: &Path, target_dir: &Path) -> io::Result<()> { - // read the MASM file, parse it, and serialize the parsed AST to bytes - let ast = ProgramAst::parse(&fs::read_to_string(source_dir.join("main.masm").clone())?)?; - let bytes = ast.to_bytes(AstSerdeOptions { serialize_imports: true }); - - // create the output file path - let masb_file_name = "transaction"; - let mut masb_file_path = target_dir.join(masb_file_name); - if let Err(e) = fs::create_dir_all(masb_file_path.clone()) { - println!("Failed to create kernels directory: {}", e); - } - - // write the binary MASB to the output dir - masb_file_path.set_extension("masb"); - fs::write(masb_file_path.clone(), bytes)?; - - Ok(()) +/// Returns a new [Assembler] loaded with miden-stdlib and the specified kernel, if provided. +/// +/// The returned assembler will be in the `debug` mode if the `with-debug-info` feature is enabled. +fn build_assembler(kernel: Option) -> Result { + kernel + .map(|kernel| Assembler::with_kernel(Arc::new(DefaultSourceManager::default()), kernel)) + .unwrap_or_default() + .with_debug_mode(cfg!(feature = "with-debug-info")) + .with_library(miden_stdlib::StdLibrary::default()) } -// HELPER FUNCTIONS -// ================================================================================================ - /// Recursively copies `src` into `dst`. /// /// This function will overwrite the existing files if re-executed. -/// -/// Panics: -/// - If any of the IO operation fails. fn copy_directory, R: AsRef>(src: T, dst: R) { let mut prefix = src.as_ref().canonicalize().unwrap(); // keep all the files inside the `asm` folder diff --git a/miden-lib/src/accounts/faucets/mod.rs b/miden-lib/src/accounts/faucets/mod.rs index 3fc871a86..8cd3dcd8f 100644 --- a/miden-lib/src/accounts/faucets/mod.rs +++ b/miden-lib/src/accounts/faucets/mod.rs @@ -4,12 +4,11 @@ use miden_objects::{ accounts::{ Account, AccountCode, AccountId, AccountStorage, AccountStorageType, AccountType, SlotItem, }, - assembly::LibraryPath, assets::TokenSymbol, AccountError, Felt, Word, ZERO, }; -use super::{AuthScheme, Library, MidenLib, TransactionKernel}; +use super::{AuthScheme, TransactionKernel}; // FUNGIBLE FAUCET // ================================================================================================ @@ -18,7 +17,8 @@ const MAX_MAX_SUPPLY: u64 = (1 << 63) - 1; const MAX_DECIMALS: u8 = 12; /// Creates a new faucet account with basic fungible faucet interface, -/// account storage type, specified authentication scheme, and provided meta data (token symbol, decimals, max supply). +/// account storage type, specified authentication scheme, and provided meta data (token symbol, +/// decimals, max supply). /// /// The basic faucet interface exposes two procedures: /// - `distribute`, which mints an assets and create a note for the provided recipient. @@ -39,19 +39,21 @@ pub fn create_basic_fungible_faucet( ) -> Result<(Account, Word), AccountError> { // Atm we only have RpoFalcon512 as authentication scheme and this is also the default in the // faucet contract, so we can just use the public key as storage slot 0. - // TODO: consider using a trait when we have more auth schemes. - let auth_data: Word = match auth_scheme { - AuthScheme::RpoFalcon512 { pub_key } => pub_key.into(), + + let (auth_scheme_procedure, auth_data): (&str, Word) = match auth_scheme { + AuthScheme::RpoFalcon512 { pub_key } => ("auth_tx_rpo_falcon512", pub_key.into()), }; - let miden = MidenLib::default(); - let path = "miden::contracts::faucets::basic_fungible"; - let faucet_code_ast = miden - .get_module_ast(&LibraryPath::new(path).unwrap()) - .expect("Getting module AST failed"); + let source_code = format!( + " + export.::miden::contracts::faucets::basic_fungible::distribute + export.::miden::contracts::faucets::basic_fungible::burn + export.::miden::contracts::auth::basic::{auth_scheme_procedure} + " + ); - let account_assembler = TransactionKernel::assembler(); - let account_code = AccountCode::new(faucet_code_ast.clone(), &account_assembler)?; + let assembler = TransactionKernel::assembler(); + let account_code = AccountCode::compile(source_code, assembler)?; // First check that the metadata is valid. if decimals > MAX_DECIMALS { @@ -79,9 +81,57 @@ pub fn create_basic_fungible_faucet( init_seed, AccountType::FungibleFaucet, account_storage_type, - account_code.root(), + account_code.commitment(), account_storage.root(), )?; Ok((Account::new(account_seed, account_code, account_storage)?, account_seed)) } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_objects::{crypto::dsa::rpo_falcon512, ONE}; + + use super::{ + create_basic_fungible_faucet, AccountStorageType, AuthScheme, Felt, TokenSymbol, ZERO, + }; + + #[test] + fn faucet_contract_creation() { + let pub_key = rpo_falcon512::PublicKey::new([ONE; 4]); + let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key }; + + // we need to use an initial seed to create the wallet account + let init_seed: [u8; 32] = [ + 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85, + 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16, + ]; + + let max_supply = Felt::new(123); + let token_symbol_string = "POL"; + let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap(); + let decimals = 2u8; + let storage_type = AccountStorageType::OffChain; + + let (faucet_account, _) = create_basic_fungible_faucet( + init_seed, + token_symbol, + decimals, + max_supply, + storage_type, + auth_scheme, + ) + .unwrap(); + + // check that max_supply (slot 1) is 123 + assert_eq!( + faucet_account.storage().get_item(1), + [Felt::new(123), Felt::new(2), token_symbol.into(), ZERO].into() + ); + + assert!(faucet_account.is_faucet()); + } +} diff --git a/miden-lib/src/accounts/mod.rs b/miden-lib/src/accounts/mod.rs index e62d68c12..87d105771 100644 --- a/miden-lib/src/accounts/mod.rs +++ b/miden-lib/src/accounts/mod.rs @@ -1,4 +1,4 @@ -use super::{auth::AuthScheme, transaction::TransactionKernel, Library, MidenLib}; +use super::{auth::AuthScheme, transaction::TransactionKernel}; pub mod faucets; pub mod wallets; diff --git a/miden-lib/src/accounts/wallets/mod.rs b/miden-lib/src/accounts/wallets/mod.rs index 3c3232e2c..f37b11298 100644 --- a/miden-lib/src/accounts/wallets/mod.rs +++ b/miden-lib/src/accounts/wallets/mod.rs @@ -7,7 +7,6 @@ use miden_objects::{ accounts::{ Account, AccountCode, AccountId, AccountStorage, AccountStorageType, AccountType, SlotItem, }, - assembly::ModuleAst, AccountError, Word, }; @@ -16,13 +15,13 @@ use super::{AuthScheme, TransactionKernel}; // BASIC WALLET // ================================================================================================ -/// Creates a new account with basic wallet interface, the specified authentication scheme and the account storage type. -/// Basic wallets can be specified to have either mutable or immutable code. +/// Creates a new account with basic wallet interface, the specified authentication scheme and the +/// account storage type. Basic wallets can be specified to have either mutable or immutable code. /// /// The basic wallet interface exposes two procedures: /// - `receive_asset`, which can be used to add an asset to the account. /// - `send_asset`, which can be used to remove an asset from the account and put into a note -/// addressed to the specified recipient. +/// addressed to the specified recipient. /// /// Both methods require authentication. The authentication procedure is defined by the specified /// authentication scheme. Public key information for the scheme is stored in the account storage @@ -40,25 +39,19 @@ pub fn create_basic_wallet( } let (auth_scheme_procedure, storage_slot_0_data): (&str, Word) = match auth_scheme { - AuthScheme::RpoFalcon512 { pub_key } => ("basic::auth_tx_rpo_falcon512", pub_key.into()), + AuthScheme::RpoFalcon512 { pub_key } => ("auth_tx_rpo_falcon512", pub_key.into()), }; - let account_code_string: String = format!( + let source_code: String = format!( " - use.miden::contracts::wallets::basic->basic_wallet - use.miden::contracts::auth::basic - - export.basic_wallet::receive_asset - export.basic_wallet::send_asset - export.{auth_scheme_procedure} + export.::miden::contracts::wallets::basic::receive_asset + export.::miden::contracts::wallets::basic::send_asset + export.::miden::contracts::auth::basic::{auth_scheme_procedure} " ); - let account_code_src: &str = &account_code_string; - let account_code_ast = ModuleAst::parse(account_code_src) - .map_err(|e| AccountError::AccountCodeAssemblerError(e.into()))?; - let account_assembler = TransactionKernel::assembler(); - let account_code = AccountCode::new(account_code_ast.clone(), &account_assembler)?; + let assembler = TransactionKernel::assembler(); + let account_code = AccountCode::compile(source_code, assembler)?; let account_storage = AccountStorage::new(vec![SlotItem::new_value(0, 0, storage_slot_0_data)], BTreeMap::new())?; @@ -67,9 +60,53 @@ pub fn create_basic_wallet( init_seed, account_type, account_storage_type, - account_code.root(), + account_code.commitment(), account_storage.root(), )?; Ok((Account::new(account_seed, account_code, account_storage)?, account_seed)) } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + + use miden_objects::{crypto::dsa::rpo_falcon512, ONE}; + use vm_processor::utils::{Deserializable, Serializable}; + + use super::{create_basic_wallet, Account, AccountStorageType, AccountType, AuthScheme}; + + #[test] + fn test_create_basic_wallet() { + let pub_key = rpo_falcon512::PublicKey::new([ONE; 4]); + let wallet = create_basic_wallet( + [1; 32], + AuthScheme::RpoFalcon512 { pub_key }, + AccountType::RegularAccountImmutableCode, + AccountStorageType::OnChain, + ); + + wallet.unwrap_or_else(|err| { + panic!("{}", err); + }); + } + + #[test] + fn test_serialize_basic_wallet() { + let pub_key = rpo_falcon512::PublicKey::new([ONE; 4]); + let wallet = create_basic_wallet( + [1; 32], + AuthScheme::RpoFalcon512 { pub_key }, + AccountType::RegularAccountImmutableCode, + AccountStorageType::OnChain, + ) + .unwrap() + .0; + + let bytes = wallet.to_bytes(); + let deserialized_wallet = Account::read_from_bytes(&bytes).unwrap(); + assert_eq!(wallet, deserialized_wallet); + } +} diff --git a/miden-lib/src/auth.rs b/miden-lib/src/auth.rs index 8dad1da4c..3443f2b8b 100644 --- a/miden-lib/src/auth.rs +++ b/miden-lib/src/auth.rs @@ -2,9 +2,9 @@ use miden_objects::crypto::dsa::rpo_falcon512; /// Defines authentication schemes available to standard and faucet accounts. pub enum AuthScheme { - /// A single-key authentication scheme which relies RPO Falcon512 signatures. RPO Falcon512 is a - /// variant of the [Falcon](https://falcon-sign.info/) signature scheme. This variant differs from - /// the standard in that instead of using SHAKE256 hash function in the hash-to-point algorithm we - /// use RPO256. This makes the signature more efficient to verify in Miden VM. + /// A single-key authentication scheme which relies RPO Falcon512 signatures. RPO Falcon512 is + /// a variant of the [Falcon](https://falcon-sign.info/) signature scheme. This variant differs from + /// the standard in that instead of using SHAKE256 hash function in the hash-to-point algorithm + /// we use RPO256. This makes the signature more efficient to verify in Miden VM. RpoFalcon512 { pub_key: rpo_falcon512::PublicKey }, } diff --git a/miden-lib/src/lib.rs b/miden-lib/src/lib.rs index 0ef06ecb3..98f994ced 100644 --- a/miden-lib/src/lib.rs +++ b/miden-lib/src/lib.rs @@ -7,7 +7,7 @@ extern crate alloc; extern crate std; use miden_objects::{ - assembly::{Library, LibraryNamespace, MaslLibrary, Version}, + assembly::{mast::MastForest, Library}, utils::serde::Deserializable, }; @@ -18,45 +18,67 @@ pub mod accounts; pub mod notes; pub mod transaction; -#[cfg(all(test, feature = "std"))] -mod tests; - // RE-EXPORTS // ================================================================================================ pub use miden_objects::utils; +pub use miden_stdlib::StdLibrary; -// STANDARD LIBRARY +// CONSTANTS // ================================================================================================ -pub struct MidenLib { - contents: MaslLibrary, -} +const MIDEN_LIB_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/assets/miden.masl")); -impl Default for MidenLib { - fn default() -> Self { - let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/miden.masl")); - let contents = MaslLibrary::read_from_bytes(bytes).expect("failed to read masl!"); - Self { contents } +// MIDEN LIBRARY +// ================================================================================================ + +pub struct MidenLib(Library); + +impl AsRef for MidenLib { + fn as_ref(&self) -> &Library { + &self.0 } } -impl Library for MidenLib { - type ModuleIterator<'a> = ::ModuleIterator<'a>; - - fn root_ns(&self) -> &LibraryNamespace { - self.contents.root_ns() +impl From for Library { + fn from(value: MidenLib) -> Self { + value.0 } +} - fn version(&self) -> &Version { - self.contents.version() +impl From for MastForest { + fn from(value: MidenLib) -> Self { + value.0.into() } +} - fn modules(&self) -> Self::ModuleIterator<'_> { - self.contents.modules() +impl Default for MidenLib { + fn default() -> Self { + let contents = Library::read_from_bytes(MIDEN_LIB_BYTES).expect("failed to read masl!"); + Self(contents) } +} + +// TESTS +// ================================================================================================ - fn dependencies(&self) -> &[LibraryNamespace] { - self.contents.dependencies() +// NOTE: Most kernel-related tests can be found under /miden-tx/kernel_tests +#[cfg(all(test, feature = "std"))] +mod tests { + use miden_objects::assembly::LibraryPath; + + use super::MidenLib; + + #[test] + fn test_compile() { + let path = "miden::account::get_id".parse::().unwrap(); + let miden = MidenLib::default(); + let exists = miden.0.module_infos().any(|module| { + module + .procedures() + .any(|(_, proc)| module.path().clone().append(&proc.name).unwrap() == path) + }); + + assert!(exists); } } diff --git a/miden-lib/src/notes/mod.rs b/miden-lib/src/notes/mod.rs index 7d9debe86..a70ac907a 100644 --- a/miden-lib/src/notes/mod.rs +++ b/miden-lib/src/notes/mod.rs @@ -5,14 +5,14 @@ use miden_objects::{ assets::Asset, crypto::rand::FeltRng, notes::{ - Note, NoteAssets, NoteDetails, NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, - NoteTag, NoteType, + Note, NoteAssets, NoteDetails, NoteExecutionHint, NoteExecutionMode, NoteInputs, + NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType, }, + utils::Deserializable, + vm::Program, Felt, NoteError, Word, }; -use self::utils::build_note_script; - pub mod utils; // STANDARDIZED SCRIPTS @@ -37,13 +37,15 @@ pub fn create_p2id_note( rng: &mut R, ) -> Result { let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/P2ID.masb")); - let note_script = build_note_script(bytes)?; + let program = + Program::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)?; + let note_script = NoteScript::new(program); let inputs = NoteInputs::new(vec![target.into()])?; - let tag = NoteTag::from_account_id(target, NoteExecutionHint::Local)?; + let tag = NoteTag::from_account_id(target, NoteExecutionMode::Local)?; let serial_num = rng.draw_word(); - let metadata = NoteMetadata::new(sender, note_type, tag, aux)?; + let metadata = NoteMetadata::new(sender, note_type, tag, NoteExecutionHint::always(), aux)?; let vault = NoteAssets::new(assets)?; let recipient = NoteRecipient::new(serial_num, note_script, inputs); Ok(Note::new(vault, metadata, recipient)) @@ -71,14 +73,16 @@ pub fn create_p2idr_note( rng: &mut R, ) -> Result { let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/P2IDR.masb")); - let note_script = build_note_script(bytes)?; + let program = + Program::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)?; + let note_script = NoteScript::new(program); let inputs = NoteInputs::new(vec![target.into(), recall_height.into()])?; - let tag = NoteTag::from_account_id(target, NoteExecutionHint::Local)?; + let tag = NoteTag::from_account_id(target, NoteExecutionMode::Local)?; let serial_num = rng.draw_word(); let vault = NoteAssets::new(assets)?; - let metadata = NoteMetadata::new(sender, note_type, tag, aux)?; + let metadata = NoteMetadata::new(sender, note_type, tag, NoteExecutionHint::always(), aux)?; let recipient = NoteRecipient::new(serial_num, note_script, inputs); Ok(Note::new(vault, metadata, recipient)) } @@ -101,14 +105,16 @@ pub fn create_swap_note( rng: &mut R, ) -> Result<(Note, NoteDetails), NoteError> { let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/SWAP.masb")); - let note_script = build_note_script(bytes)?; + let program = + Program::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)?; + let note_script = NoteScript::new(program); let payback_serial_num = rng.draw_word(); let payback_recipient = utils::build_p2id_recipient(sender, payback_serial_num)?; let payback_recipient_word: Word = payback_recipient.digest().into(); let requested_asset_word: Word = requested_asset.into(); - let payback_tag = NoteTag::from_account_id(sender, NoteExecutionHint::Local)?; + let payback_tag = NoteTag::from_account_id(sender, NoteExecutionMode::Local)?; let inputs = NoteInputs::new(vec![ payback_recipient_word[0], @@ -120,6 +126,7 @@ pub fn create_swap_note( requested_asset_word[2], requested_asset_word[3], payback_tag.inner().into(), + NoteExecutionHint::always().into(), ])?; // build the tag for the SWAP use case @@ -127,7 +134,7 @@ pub fn create_swap_note( let serial_num = rng.draw_word(); // build the outgoing note - let metadata = NoteMetadata::new(sender, note_type, tag, aux)?; + let metadata = NoteMetadata::new(sender, note_type, tag, NoteExecutionHint::always(), aux)?; let assets = NoteAssets::new(vec![offered_asset])?; let recipient = NoteRecipient::new(serial_num, note_script, inputs); let note = Note::new(assets, metadata, recipient); @@ -150,7 +157,7 @@ pub fn create_swap_note( /// together as offered_asset_tag + requested_asset tag. /// /// Network execution hint for the returned tag is set to `Local`. -fn build_swap_tag( +pub fn build_swap_tag( note_type: NoteType, offered_asset: &Asset, requested_asset: &Asset, @@ -169,7 +176,7 @@ fn build_swap_tag( let payload = ((offered_asset_tag as u16) << 8) | (requested_asset_tag as u16); - let execution = NoteExecutionHint::Local; + let execution = NoteExecutionMode::Local; match note_type { NoteType::Public => NoteTag::for_public_use_case(SWAP_USE_CASE_ID, payload, execution), _ => NoteTag::for_local_use_case(SWAP_USE_CASE_ID, payload), diff --git a/miden-lib/src/notes/utils.rs b/miden-lib/src/notes/utils.rs index 0a2b37507..12ad0dbbe 100644 --- a/miden-lib/src/notes/utils.rs +++ b/miden-lib/src/notes/utils.rs @@ -1,22 +1,11 @@ use miden_objects::{ accounts::AccountId, - assembly::ProgramAst, notes::{NoteInputs, NoteRecipient, NoteScript}, + utils::Deserializable, + vm::Program, NoteError, Word, }; -use crate::transaction::TransactionKernel; - -/// Creates the note_script from inputs -pub fn build_note_script(bytes: &[u8]) -> Result { - let note_assembler = TransactionKernel::assembler(); - - let script_ast = ProgramAst::from_bytes(bytes).map_err(NoteError::NoteDeserializationError)?; - let (note_script, _) = NoteScript::new(script_ast, ¬e_assembler)?; - - Ok(note_script) -} - /// Creates a [NoteRecipient] for the P2ID note. /// /// Notes created with this recipient will be P2ID notes consumable by the specified target @@ -25,10 +14,10 @@ pub fn build_p2id_recipient( target: AccountId, serial_num: Word, ) -> Result { - // TODO: add lazy_static initialization or compile-time optimization instead of re-generating - // the script hash every time we call the SWAP script let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/P2ID.masb")); - let note_script = build_note_script(bytes)?; + let program = + Program::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)?; + let note_script = NoteScript::new(program); let note_inputs = NoteInputs::new(vec![target.into()])?; Ok(NoteRecipient::new(serial_num, note_script, note_inputs)) diff --git a/miden-lib/src/tests/mod.rs b/miden-lib/src/tests/mod.rs deleted file mode 100644 index 4dca226f5..000000000 --- a/miden-lib/src/tests/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -/// NOTE: Most kernel-related tests can be found under /miden-tx/kernel_tests -// CONSTANTS -// ================================================================================================ -use miden_objects::assembly::Library; - -// TESTS -// ================================================================================================ - -#[test] -fn test_compile() { - let path = "miden::kernels::tx::memory::get_consumed_note_ptr"; - let miden = super::MidenLib::default(); - let exists = miden.modules().any(|module| { - module - .ast - .procs() - .iter() - .any(|proc| module.path.append(&proc.name).unwrap().as_str() == path) - }); - - assert!(exists); -} diff --git a/miden-lib/src/transaction/errors.rs b/miden-lib/src/transaction/errors.rs index 6a9827435..eade4d37a 100644 --- a/miden-lib/src/transaction/errors.rs +++ b/miden-lib/src/transaction/errors.rs @@ -2,8 +2,8 @@ use alloc::{string::String, vec::Vec}; use core::fmt; use miden_objects::{ - accounts::AccountStorage, notes::NoteMetadata, AccountError, AssetError, Digest, Felt, - NoteError, + accounts::AccountStorage, notes::NoteMetadata, AccountDeltaError, AccountError, AssetError, + Digest, Felt, NoteError, }; // TRANSACTION KERNEL ERROR @@ -11,6 +11,7 @@ use miden_objects::{ #[derive(Debug, Clone, Eq, PartialEq)] pub enum TransactionKernelError { + AccountDeltaError(AccountDeltaError), FailedToAddAssetToNote(NoteError), InvalidNoteInputs { expected: Digest, @@ -39,7 +40,7 @@ impl fmt::Display for TransactionKernelError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TransactionKernelError::FailedToAddAssetToNote(err) => { - write!(f, "failed to add asset to note: {err}") + write!(f, "Failed to add asset to note: {err}") }, TransactionKernelError::InvalidNoteInputs { expected, got, data } => { write!( @@ -50,7 +51,7 @@ impl fmt::Display for TransactionKernelError { }, TransactionKernelError::InvalidStorageSlotIndex(index) => { let num_slots = AccountStorage::NUM_STORAGE_SLOTS; - write!(f, "storage slot index {index} is invalid, must be smaller than {num_slots}") + write!(f, "Storage slot index {index} is invalid, must be smaller than {num_slots}") }, TransactionKernelError::MalformedAccountId(err) => { write!( f, "Account id data extracted from the stack by the event handler is not well formed {err}") @@ -59,7 +60,7 @@ impl fmt::Display for TransactionKernelError { write!(f, "Asset data extracted from the stack by the event handler is not well formed {err}") }, TransactionKernelError::MalformedAssetOnAccountVaultUpdate(err) => { - write!(f, "malformed asset during account vault update: {err}") + write!(f, "Malformed asset during account vault update: {err}") }, TransactionKernelError::MalformedNoteInputs(err) => { write!( f, "Note inputs data extracted from the advice map by the event handler is not well formed {err}") @@ -89,16 +90,19 @@ impl fmt::Display for TransactionKernelError { write!(f, "Public note missing or incomplete inputs in the advice provider") }, TransactionKernelError::MissingStorageSlotValue(index, err) => { - write!(f, "value for storage slot {index} could not be found: {err}") + write!(f, "Value for storage slot {index} could not be found: {err}") }, TransactionKernelError::TooFewElementsForNoteInputs => { write!( f, - "note input data in advice provider contains fewer elements than specified by its inputs length" + "Note input data in advice provider contains fewer elements than specified by its inputs length" ) }, TransactionKernelError::UnknownAccountProcedure(proc_root) => { - write!(f, "account procedure with root {proc_root} is not in the advice provider") + write!(f, "Account procedure with root {proc_root} is not in the advice provider") + }, + TransactionKernelError::AccountDeltaError(error) => { + write!(f, "Account delta error: {error}") }, } } diff --git a/miden-lib/src/transaction/events.rs b/miden-lib/src/transaction/events.rs index 5542495c8..aa2d90758 100644 --- a/miden-lib/src/transaction/events.rs +++ b/miden-lib/src/transaction/events.rs @@ -26,13 +26,13 @@ const ACCOUNT_STORAGE_AFTER_SET_MAP_ITEM: u32 = 0x2_0007; // 131079 const ACCOUNT_BEFORE_INCREMENT_NONCE: u32 = 0x2_0008; // 131080 const ACCOUNT_AFTER_INCREMENT_NONCE: u32 = 0x2_0009; // 131081 -const ACCOUNT_PUSH_PROCEDURE_INDEX: u32 = 0x2_000A; // 131082 +const ACCOUNT_PUSH_PROCEDURE_INDEX: u32 = 0x2_000a; // 131082 -const NOTE_BEFORE_CREATED: u32 = 0x2_000B; // 131083 -const NOTE_AFTER_CREATED: u32 = 0x2_000C; // 131084 +const NOTE_BEFORE_CREATED: u32 = 0x2_000b; // 131083 +const NOTE_AFTER_CREATED: u32 = 0x2_000c; // 131084 -const NOTE_BEFORE_ADD_ASSET: u32 = 0x2_000D; // 131085 -const NOTE_AFTER_ADD_ASSET: u32 = 0x2_000E; // 131086 +const NOTE_BEFORE_ADD_ASSET: u32 = 0x2_000d; // 131085 +const NOTE_AFTER_ADD_ASSET: u32 = 0x2_000e; // 131086 /// Events which may be emitted by a transaction kernel. /// diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index bf3ef565b..c25c834f2 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -2,77 +2,11 @@ use alloc::vec::Vec; use miden_objects::{ accounts::Account, - transaction::{ - ChainMmr, ExecutedTransaction, InputNote, InputNotes, PreparedTransaction, TransactionArgs, - TransactionInputs, TransactionScript, TransactionWitness, - }, - vm::{AdviceInputs, StackInputs}, + transaction::{ChainMmr, InputNote, TransactionArgs, TransactionInputs, TransactionScript}, + vm::AdviceInputs, Felt, FieldElement, Word, EMPTY_WORD, ZERO, }; -use super::TransactionKernel; - -// TRANSACTION KERNEL INPUTS -// ================================================================================================ - -/// Defines how inputs required to execute a transaction kernel can be extracted from self. -pub trait ToTransactionKernelInputs { - /// Returns stack and advice inputs required to execute the transaction kernel. - fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs); -} - -impl ToTransactionKernelInputs for PreparedTransaction { - fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { - let account = self.account(); - let stack_inputs = TransactionKernel::build_input_stack( - account.id(), - account.init_hash(), - self.input_notes().commitment(), - self.block_header().hash(), - ); - - let mut advice_inputs = AdviceInputs::default(); - extend_advice_inputs(self.tx_inputs(), self.tx_args(), &mut advice_inputs); - - (stack_inputs, advice_inputs) - } -} - -impl ToTransactionKernelInputs for ExecutedTransaction { - fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { - let account = self.initial_account(); - let stack_inputs = TransactionKernel::build_input_stack( - account.id(), - account.init_hash(), - self.input_notes().commitment(), - self.block_header().hash(), - ); - - let mut advice_inputs = self.advice_witness().clone(); - extend_advice_inputs(self.tx_inputs(), self.tx_args(), &mut advice_inputs); - - (stack_inputs, advice_inputs) - } -} - -impl ToTransactionKernelInputs for TransactionWitness { - fn get_kernel_inputs(&self) -> (StackInputs, AdviceInputs) { - let account = self.account(); - - let stack_inputs = TransactionKernel::build_input_stack( - account.id(), - account.init_hash(), - self.input_notes().commitment(), - self.block_header().hash(), - ); - - let mut advice_inputs = self.advice_witness().clone(); - extend_advice_inputs(self.tx_inputs(), self.tx_args(), &mut advice_inputs); - - (stack_inputs, advice_inputs) - } -} - // ADVICE INPUTS // ================================================================================================ @@ -82,7 +16,7 @@ impl ToTransactionKernelInputs for TransactionWitness { /// This includes the initial account, an optional account seed (required for new accounts), and /// the input note data, including core note data + authentication paths all the way to the root /// of one of chain MMR peaks. -fn extend_advice_inputs( +pub(super) fn extend_advice_inputs( tx_inputs: &TransactionInputs, tx_args: &TransactionArgs, advice_inputs: &mut AdviceInputs, @@ -92,8 +26,8 @@ fn extend_advice_inputs( // build the advice map and Merkle store for relevant components add_chain_mmr_to_advice_inputs(tx_inputs.block_chain(), advice_inputs); add_account_to_advice_inputs(tx_inputs.account(), tx_inputs.account_seed(), advice_inputs); - add_input_notes_to_advice_inputs(tx_inputs.input_notes(), tx_args, advice_inputs); - advice_inputs.extend_map(tx_args.advice_map().clone()); + add_input_notes_to_advice_inputs(tx_inputs, tx_args, advice_inputs); + advice_inputs.extend(tx_args.advice_inputs().clone()); } // ADVICE STACK BUILDER @@ -116,7 +50,7 @@ fn extend_advice_inputs( /// [account_id, 0, 0, account_nonce], /// ACCOUNT_VAULT_ROOT, /// ACCOUNT_STORAGE_ROOT, -/// ACCOUNT_CODE_ROOT, +/// ACCOUNT_CODE_COMMITMENT, /// number_of_input_notes, /// TX_SCRIPT_ROOT, /// ] @@ -149,13 +83,13 @@ fn build_advice_stack( inputs.extend_stack([account.id().into(), ZERO, ZERO, account.nonce()]); inputs.extend_stack(account.vault().commitment()); inputs.extend_stack(account.storage().root()); - inputs.extend_stack(account.code().root()); + inputs.extend_stack(account.code().commitment()); // push the number of input notes onto the stack inputs.extend_stack([Felt::from(tx_inputs.input_notes().num_notes() as u32)]); // push tx_script root onto the stack - inputs.extend_stack(tx_script.map_or(Word::default(), |script| **script.hash())); + inputs.extend_stack(tx_script.map_or(Word::default(), |script| *script.hash())); } // CHAIN MMR INJECTOR @@ -195,12 +129,11 @@ fn add_chain_mmr_to_advice_inputs(mmr: &ChainMmr, inputs: &mut AdviceInputs) { /// Inserts the following items into the Merkle store: /// - The Merkle nodes associated with the storage slots tree. /// - The Merkle nodes associated with the account vault tree. -/// - The Merkle nodes associated with the account code procedures tree. /// - If present, the Merkle nodes associated with the account storage maps. /// /// Inserts the following entries into the advice map: /// - The storage types commitment |-> storage slot types vector. -/// - The account procedure root |-> procedure index, for each account procedure. +/// - The account code commitment |-> procedures as elements and length. /// - The node |-> (key, value), for all leaf nodes of the asset vault SMT. /// - [account_id, 0, 0, 0] |-> account_seed, when account seed is provided. /// - If present, the Merkle leaves associated with the account storage maps. @@ -245,8 +178,10 @@ fn add_account_to_advice_inputs( // --- account code ------------------------------------------------------- let code = account.code(); - // extend the merkle store with account code tree - inputs.extend_merkle_store(code.procedure_tree().inner_nodes()); + // extend the advice_map with the account code data and number of procedures + let mut proc_elements: Vec = vec![(code.num_procedures() as u32).into()]; + proc_elements.append(&mut code.as_elements()); + inputs.extend_map([(code.commitment(), proc_elements)]); // --- account seed ------------------------------------------------------- if let Some(account_seed) = account_seed { @@ -265,29 +200,29 @@ fn add_account_to_advice_inputs( /// The advice provider is populated with: /// /// - For each note: -/// - The note's details (serial number, script root, and its' input / assets hash). +/// - The note's details (serial number, script root, and its input / assets hash). /// - The note's private arguments. /// - The note's public metadata. /// - The note's public inputs data. Prefixed by its length and padded to an even word length. /// - The note's asset padded. Prefixed by its length and padded to an even word length. -/// - For autheticated notes (determined by the `is_authenticated` flag): +/// - For authenticated notes (determined by the `is_authenticated` flag): /// - The note's authentication path against its block's note tree. /// - The block number, sub hash, note root. /// - The note's position in the note tree /// /// The data above is processed by `prologue::process_input_notes_data`. fn add_input_notes_to_advice_inputs( - notes: &InputNotes, + tx_inputs: &TransactionInputs, tx_args: &TransactionArgs, inputs: &mut AdviceInputs, ) { // if there are no input notes, nothing is added to the advice inputs - if notes.is_empty() { + if tx_inputs.input_notes().is_empty() { return; } let mut note_data = Vec::new(); - for input_note in notes.iter() { + for input_note in tx_inputs.input_notes().iter() { let note = input_note.note(); let assets = note.assets(); let recipient = note.recipient(); @@ -318,6 +253,16 @@ fn add_input_notes_to_advice_inputs( // insert note authentication path nodes into the Merkle store match input_note { InputNote::Authenticated { note, proof } => { + let block_num = proof.location().block_num(); + let note_block_header = if block_num == tx_inputs.block_header().block_num() { + tx_inputs.block_header() + } else { + tx_inputs + .block_chain() + .get_block(block_num) + .expect("block not found in chain MMR") + }; + // NOTE: keep in sync with the `prologue::process_input_note` kernel procedure // Push the `is_authenticated` flag note_data.push(Felt::ONE); @@ -326,20 +271,13 @@ fn add_input_notes_to_advice_inputs( inputs.extend_merkle_store( proof .note_path() - .inner_nodes(proof.origin().node_index.value(), note.hash()) + .inner_nodes(proof.location().node_index_in_block().into(), note.hash()) .unwrap(), ); - note_data.push(proof.origin().block_num.into()); - note_data.extend(*proof.sub_hash()); - note_data.extend(*proof.note_root()); - note_data.push( - proof - .origin() - .node_index - .value() - .try_into() - .expect("value is greater than or equal to the field modulus"), - ); + note_data.push(proof.location().block_num().into()); + note_data.extend(note_block_header.sub_hash()); + note_data.extend(note_block_header.note_root()); + note_data.push(proof.location().node_index_in_block().into()); }, InputNote::Unauthenticated { .. } => { // NOTE: keep in sync with the `prologue::process_input_note` kernel procedure @@ -350,5 +288,5 @@ fn add_input_notes_to_advice_inputs( } // NOTE: keep map in sync with the `prologue::process_input_notes_data` kernel procedure - inputs.extend_map([(notes.commitment(), note_data)]); + inputs.extend_map([(tx_inputs.input_notes().commitment(), note_data)]); } diff --git a/miden-lib/src/transaction/memory.rs b/miden-lib/src/transaction/memory.rs index d83c51526..6c10089ce 100644 --- a/miden-lib/src/transaction/memory.rs +++ b/miden-lib/src/transaction/memory.rs @@ -10,12 +10,25 @@ pub type StorageSlot = u8; // PUBLIC CONSTANTS // ================================================================================================ +// | Section | Start address | End address | +// | ------------- | :------------:| :-----------:| +// | Bookkeeping | 0 | 4 | +// | Global inputs | 100 | 105 | +// | Block header | 200 | 207 | +// | Chain MMR | 300 | 332? | +// | Account data | 400 | 651? | +// | Account procedures| 999 | ? | +// | Input notes | 1_048_576 | ? | +// | Output notes | 4_194_304 | ? | + // RESERVED ACCOUNT STORAGE SLOTS // ------------------------------------------------------------------------------------------------ /// The account storage slot at which faucet data is stored. -/// Fungible faucet: The faucet data consists of [0, 0, 0, total_issuance] -/// Non-fungible faucet: The faucet data consists of SMT root containing minted non-fungible assets. +/// +/// - Fungible faucet: The faucet data consists of [0, 0, 0, total_issuance]. +/// - Non-fungible faucet: The faucet data consists of SMT root containing minted non-fungible +/// assets. pub const FAUCET_STORAGE_DATA_SLOT: StorageSlot = 254; /// The account storage slot at which the slot types commitment is stored. @@ -27,11 +40,11 @@ pub const SLOT_TYPES_COMMITMENT_STORAGE_SLOT: StorageSlot = 255; /// The memory address at which the transaction vault root is stored. pub const TX_VAULT_ROOT_PTR: MemoryAddress = 0; -/// The memory address at which a pointer to the consumed note being executed is stored. -pub const CURRENT_CONSUMED_NOTE_PTR: MemoryAddress = 1; +/// The memory address at which a pointer to the input note being executed is stored. +pub const CURRENT_INPUT_NOTE_PTR: MemoryAddress = 1; -/// The memory address at which the number of created notes is stored. -pub const NUM_CREATED_NOTES_PTR: MemoryAddress = 2; +/// The memory address at which the number of output notes is stored. +pub const NUM_OUTPUT_NOTES_PTR: MemoryAddress = 2; /// The memory address at which the input vault root is stored pub const INPUT_VAULT_ROOT_PTR: MemoryAddress = 3; @@ -117,14 +130,14 @@ pub const CHAIN_MMR_PEAKS_PTR: MemoryAddress = 301; // ACCOUNT DATA // ------------------------------------------------------------------------------------------------ -/// The size of the memory segment allocated to core account data (excluding new code root) +/// The size of the memory segment allocated to core account data (excluding new code commitment) pub const ACCT_DATA_MEM_SIZE: MemSize = 4; /// The memory address at which the account data section begins pub const ACCT_DATA_SECTION_OFFSET: MemoryOffset = 400; -/// The offset at which the account id and nonce is stored relative to the start of the account -/// data segment. +/// The offset at which the account id and nonce is stored relative to the start of +/// the account data segment. pub const ACCT_ID_AND_NONCE_OFFSET: MemoryOffset = 0; /// The index of the account id within the account id and nonce data. @@ -154,30 +167,38 @@ pub const ACCT_STORAGE_ROOT_OFFSET: MemoryOffset = 2; pub const ACCT_STORAGE_ROOT_PTR: MemoryAddress = ACCT_DATA_SECTION_OFFSET + ACCT_STORAGE_ROOT_OFFSET; -/// The offset at which the account code root is stored relative to the start of the account +/// The offset at which the account code commitment is stored relative to the start of the account /// data segment. -pub const ACCT_CODE_ROOT_OFFSET: MemoryOffset = 3; +pub const ACCT_CODE_COMMITMENT_OFFSET: MemoryOffset = 3; -/// The memory address at which the account code root is stored. -pub const ACCT_CODE_ROOT_PTR: MemoryAddress = ACCT_DATA_SECTION_OFFSET + ACCT_CODE_ROOT_OFFSET; +/// The memory address at which the account code commitment is stored. +pub const ACCT_CODE_COMMITMENT_PTR: MemoryAddress = + ACCT_DATA_SECTION_OFFSET + ACCT_CODE_COMMITMENT_OFFSET; -/// The offset at which the accounts new code root is stored relative to the start of the account -/// data segment. -pub const ACCT_NEW_CODE_ROOT_OFFSET: MemoryOffset = 4; +/// The offset at which the accounts new code commitment is stored relative to the start of the +/// account data segment. +pub const ACCT_NEW_CODE_COMMITMENT_OFFSET: MemoryOffset = 4; -/// The memory address at which the new account code root is stored -pub const ACCT_NEW_CODE_ROOT_PTR: MemoryAddress = - ACCT_DATA_SECTION_OFFSET + ACCT_NEW_CODE_ROOT_OFFSET; +/// The memory address at which the new account code commitment is stored +pub const ACCT_NEW_CODE_COMMITMENT_PTR: MemoryAddress = + ACCT_DATA_SECTION_OFFSET + ACCT_NEW_CODE_COMMITMENT_OFFSET; -/// The memory address at which the account storage slot type data beings +/// The memory address at which the account storage slot type data begins pub const ACCT_STORAGE_SLOT_TYPE_DATA_OFFSET: MemoryAddress = 405; +/// The memory address at which the number of procedures contained in the account code is stored +pub const NUM_ACCT_PROCEDURES_PTR: MemoryAddress = 999; + +/// The memory address at which the account procedures section begins. +pub const ACCT_PROCEDURES_SECTION_OFFSET: MemoryAddress = 1000; + // NOTES DATA // ================================================================================================ /// The size of the memory segment allocated to each note. pub const NOTE_MEM_SIZE: MemoryAddress = 512; +#[rustfmt::skip] // INPUT NOTES DATA // ------------------------------------------------------------------------------------------------ // Inputs note section contains data of all notes consumed by a transaction. The section starts at @@ -203,25 +224,25 @@ pub const NOTE_MEM_SIZE: MemoryAddress = 512; // - INPUTS_HASH is the key to look up note inputs in the advice map. // - ASSETS_HASH is the key to look up note assets in the advice map. -/// The memory address at which the consumed note section begins. -pub const CONSUMED_NOTE_SECTION_OFFSET: MemoryOffset = 1_048_576; +/// The memory address at which the input note section begins. +pub const INPUT_NOTE_SECTION_OFFSET: MemoryOffset = 1_048_576; -/// The memory address at which the consumed note data section begins. -pub const CONSUMED_NOTE_DATA_SECTION_OFFSET: MemoryAddress = 1_064_960; +/// The memory address at which the input note data section begins. +pub const INPUT_NOTE_DATA_SECTION_OFFSET: MemoryAddress = 1_064_960; -/// The memory address at which the number of consumed notes is stored. -pub const CONSUMED_NOTE_NUM_PTR: MemoryAddress = CONSUMED_NOTE_SECTION_OFFSET; +/// The memory address at which the number of input notes is stored. +pub const NUM_INPUT_NOTES_PTR: MemoryAddress = INPUT_NOTE_SECTION_OFFSET; -/// The offsets at which data of a consumed note is stored relative to the start of its data segment. -pub const CONSUMED_NOTE_ID_OFFSET: MemoryOffset = 0; -pub const CONSUMED_NOTE_SERIAL_NUM_OFFSET: MemoryOffset = 1; -pub const CONSUMED_NOTE_SCRIPT_ROOT_OFFSET: MemoryOffset = 2; -pub const CONSUMED_NOTE_INPUTS_HASH_OFFSET: MemoryOffset = 3; -pub const CONSUMED_NOTE_ASSETS_HASH_OFFSET: MemoryOffset = 4; -pub const CONSUMED_NOTE_METADATA_OFFSET: MemoryOffset = 5; -pub const CONSUMED_NOTE_ARGS_OFFSET: MemoryOffset = 6; -pub const CONSUMED_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 7; -pub const CONSUMED_NOTE_ASSETS_OFFSET: MemoryOffset = 8; +/// The offsets at which data of a input note is stored relative to the start of its data segment. +pub const INPUT_NOTE_ID_OFFSET: MemoryOffset = 0; +pub const INPUT_NOTE_SERIAL_NUM_OFFSET: MemoryOffset = 1; +pub const INPUT_NOTE_SCRIPT_ROOT_OFFSET: MemoryOffset = 2; +pub const INPUT_NOTE_INPUTS_HASH_OFFSET: MemoryOffset = 3; +pub const INPUT_NOTE_ASSETS_HASH_OFFSET: MemoryOffset = 4; +pub const INPUT_NOTE_METADATA_OFFSET: MemoryOffset = 5; +pub const INPUT_NOTE_ARGS_OFFSET: MemoryOffset = 6; +pub const INPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 7; +pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 8; // OUTPUT NOTES DATA // ------------------------------------------------------------------------------------------------ @@ -244,16 +265,16 @@ pub const CONSUMED_NOTE_ASSETS_OFFSET: MemoryOffset = 8; // Even though NUM_ASSETS takes up a while word, the actual value of this variable is stored in the // first element of the word. -/// The memory address at which the created notes section begins. -pub const CREATED_NOTE_SECTION_OFFSET: MemoryOffset = 4_194_304; +/// The memory address at which the output notes section begins. +pub const OUTPUT_NOTE_SECTION_OFFSET: MemoryOffset = 4_194_304; -/// The size of the core created note data segment. -pub const CREATED_NOTE_CORE_DATA_SIZE: MemSize = 4; +/// The size of the core output note data segment. +pub const OUTPUT_NOTE_CORE_DATA_SIZE: MemSize = 4; -/// The offsets at which data of a created note is stored relative to the start of its data segment. -pub const CREATED_NOTE_ID_OFFSET: MemoryOffset = 0; -pub const CREATED_NOTE_METADATA_OFFSET: MemoryOffset = 1; -pub const CREATED_NOTE_RECIPIENT_OFFSET: MemoryOffset = 2; -pub const CREATED_NOTE_ASSET_HASH_OFFSET: MemoryOffset = 3; -pub const CREATED_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 4; -pub const CREATED_NOTE_ASSETS_OFFSET: MemoryOffset = 5; +/// The offsets at which data of a output note is stored relative to the start of its data segment. +pub const OUTPUT_NOTE_ID_OFFSET: MemoryOffset = 0; +pub const OUTPUT_NOTE_METADATA_OFFSET: MemoryOffset = 1; +pub const OUTPUT_NOTE_RECIPIENT_OFFSET: MemoryOffset = 2; +pub const OUTPUT_NOTE_ASSET_HASH_OFFSET: MemoryOffset = 3; +pub const OUTPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 4; +pub const OUTPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 5; diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index c0a4a0d5d..f4d4e42f8 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -1,11 +1,13 @@ -use alloc::{string::ToString, vec::Vec}; +use alloc::{string::ToString, sync::Arc, vec::Vec}; use miden_objects::{ accounts::AccountId, - assembly::{Assembler, AssemblyContext, ProgramAst}, - transaction::{OutputNote, OutputNotes, TransactionOutputs}, - utils::{group_slice_elements, serde::DeserializationError}, - vm::{AdviceMap, ProgramInfo, StackInputs, StackOutputs}, + assembly::{Assembler, DefaultSourceManager, KernelLibrary}, + transaction::{ + OutputNote, OutputNotes, TransactionArgs, TransactionInputs, TransactionOutputs, + }, + utils::{group_slice_elements, serde::Deserializable}, + vm::{AdviceInputs, AdviceMap, Program, ProgramInfo, StackInputs, StackOutputs}, Digest, Felt, TransactionOutputError, Word, EMPTY_WORD, }; use miden_stdlib::StdLibrary; @@ -18,7 +20,6 @@ mod events; pub use events::{TransactionEvent, TransactionTrace}; mod inputs; -pub use inputs::ToTransactionKernelInputs; mod outputs; pub use outputs::{ @@ -30,6 +31,14 @@ pub use errors::{ TransactionEventParsingError, TransactionKernelError, TransactionTraceParsingError, }; +// CONSTANTS +// ================================================================================================ + +const KERNEL_LIB_BYTES: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_kernel.masl")); +const KERNEL_MAIN_BYTES: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_kernel.masb")); + // TRANSACTION KERNEL // ================================================================================================ @@ -39,19 +48,24 @@ impl TransactionKernel { // KERNEL SOURCE CODE // -------------------------------------------------------------------------------------------- - /// Returns MASM source code which encodes the transaction kernel system procedures. - pub fn kernel() -> &'static str { - include_str!("../../asm/kernels/transaction/api.masm") + /// Returns a library with the transaction kernel system procedures. + /// + /// # Panics + /// Panics if the transaction kernel source is not well-formed. + pub fn kernel() -> KernelLibrary { + // TODO: make this static + KernelLibrary::read_from_bytes(KERNEL_LIB_BYTES) + .expect("failed to deserialize transaction kernel library") } /// Returns an AST of the transaction kernel executable program. /// - /// # Errors - /// Returns an error if deserialization of the binary fails. - pub fn main() -> Result { - let kernel_bytes = - include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/transaction.masb")); - ProgramAst::from_bytes(kernel_bytes) + /// # Panics + /// Panics if the transaction kernel source is not well-formed. + pub fn main() -> Program { + // TODO: make static + Program::read_from_bytes(KERNEL_MAIN_BYTES) + .expect("failed to deserialize transaction kernel runtime") } /// Returns [ProgramInfo] for the transaction kernel executable program. @@ -59,29 +73,48 @@ impl TransactionKernel { /// # Panics /// Panics if the transaction kernel source is not well-formed. pub fn program_info() -> ProgramInfo { - // TODO: construct kernel_main and kernel using lazy static or at build time - let assembler = Self::assembler(); - let main_ast = TransactionKernel::main().expect("main is well formed"); - let kernel_main = assembler - .compile_in_context(&main_ast, &mut AssemblyContext::for_program(Some(&main_ast))) - .expect("main is well formed"); - - ProgramInfo::new(kernel_main.hash(), assembler.kernel().clone()) + // TODO: make static + let program_hash = Self::main().hash(); + let kernel = Self::kernel().kernel().clone(); + + ProgramInfo::new(program_hash, kernel) + } + + /// Transforms the provided [TransactionInputs] and [TransactionArgs] into stack and advice + /// inputs needed to execute a transaction kernel for a specific transaction. + /// + /// If `init_advice_inputs` is provided, they will be included in the returned advice inputs. + pub fn prepare_inputs( + tx_inputs: &TransactionInputs, + tx_args: &TransactionArgs, + init_advice_inputs: Option, + ) -> (StackInputs, AdviceInputs) { + let account = tx_inputs.account(); + let stack_inputs = TransactionKernel::build_input_stack( + account.id(), + account.init_hash(), + tx_inputs.input_notes().commitment(), + tx_inputs.block_header().hash(), + ); + + let mut advice_inputs = init_advice_inputs.unwrap_or_default(); + inputs::extend_advice_inputs(tx_inputs, tx_args, &mut advice_inputs); + + (stack_inputs, advice_inputs) } // ASSEMBLER CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Returns a new Miden assembler instantiated with the transaction kernel and loaded with the - /// Miden stdlib as well as with midenlib. + /// Miden stdlib as well as with miden-lib. pub fn assembler() -> Assembler { - Assembler::default() - .with_library(&MidenLib::default()) - .expect("failed to load miden-lib") - .with_library(&StdLibrary::default()) + let source_manager = Arc::new(DefaultSourceManager::default()); + Assembler::with_kernel(source_manager, Self::kernel()) + .with_library(StdLibrary::default()) .expect("failed to load std-lib") - .with_kernel(Self::kernel()) - .expect("kernel must be well formed") + .with_library(MidenLib::default()) + .expect("failed to load miden-lib") } // STACK INPUTS / OUTPUTS @@ -133,8 +166,8 @@ impl TransactionKernel { /// /// Where: /// - CNC is the commitment to the notes created by the transaction. - /// - FAH is the final account hash of the account that the transaction is being - /// executed against. + /// - FAH is the final account hash of the account that the transaction is being executed + /// against. /// /// # Errors /// Returns an error if: @@ -183,8 +216,8 @@ impl TransactionKernel { /// /// Where: /// - CNC is the commitment to the notes created by the transaction. - /// - FAH is the final account hash of the account that the transaction is being - /// executed against. + /// - FAH is the final account hash of the account that the transaction is being executed + /// against. /// /// The actual data describing the new account state and output notes is expected to be located /// in the provided advice map under keys CNC and FAH. @@ -216,3 +249,34 @@ impl TransactionKernel { Ok(TransactionOutputs { account, output_notes }) } } + +#[cfg(feature = "testing")] +impl TransactionKernel { + const KERNEL_TESTING_LIB_BYTES: &'static [u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/kernel_library.masl")); + + pub fn kernel_as_library() -> miden_objects::assembly::Library { + miden_objects::assembly::Library::read_from_bytes(Self::KERNEL_TESTING_LIB_BYTES) + .expect("failed to deserialize transaction kernel library") + } + + /// Contains code to get an instance of the [Assembler] that should be used in tests. + /// + /// This assembler is similar to the assembler used to assemble the kernel and transactions, + /// with the difference that it also includes an extra library on the namespace of `kernel`. + /// The `kernel` library is added separately because even though the library (`api.masm`) and + /// the kernel binary (`main.masm`) include this code, it is not exposed explicitly. By adding + /// it separately, we can expose procedures from `/lib` and test them individually. + pub fn assembler_testing() -> Assembler { + let source_manager = Arc::new(DefaultSourceManager::default()); + let kernel_library = Self::kernel_as_library(); + + Assembler::with_kernel(source_manager, Self::kernel()) + .with_library(StdLibrary::default()) + .expect("failed to load std-lib") + .with_library(MidenLib::default()) + .expect("failed to load miden-lib") + .with_library(kernel_library) + .expect("failed to load kernel library (/lib)") + } +} diff --git a/miden-lib/src/transaction/outputs.rs b/miden-lib/src/transaction/outputs.rs index d7bd411a5..79f33767c 100644 --- a/miden-lib/src/transaction/outputs.rs +++ b/miden-lib/src/transaction/outputs.rs @@ -4,7 +4,7 @@ use miden_objects::{ }; use super::memory::{ - ACCT_CODE_ROOT_OFFSET, ACCT_DATA_MEM_SIZE, ACCT_ID_AND_NONCE_OFFSET, ACCT_ID_IDX, + ACCT_CODE_COMMITMENT_OFFSET, ACCT_DATA_MEM_SIZE, ACCT_ID_AND_NONCE_OFFSET, ACCT_ID_IDX, ACCT_NONCE_IDX, ACCT_STORAGE_ROOT_OFFSET, ACCT_VAULT_ROOT_OFFSET, }; @@ -21,7 +21,7 @@ pub const FINAL_ACCOUNT_HASH_WORD_IDX: usize = 1; // ================================================================================================ /// Parses the stub account data returned by the VM into individual account component commitments. -/// Returns a tuple of account ID, vault root, storage root, code root, and nonce. +/// Returns a tuple of account ID, vault root, storage root, code commitment, and nonce. pub fn parse_final_account_stub(elements: &[Word]) -> Result { if elements.len() != ACCT_DATA_MEM_SIZE { return Err(AccountError::StubDataIncorrectLength(elements.len(), ACCT_DATA_MEM_SIZE)); @@ -31,7 +31,7 @@ pub fn parse_final_account_stub(elements: &[Word]) -> Result>, - kernel_main: CodeBlock, -} - -impl TransactionCompiler { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [TransactionCompiler]. - pub fn new() -> TransactionCompiler { - let assembler = TransactionKernel::assembler(); - - // compile transaction kernel main - let main_ast = TransactionKernel::main().expect("main is well formed"); - let kernel_main = assembler - .compile_in_context(&main_ast, &mut AssemblyContext::for_program(Some(&main_ast))) - .expect("main is well formed"); - - TransactionCompiler { - assembler, - account_procedures: BTreeMap::default(), - kernel_main, - } - } - - /// Puts the [TransactionCompiler] into debug mode. - /// - /// When transaction compiler is in debug mode, all transaction-related code (note scripts, - /// account code) will be compiled in debug mode which will preserve debug artifacts from the - /// original source code. - pub fn with_debug_mode(mut self, in_debug_mode: bool) -> Self { - self.assembler = self.assembler.with_debug_mode(in_debug_mode); - self - } - - // ACCOUNT CODE AND NOTE SCRIPT COMPILERS - // -------------------------------------------------------------------------------------------- - - /// Compiles the provided module into [AccountCode] and associates the resulting procedures - /// with the specified account ID. - pub fn load_account( - &mut self, - account_id: AccountId, - account_code: ModuleAst, - ) -> Result { - let account_code = AccountCode::new(account_code, &self.assembler) - .map_err(TransactionCompilerError::LoadAccountFailed)?; - self.account_procedures.insert(account_id, account_code.procedures().to_vec()); - Ok(account_code) - } - - /// Loads the provided account interface (vector of procedure digests) into this compiler. - /// Returns the old account interface if it previously existed. - pub fn load_account_interface( - &mut self, - account_id: AccountId, - procedures: Vec, - ) -> Option> { - self.account_procedures.insert(account_id, procedures) - } - - /// Compiles the provided program into the [NoteScript] and checks (to the extent possible) - /// if a note could be executed against all accounts with the specified interfaces. - pub fn compile_note_script( - &self, - note_script_ast: ProgramAst, - target_account_proc: Vec, - ) -> Result { - let (note_script, code_block) = - NoteScript::new(note_script_ast, &self.assembler).map_err(|err| match err { - NoteError::ScriptCompilationError(err) => { - TransactionCompilerError::CompileNoteScriptFailed(err) - }, - _ => TransactionCompilerError::NoteScriptError(err), - })?; - for note_target in target_account_proc.into_iter() { - verify_program_account_compatibility( - &code_block, - &self.get_target_interface(note_target)?, - ScriptType::NoteScript, - )?; - } - - Ok(note_script) - } - - /// Constructs a [TransactionScript] by compiling the provided source code and checking the - /// compatibility of the resulting program with the target account interfaces. - pub fn compile_tx_script( - &self, - tx_script_ast: ProgramAst, - tx_script_inputs: T, - target_account_proc: Vec, - ) -> Result - where - T: IntoIterator)>, - { - let (tx_script, code_block) = - TransactionScript::new(tx_script_ast, tx_script_inputs, &self.assembler).map_err( - |e| match e { - TransactionScriptError::ScriptCompilationError(asm_error) => { - TransactionCompilerError::CompileTxScriptFailed(asm_error) - }, - }, - )?; - for target in target_account_proc.into_iter() { - verify_program_account_compatibility( - &code_block, - &self.get_target_interface(target)?, - ScriptType::TransactionScript, - )?; - } - Ok(tx_script) - } - - // TRANSACTION PROGRAM BUILDER - // -------------------------------------------------------------------------------------------- - /// Compiles a transaction which executes the provided notes and an optional tx script against - /// the specified account. Returns the compiled transaction program. - /// - /// The account is assumed to have been previously loaded into this compiler. - pub fn compile_transaction( - &self, - account_id: AccountId, - notes: &InputNotes, - tx_script: Option<&ProgramAst>, - ) -> Result { - // Fetch the account interface from the `account_procedures` map. Return an error if the - // interface is not found. - let target_account_interface = self - .account_procedures - .get(&account_id) - .cloned() - .ok_or(TransactionCompilerError::AccountInterfaceNotFound(account_id))?; - - // Transaction must contain at least one input note or a transaction script - if notes.is_empty() && tx_script.is_none() { - return Err(TransactionCompilerError::NoTransactionDriver); - } - - // Create the [AssemblyContext] for compilation of notes scripts and the transaction script - let mut assembly_context = AssemblyContext::for_program(None); - - // Compile note scripts - let note_script_programs = - self.compile_notes(&target_account_interface, notes, &mut assembly_context)?; - - // Compile the transaction script - let tx_script_program = match tx_script { - Some(tx_script) => Some(self.compile_tx_script_program( - tx_script, - &mut assembly_context, - target_account_interface, - )?), - None => None, - }; - - // Create [CodeBlockTable] from [AssemblyContext] - let mut cb_table = self - .assembler - .build_cb_table(assembly_context) - .map_err(TransactionCompilerError::BuildCodeBlockTableFailed)?; - - // insert note roots into [CodeBlockTable] - note_script_programs.into_iter().for_each(|note_root| { - cb_table.insert(note_root); - }); - - // insert transaction script into [CodeBlockTable] - if let Some(tx_script_program) = tx_script_program { - cb_table.insert(tx_script_program); - } - - // Create transaction program with kernel - let program = Program::with_kernel( - self.kernel_main.clone(), - self.assembler.kernel().clone(), - cb_table, - ); - - // Create compiled transaction - Ok(program) - } - - // HELPER METHODS - // -------------------------------------------------------------------------------------------- - - /// Compiles the provided notes into [CodeBlock]s (programs) and verifies that each note is - /// compatible with the target account interfaces. Returns a vector of the compiled note - /// programs. - fn compile_notes( - &self, - target_account_interface: &[Digest], - notes: &InputNotes, - assembly_context: &mut AssemblyContext, - ) -> Result, TransactionCompilerError> { - let mut note_programs = Vec::new(); - - // Create and verify note programs. Note programs are verified against the target account. - for recorded_note in notes.iter() { - let note_program = self - .assembler - .compile_in_context(recorded_note.note().script().code(), assembly_context) - .map_err(TransactionCompilerError::CompileNoteScriptFailed)?; - verify_program_account_compatibility( - ¬e_program, - target_account_interface, - ScriptType::NoteScript, - )?; - note_programs.push(note_program); - } - - Ok(note_programs) - } - - /// Returns a [CodeBlock] of the compiled transaction script program. - /// - /// The transaction script compatibility is verified against the target account interface. - fn compile_tx_script_program( - &self, - tx_script: &ProgramAst, - assembly_context: &mut AssemblyContext, - target_account_interface: Vec, - ) -> Result { - let tx_script_code_block = self - .assembler - .compile_in_context(tx_script, assembly_context) - .map_err(TransactionCompilerError::CompileTxScriptFailed)?; - verify_program_account_compatibility( - &tx_script_code_block, - &target_account_interface, - ScriptType::TransactionScript, - )?; - Ok(tx_script_code_block) - } - - /// Returns the account interface associated with the provided [ScriptTarget]. - /// - /// # Errors - /// - If the account interface associated with the [AccountId] provided as a target can not be - /// found in the `account_procedures` map. - fn get_target_interface( - &self, - target: ScriptTarget, - ) -> Result, TransactionCompilerError> { - match target { - ScriptTarget::AccountId(id) => self - .account_procedures - .get(&id) - .cloned() - .ok_or(TransactionCompilerError::AccountInterfaceNotFound(id)), - ScriptTarget::Procedures(procs) => Ok(procs), - } - } -} - -impl Default for TransactionCompiler { - fn default() -> Self { - Self::new() - } -} - -// TRANSACTION COMPILER HELPERS -// ------------------------------------------------------------------------------------------------ - -/// Verifies that the provided program is compatible with the target account interface. -/// -/// This is achieved by checking that at least one execution branch in the program is compatible -/// with the target account interface. -/// -/// # Errors -/// Returns an error if the program is not compatible with the target account interface. -fn verify_program_account_compatibility( - program: &CodeBlock, - target_account_interface: &[Digest], - script_type: ScriptType, -) -> Result<(), TransactionCompilerError> { - // collect call branches - let branches = collect_call_branches(program); - - // if none of the branches are compatible with the target account, return an error - if !branches.iter().any(|call_targets| { - call_targets.iter().all(|target| target_account_interface.contains(target)) - }) { - return match script_type { - ScriptType::NoteScript => { - Err(TransactionCompilerError::NoteIncompatibleWithAccountInterface(program.hash())) - }, - ScriptType::TransactionScript => Err( - TransactionCompilerError::TxScriptIncompatibleWithAccountInterface(program.hash()), - ), - }; - } - - Ok(()) -} - -/// Collect call branches by recursively traversing through program execution branches and -/// accumulating call targets. -fn collect_call_branches(code_block: &CodeBlock) -> Vec> { - let mut branches = vec![vec![]]; - recursively_collect_call_branches(code_block, &mut branches); - branches -} - -/// Generates a list of calls invoked in each execution branch of the provided code block. -fn recursively_collect_call_branches(code_block: &CodeBlock, branches: &mut Vec>) { - match code_block { - CodeBlock::Join(block) => { - recursively_collect_call_branches(block.first(), branches); - recursively_collect_call_branches(block.second(), branches); - }, - CodeBlock::Split(block) => { - let current_len = branches.last().expect("at least one execution branch").len(); - recursively_collect_call_branches(block.on_false(), branches); - - // If the previous branch had additional calls we need to create a new branch - if branches.last().expect("at least one execution branch").len() > current_len { - branches.push( - branches.last().expect("at least one execution branch")[..current_len].to_vec(), - ); - } - - recursively_collect_call_branches(block.on_true(), branches); - }, - CodeBlock::Loop(block) => { - recursively_collect_call_branches(block.body(), branches); - }, - CodeBlock::Call(block) => { - if block.is_syscall() { - return; - } - - branches - .last_mut() - .expect("at least one execution branch") - .push(block.fn_hash()); - }, - CodeBlock::Span(_) => {}, - CodeBlock::Proxy(_) => {}, - CodeBlock::Dyn(_) => {}, - } -} - -// SCRIPT TARGET -// ================================================================================================ - -/// The [ScriptTarget] enum is used to specify the target account interface for note and -/// transaction scripts. -/// -/// This is specified as an account ID (for which the interface should be fetched) or a vector of -/// procedure digests which represents the account interface. -#[derive(Clone)] -pub enum ScriptTarget { - AccountId(AccountId), - Procedures(Vec), -} - -// SCRIPT TYPE -// ================================================================================================ - -enum ScriptType { - NoteScript, - TransactionScript, -} diff --git a/miden-tx/src/compiler/tests.rs b/miden-tx/src/compiler/tests.rs deleted file mode 100644 index a11436ac3..000000000 --- a/miden-tx/src/compiler/tests.rs +++ /dev/null @@ -1,218 +0,0 @@ -use alloc::vec::Vec; - -use miden_objects::{ - accounts::account_id::testing::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, - }, - assets::{Asset, FungibleAsset}, - notes::{ - Note, NoteAssets, NoteInclusionProof, NoteInputs, NoteMetadata, NoteRecipient, NoteType, - }, - transaction::{InputNote, InputNotes}, - Felt, Word, ZERO, -}; - -use super::{AccountId, ModuleAst, ProgramAst, ScriptTarget, TransactionCompiler}; - -// CONSTANTS -// ================================================================================================ - -// Mast roots of account procedures: -const ACCT_PROC_1: &str = "0x8ef0092134469a1330e3c468f57c7f085ce611645d09cc7516c786fefc71d794"; -const ACCT_PROC_2: &str = "0xff06b90f849c4b262cbfbea67042c4ea017ea0e9c558848a951d44b23370bec5"; -const ACCOUNT_CODE_MASM: &str = "\ -export.account_procedure_1 - push.1.2 - add -end - -export.account_procedure_2 - push.2.1 - sub -end -"; - -// Mast roots of additional procedures: -const ADD_PROC_1: &str = "0x5b6f7afcde4aaf538519c3bf5bb9321fac83cd769a3100c0b1225c9a6d75c9a1"; -const ADD_PROC_2: &str = "0xd4b1f9fbad5d0e6d2386509eab6a865298db20095d7315226dfa513ce017c990"; -const ADDITIONAL_PROCEDURES: &str = "\ -export.additional_procedure_1 - push.3.4 - add -end - -export.additional_procedure_2 - push.4.5 - add -end -"; - -// TESTS -// ================================================================================================ - -#[test] -fn test_load_account() { - let mut tx_compiler = TransactionCompiler::new(); - let account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap(); - let account_code_ast = ModuleAst::parse(ACCOUNT_CODE_MASM).unwrap(); - let account_code = tx_compiler.load_account(account_id, account_code_ast).unwrap(); - - let acct_procs = [hex_to_bytes(ACCT_PROC_1), hex_to_bytes(ACCT_PROC_2)]; - for proc in account_code.procedures() { - assert!(acct_procs.contains(&proc.as_bytes().to_vec())); - } -} - -#[test] -fn test_compile_valid_note_script() { - let test_cases = [ - ( - format!( - "begin - call.{ACCT_PROC_1} - call.{ACCT_PROC_2} - end" - ), - true, - ), - ( - format!( - "begin - if.true - call.{ACCT_PROC_1} - if.true - call.{ACCT_PROC_2} - else - call.{ADD_PROC_1} - end - else - call.{ADD_PROC_2} - end - end" - ), - true, - ), - ( - format!( - "begin - call.{ACCT_PROC_1} - if.true - call.{ADD_PROC_1} - else - call.{ADD_PROC_2} - end - end" - ), - false, - ), - ]; - - let mut tx_compiler = TransactionCompiler::new(); - let account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap(); - let account_code_ast = ModuleAst::parse(ACCOUNT_CODE_MASM).unwrap(); - let _account_code = tx_compiler.load_account(account_id, account_code_ast).unwrap(); - let target_account_proc = ScriptTarget::AccountId(account_id); - - // TODO: replace this with anonymous call targets once they are implemented - let account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2).unwrap(); - let account_code_ast = ModuleAst::parse(ADDITIONAL_PROCEDURES).unwrap(); - tx_compiler.load_account(account_id, account_code_ast).unwrap(); - - for (note_script_src, expected) in test_cases { - let note_script_ast = ProgramAst::parse(note_script_src.as_str()).unwrap(); - - let result = - tx_compiler.compile_note_script(note_script_ast, vec![target_account_proc.clone()]); - match expected { - true => assert!(result.is_ok()), - false => assert!(result.is_err()), - } - } -} - -fn mock_consumed_notes( - tx_compiler: &mut TransactionCompiler, - target_account: AccountId, -) -> Vec { - // Note Assets - let faucet_id_1 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1).unwrap(); - let faucet_id_3 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2).unwrap(); - let fungible_asset_1: Asset = FungibleAsset::new(faucet_id_1, 100).unwrap().into(); - let fungible_asset_2: Asset = FungibleAsset::new(faucet_id_2, 200).unwrap().into(); - let fungible_asset_3: Asset = FungibleAsset::new(faucet_id_3, 300).unwrap().into(); - - let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - - // create note script - let note_program_ast = - ProgramAst::parse(format!("begin call.{ACCT_PROC_1} drop end").as_str()).unwrap(); - let note_script = tx_compiler - .compile_note_script(note_program_ast, vec![ScriptTarget::AccountId(target_account)]) - .unwrap(); - - // Consumed Notes - const SERIAL_NUM_1: Word = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; - let vault = - NoteAssets::new(vec![fungible_asset_1, fungible_asset_2, fungible_asset_3]).unwrap(); - let metadata = NoteMetadata::new(sender, NoteType::Public, 0.into(), ZERO).unwrap(); - let inputs = NoteInputs::new(vec![Felt::new(1)]).unwrap(); - let recipient = NoteRecipient::new(SERIAL_NUM_1, note_script.clone(), inputs); - let note_1 = Note::new(vault, metadata, recipient); - - const SERIAL_NUM_2: Word = [Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]; - let vault = - NoteAssets::new(vec![fungible_asset_1, fungible_asset_2, fungible_asset_3]).unwrap(); - let metadata = NoteMetadata::new(sender, NoteType::Public, 0.into(), ZERO).unwrap(); - let inputs = NoteInputs::new(vec![Felt::new(2)]).unwrap(); - let recipient = NoteRecipient::new(SERIAL_NUM_2, note_script, inputs); - let note_2 = Note::new(vault, metadata, recipient); - - vec![note_1, note_2] -} - -#[test] -fn test_transaction_compilation_succeeds() { - let mut tx_compiler = TransactionCompiler::new(); - let account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap(); - let account_code_ast = ModuleAst::parse(ACCOUNT_CODE_MASM).unwrap(); - let _account_code = tx_compiler.load_account(account_id, account_code_ast).unwrap(); - - let notes = mock_consumed_notes(&mut tx_compiler, account_id); - let mock_inclusion_proof = NoteInclusionProof::new( - Default::default(), - Default::default(), - Default::default(), - 0, - Default::default(), - ) - .unwrap(); - let notes = notes - .into_iter() - .map(|note| InputNote::authenticated(note, mock_inclusion_proof.clone())) - .collect::>(); - - let notes = InputNotes::new(notes).unwrap(); - - let tx_script_src = format!("begin call.{ACCT_PROC_2} end"); - let tx_script_ast = ProgramAst::parse(tx_script_src.as_str()).unwrap(); - - let res = tx_compiler.compile_transaction(account_id, ¬es, Some(&tx_script_ast)); - assert!(res.is_ok()); -} - -// HELPERS -// ================================================================================================ - -fn hex_to_bytes(hex: &str) -> Vec { - (2..hex.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()) - .collect::>() -} diff --git a/miden-tx/src/error.rs b/miden-tx/src/error.rs index eabce6342..31bb6322e 100644 --- a/miden-tx/src/error.rs +++ b/miden-tx/src/error.rs @@ -2,12 +2,11 @@ use alloc::string::String; use core::fmt::{self, Display}; use miden_objects::{ - assembly::AssemblyError, notes::NoteId, Felt, NoteError, ProvenTransactionError, - TransactionInputError, TransactionOutputError, + accounts::AccountId, notes::NoteId, AccountError, Digest, Felt, NoteError, + ProvenTransactionError, TransactionInputError, TransactionOutputError, TransactionScriptError, }; use miden_verifier::VerificationError; - -use super::{AccountError, AccountId, Digest, ExecutionError}; +use vm_processor::ExecutionError; // TRANSACTION COMPILER ERROR // ================================================================================================ @@ -15,9 +14,6 @@ use super::{AccountError, AccountId, Digest, ExecutionError}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum TransactionCompilerError { AccountInterfaceNotFound(AccountId), - BuildCodeBlockTableFailed(AssemblyError), - CompileNoteScriptFailed(AssemblyError), - CompileTxScriptFailed(AssemblyError), LoadAccountFailed(AccountError), NoteIncompatibleWithAccountInterface(Digest), NoteScriptError(NoteError), @@ -39,8 +35,7 @@ impl std::error::Error for TransactionCompilerError {} #[derive(Debug, Clone, PartialEq, Eq)] pub enum TransactionExecutorError { - CompileNoteScriptFailed(TransactionCompilerError), - CompileTransactionScriptFailed(TransactionCompilerError), + CompileTransactionScriptFailed(TransactionScriptError), CompileTransactionFailed(TransactionCompilerError), ExecuteTransactionProgramFailed(ExecutionError), FetchAccountCodeFailed(DataStoreError), @@ -55,6 +50,7 @@ pub enum TransactionExecutorError { }, InvalidTransactionOutput(TransactionOutputError), LoadAccountFailed(TransactionCompilerError), + TransactionHostCreationFailed(TransactionHostError), } impl fmt::Display for TransactionExecutorError { @@ -75,6 +71,7 @@ pub enum TransactionProverError { InvalidAccountDelta(AccountError), InvalidTransactionOutput(TransactionOutputError), ProvenTransactionError(ProvenTransactionError), + TransactionHostCreationFailed(TransactionHostError), } impl Display for TransactionProverError { @@ -92,6 +89,9 @@ impl Display for TransactionProverError { TransactionProverError::ProvenTransactionError(inner) => { write!(f, "Building proven transaction error: {}", inner) }, + TransactionProverError::TransactionHostCreationFailed(inner) => { + write!(f, "Failed to create the transaction host: {}", inner) + }, } } } @@ -117,6 +117,23 @@ impl fmt::Display for TransactionVerifierError { #[cfg(feature = "std")] impl std::error::Error for TransactionVerifierError {} +// TRANSACTION HOST ERROR +// ================================================================================================ + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TransactionHostError { + AccountProcedureIndexMapError(String), +} + +impl fmt::Display for TransactionHostError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionHostError {} + // DATA STORE ERROR // ================================================================================================ @@ -244,8 +261,12 @@ const ERR_NOTE_TAG_MUST_BE_U32: u32 = 131142; const ERR_SETTING_NON_VALUE_ITEM_ON_VALUE_SLOT: u32 = 131143; const ERR_SETTING_MAP_ITEM_ON_NON_MAP_SLOT: u32 = 131144; const ERR_READING_MAP_VALUE_FROM_NON_MAP_SLOT: u32 = 131145; +const ERR_PROC_NOT_PART_OF_ACCOUNT_CODE: u32 = 131146; +const ERR_PROC_INDEX_OUT_OF_BOUNDS: u32 = 131147; +const ERR_ACCT_CODE_HASH_MISMATCH: u32 = 131148; +const ERR_ACCT_TOO_MANY_PROCEDURES: u32 = 131149; -pub const KERNEL_ERRORS: [(u32, &str); 75] = [ +pub const KERNEL_ERRORS: [(u32, &str); 79] = [ (ERR_FAUCET_RESERVED_DATA_SLOT, "For faucets, storage slot 254 is reserved and can not be used with set_account_item procedure"), (ERR_ACCT_MUST_BE_A_FAUCET, "Procedure can only be called from faucet accounts"), (ERR_P2ID_WRONG_NUMBER_OF_INPUTS, "P2ID scripts expect exactly 1 note input"), @@ -253,7 +274,7 @@ pub const KERNEL_ERRORS: [(u32, &str); 75] = [ (ERR_P2IDR_WRONG_NUMBER_OF_INPUTS, "P2IDR scripts expect exactly 2 note inputs"), (ERR_P2IDR_RECLAIM_ACCT_IS_NOT_SENDER, "P2IDR's can only be reclaimed by the sender"), (ERR_P2IDR_RECLAIM_HEIGHT_NOT_REACHED, "Transaction's reference block is lower than reclaim height. The P2IDR can not be reclaimed"), - (ERR_SWAP_WRONG_NUMBER_OF_INPUTS, "SWAP script expects exactly 9 note inputs"), + (ERR_SWAP_WRONG_NUMBER_OF_INPUTS, "SWAP script expects exactly 10 note inputs"), (ERR_SWAP_WRONG_NUMBER_OF_ASSETS, "SWAP script requires exactly 1 note asset"), (ERR_NONCE_DID_NOT_INCREASE, "The nonce did not increase after a state changing transaction"), (ERR_EPILOGUE_ASSETS_DONT_ADD_UP, "Total number of assets in the account and all involved notes must stay the same"), @@ -321,4 +342,8 @@ pub const KERNEL_ERRORS: [(u32, &str); 75] = [ (ERR_SETTING_NON_VALUE_ITEM_ON_VALUE_SLOT, "Setting a non-value item on a value slot"), (ERR_SETTING_MAP_ITEM_ON_NON_MAP_SLOT, "Setting a map item on a non-map slot"), (ERR_READING_MAP_VALUE_FROM_NON_MAP_SLOT, "Slot type is not a map"), + (ERR_PROC_NOT_PART_OF_ACCOUNT_CODE, "Provided procedure is not part of account code"), + (ERR_PROC_INDEX_OUT_OF_BOUNDS, "Provided procedure index is out of bounds"), + (ERR_ACCT_CODE_HASH_MISMATCH, "Provided account hash does not match stored account hash"), + (ERR_ACCT_TOO_MANY_PROCEDURES, "Number of account procedures exceeded the maximum limit of 256") ]; diff --git a/miden-tx/src/executor/data_store.rs b/miden-tx/src/executor/data_store.rs index 5bdcc09d7..0e21e22b9 100644 --- a/miden-tx/src/executor/data_store.rs +++ b/miden-tx/src/executor/data_store.rs @@ -1,6 +1,4 @@ -use miden_objects::{ - accounts::AccountId, assembly::ModuleAst, notes::NoteId, transaction::TransactionInputs, -}; +use miden_objects::{accounts::AccountId, notes::NoteId, transaction::TransactionInputs}; use winter_maybe_async::maybe_async; use crate::DataStoreError; @@ -33,8 +31,4 @@ pub trait DataStore { block_ref: u32, notes: &[NoteId], ) -> Result; - - /// Returns the account code [ModuleAst] associated with the specified [AccountId]. - #[maybe_async] - fn get_account_code(&self, account_id: AccountId) -> Result; } diff --git a/miden-tx/src/executor/mast_store.rs b/miden-tx/src/executor/mast_store.rs new file mode 100644 index 000000000..be39e4f92 --- /dev/null +++ b/miden-tx/src/executor/mast_store.rs @@ -0,0 +1,93 @@ +use alloc::{collections::BTreeMap, sync::Arc}; +use core::cell::RefCell; + +use miden_lib::{transaction::TransactionKernel, MidenLib, StdLibrary}; +use miden_objects::{ + assembly::mast::MastForest, + transaction::{TransactionArgs, TransactionInputs}, + Digest, +}; +use vm_processor::MastForestStore; + +// TRANSACTION MAST STORE +// ================================================================================================ + +/// A store for the code available during transaction execution. +/// +/// Transaction MAST store contains a map between procedure MAST roots and [MastForest]s containing +/// MASTs for these procedures. The VM will request [MastForest]s from the store when it encounters +/// a procedure which it doesn't have the code for. Thus, to execute a program which makes +/// references to external procedures, the store must be loaded with [MastForest]s containing these +/// procedures. +pub struct TransactionMastStore { + mast_forests: RefCell>>, +} + +#[allow(clippy::new_without_default)] +impl TransactionMastStore { + /// Returns a new [TransactionMastStore] instantiated with the default libraries. + /// + /// The default libraries include: + /// - Miden standard library (miden-stdlib). + /// - Miden rollup library (miden-lib). + /// - Transaction kernel. + pub fn new() -> Self { + let mast_forests = RefCell::new(BTreeMap::new()); + let store = Self { mast_forests }; + + // load transaction kernel MAST forest + let kernels_forest = Arc::new(TransactionKernel::kernel().into()); + store.insert(kernels_forest); + + // load miden-stdlib MAST forest + let miden_stdlib_forest = Arc::new(StdLibrary::default().into()); + store.insert(miden_stdlib_forest); + + // load miden lib MAST forest + let miden_lib_forest = Arc::new(MidenLib::default().into()); + store.insert(miden_lib_forest); + + store + } + + /// Loads code required for executing a transaction with the specified inputs and args into + /// this store. + /// + /// The loaded code includes: + /// - Account code for the account specified in the provided [TransactionInputs]. + /// - Note scripts for all input notes in the provided [TransactionInputs]. + /// - Transaction script (if any) from the specified [TransactionArgs]. + pub fn load_transaction_code(&self, tx_inputs: &TransactionInputs, tx_args: &TransactionArgs) { + // load account code + self.insert(tx_inputs.account().code().mast().clone()); + + // load note script MAST into the MAST store + for note in tx_inputs.input_notes() { + self.insert(note.note().script().mast().clone()); + } + + // load tx script MAST into the MAST store + if let Some(tx_script) = tx_args.tx_script() { + self.insert(tx_script.mast().clone()); + } + } + + /// Registers all procedures of the provided [MastForest] with this store. + pub fn insert(&self, mast_forest: Arc) { + let mut mast_forests = self.mast_forests.borrow_mut(); + + // only register procedures that are local to this forest + for proc_digest in mast_forest.local_procedure_digests() { + mast_forests.insert(proc_digest, mast_forest.clone()); + } + } +} + +// MAST FOREST STORE IMPLEMENTATION +// ================================================================================================ + +impl MastForestStore for TransactionMastStore { + fn get(&self, procedure_hash: &Digest) -> Option> { + self.mast_forests.borrow().get(procedure_hash).cloned() + } +} diff --git a/miden-tx/src/executor/mod.rs b/miden-tx/src/executor/mod.rs index 307a5ccb6..d883d0f52 100644 --- a/miden-tx/src/executor/mod.rs +++ b/miden-tx/src/executor/mod.rs @@ -1,25 +1,25 @@ -use alloc::{rc::Rc, vec::Vec}; +use alloc::rc::Rc; -use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; +use miden_lib::transaction::TransactionKernel; use miden_objects::{ - assembly::ProgramAst, - transaction::{TransactionArgs, TransactionInputs, TransactionScript}, - vm::{Program, StackOutputs}, - Felt, Word, ZERO, + accounts::AccountId, + notes::NoteId, + transaction::{ExecutedTransaction, TransactionArgs, TransactionInputs}, + vm::StackOutputs, + ZERO, }; -use vm_processor::ExecutionOptions; +use vm_processor::{ExecutionOptions, RecAdviceProvider}; use winter_maybe_async::{maybe_async, maybe_await}; -use super::{ - AccountCode, AccountId, Digest, ExecutedTransaction, NoteId, NoteScript, PreparedTransaction, - RecAdviceProvider, ScriptTarget, TransactionCompiler, TransactionExecutorError, - TransactionHost, -}; +use super::{TransactionExecutorError, TransactionHost}; use crate::auth::TransactionAuthenticator; mod data_store; pub use data_store::DataStore; +mod mast_store; +pub use mast_store::TransactionMastStore; + // TRANSACTION EXECUTOR // ================================================================================================ @@ -27,7 +27,7 @@ pub use data_store::DataStore; /// /// Transaction execution consists of the following steps: /// - Fetch the data required to execute a transaction from the [DataStore]. -/// - Compile the transaction into a program using the [TransactionCompiler](crate::TransactionCompiler). +/// - Load the code associated with the transaction into the [TransactionMastStore]. /// - Execute the transaction program and create an [ExecutedTransaction]. /// /// The transaction executor is generic over the [DataStore] which allows it to be used with @@ -38,8 +38,8 @@ pub use data_store::DataStore; /// can then be used to by the prover to generate a proof transaction execution. pub struct TransactionExecutor { data_store: D, + mast_store: Rc, authenticator: Option>, - compiler: TransactionCompiler, exec_options: ExecutionOptions, } @@ -47,12 +47,13 @@ impl TransactionExecutor { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Creates a new [TransactionExecutor] instance with the specified [DataStore] and [TransactionAuthenticator]. + /// Creates a new [TransactionExecutor] instance with the specified [DataStore] and + /// [TransactionAuthenticator]. pub fn new(data_store: D, authenticator: Option>) -> Self { Self { data_store, + mast_store: Rc::new(TransactionMastStore::new()), authenticator, - compiler: TransactionCompiler::new(), exec_options: ExecutionOptions::default(), } } @@ -63,7 +64,6 @@ impl TransactionExecutor { /// account code) will be compiled and executed in debug mode. This will ensure that all debug /// instructions present in the original source code are executed. pub fn with_debug_mode(mut self, in_debug_mode: bool) -> Self { - self.compiler = self.compiler.with_debug_mode(in_debug_mode); if in_debug_mode && !self.exec_options.enable_debugging() { self.exec_options = self.exec_options.with_debugging(); } else if !in_debug_mode && self.exec_options.enable_debugging() { @@ -91,74 +91,6 @@ impl TransactionExecutor { self } - // STATE MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Fetches the account code from the [DataStore], compiles it, and loads the compiled code - /// into the internal cache. - /// - /// This also returns the [AccountCode] object built from the loaded account code. - /// - /// # Errors: - /// Returns an error if: - /// - If the account code cannot be fetched from the [DataStore]. - /// - If the account code fails to be loaded into the compiler. - #[maybe_async] - pub fn load_account( - &mut self, - account_id: AccountId, - ) -> Result { - let account_code = maybe_await!(self.data_store.get_account_code(account_id)) - .map_err(TransactionExecutorError::FetchAccountCodeFailed)?; - self.compiler - .load_account(account_id, account_code) - .map_err(TransactionExecutorError::LoadAccountFailed) - } - - /// Loads the provided account interface (vector of procedure digests) into the compiler. - /// - /// Returns the old interface for the specified account ID if it previously existed. - pub fn load_account_interface( - &mut self, - account_id: AccountId, - procedures: Vec, - ) -> Option> { - self.compiler.load_account_interface(account_id, procedures) - } - - // COMPILERS - // -------------------------------------------------------------------------------------------- - - /// Compiles the provided program into a [NoteScript] and checks (to the extent possible) if - /// the specified note program could be executed against all accounts with the specified - /// interfaces. - pub fn compile_note_script( - &self, - note_script_ast: ProgramAst, - target_account_procs: Vec, - ) -> Result { - self.compiler - .compile_note_script(note_script_ast, target_account_procs) - .map_err(TransactionExecutorError::CompileNoteScriptFailed) - } - - /// Compiles the provided transaction script source and inputs into a [TransactionScript] and - /// checks (to the extent possible) that the transaction script can be executed against all - /// accounts with the specified interfaces. - pub fn compile_tx_script( - &self, - tx_script_ast: ProgramAst, - inputs: T, - target_account_procs: Vec, - ) -> Result - where - T: IntoIterator)>, - { - self.compiler - .compile_tx_script(tx_script_ast, inputs, target_account_procs) - .map_err(TransactionExecutorError::CompileTransactionScriptFailed) - } - // TRANSACTION EXECUTION // -------------------------------------------------------------------------------------------- @@ -172,8 +104,6 @@ impl TransactionExecutor { /// # Errors: /// Returns an error if: /// - If required data can not be fetched from the [DataStore]. - /// - If the transaction program can not be compiled. - /// - If the transaction program can not be executed. #[maybe_async] pub fn execute_transaction( &self, @@ -182,69 +112,35 @@ impl TransactionExecutor { notes: &[NoteId], tx_args: TransactionArgs, ) -> Result { - let transaction = - maybe_await!(self.prepare_transaction(account_id, block_ref, notes, tx_args))?; + let tx_inputs = + maybe_await!(self.data_store.get_transaction_inputs(account_id, block_ref, notes)) + .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; - let (stack_inputs, advice_inputs) = transaction.get_kernel_inputs(); + let (stack_inputs, advice_inputs) = + TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None); let advice_recorder: RecAdviceProvider = advice_inputs.into(); + + // load note script MAST into the MAST store + self.mast_store.load_transaction_code(&tx_inputs, &tx_args); + let mut host = TransactionHost::new( - transaction.account().into(), + tx_inputs.account().into(), advice_recorder, + self.mast_store.clone(), self.authenticator.clone(), - ); + ) + .map_err(TransactionExecutorError::TransactionHostCreationFailed)?; + // execute the transaction kernel let result = vm_processor::execute( - transaction.program(), + &TransactionKernel::main(), stack_inputs, &mut host, self.exec_options, ) .map_err(TransactionExecutorError::ExecuteTransactionProgramFailed)?; - let (tx_program, tx_inputs, tx_args) = transaction.into_parts(); - - build_executed_transaction( - tx_program, - tx_args, - tx_inputs, - result.stack_outputs().clone(), - host, - ) - } - - // HELPER METHODS - // -------------------------------------------------------------------------------------------- - - /// Fetches the data required to execute the transaction from the [DataStore], compiles the - /// transaction into an executable program using the [TransactionCompiler], and returns a - /// [PreparedTransaction]. - /// - /// # Errors: - /// Returns an error if: - /// - If required data can not be fetched from the [DataStore]. - /// - If the transaction can not be compiled. - #[maybe_async] - pub fn prepare_transaction( - &self, - account_id: AccountId, - block_ref: u32, - notes: &[NoteId], - tx_args: TransactionArgs, - ) -> Result { - let tx_inputs = - maybe_await!(self.data_store.get_transaction_inputs(account_id, block_ref, notes)) - .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?; - - let tx_program = self - .compiler - .compile_transaction( - account_id, - tx_inputs.input_notes(), - tx_args.tx_script().map(|x| x.code()), - ) - .map_err(TransactionExecutorError::CompileTransactionFailed)?; - - Ok(PreparedTransaction::new(tx_program, tx_inputs, tx_args)) + build_executed_transaction(tx_args, tx_inputs, result.stack_outputs().clone(), host) } } @@ -253,19 +149,20 @@ impl TransactionExecutor { /// Creates a new [ExecutedTransaction] from the provided data. fn build_executed_transaction( - program: Program, tx_args: TransactionArgs, tx_inputs: TransactionInputs, stack_outputs: StackOutputs, host: TransactionHost, ) -> Result { - let (advice_recorder, account_delta, output_notes, generated_signatures) = host.into_parts(); + let (advice_recorder, account_delta, output_notes, generated_signatures, tx_progress) = + host.into_parts(); let (mut advice_witness, _, map, _store) = advice_recorder.finalize(); let tx_outputs = TransactionKernel::from_transaction_parts(&stack_outputs, &map.into(), output_notes) .map_err(TransactionExecutorError::InvalidTransactionOutput)?; + let final_account = &tx_outputs.account; let initial_account = tx_inputs.account(); @@ -297,11 +194,11 @@ fn build_executed_transaction( advice_witness.extend_map(generated_signatures); Ok(ExecutedTransaction::new( - program, tx_inputs, tx_outputs, account_delta, tx_args, advice_witness, + tx_progress.into(), )) } diff --git a/miden-tx/src/host/account_delta_tracker.rs b/miden-tx/src/host/account_delta_tracker.rs index 1b019808a..7d41107f2 100644 --- a/miden-tx/src/host/account_delta_tracker.rs +++ b/miden-tx/src/host/account_delta_tracker.rs @@ -1,14 +1,7 @@ -use alloc::{collections::BTreeMap, vec::Vec}; - use miden_objects::{ - accounts::{ - AccountDelta, AccountId, AccountStorageDelta, AccountStub, AccountVaultDelta, - StorageMapDelta, - }, - assets::{Asset, FungibleAsset, NonFungibleAsset}, - Digest, Felt, Word, EMPTY_WORD, ZERO, + accounts::{AccountDelta, AccountStorageDelta, AccountStub, AccountVaultDelta}, + Felt, ZERO, }; - // ACCOUNT DELTA TRACKER // ================================================================================================ @@ -24,8 +17,8 @@ use miden_objects::{ /// - account code changes. #[derive(Debug, Clone, PartialEq, Eq)] pub struct AccountDeltaTracker { - storage: AccountStorageDeltaTracker, - vault: AccountVaultDeltaTracker, + storage: AccountStorageDelta, + vault: AccountVaultDelta, init_nonce: Felt, nonce_delta: Felt, } @@ -34,8 +27,8 @@ impl AccountDeltaTracker { /// Returns a new [AccountDeltaTracker] instantiated for the specified account. pub fn new(account: &AccountStub) -> Self { Self { - storage: AccountStorageDeltaTracker::default(), - vault: AccountVaultDeltaTracker::default(), + storage: AccountStorageDelta::default(), + vault: AccountVaultDelta::default(), init_nonce: account.nonce(), nonce_delta: ZERO, } @@ -43,15 +36,9 @@ impl AccountDeltaTracker { /// Consumes `self` and returns the resulting [AccountDelta]. pub fn into_delta(self) -> AccountDelta { - let storage_delta = self.storage.into_delta(); - let vault_delta = self.vault.into_delta(); - let nonce_delta = if self.nonce_delta == ZERO { - None - } else { - Some(self.init_nonce + self.nonce_delta) - }; + let nonce_delta = (self.nonce_delta != ZERO).then_some(self.init_nonce + self.nonce_delta); - AccountDelta::new(storage_delta, vault_delta, nonce_delta).expect("invalid account delta") + AccountDelta::new(self.storage, self.vault, nonce_delta).expect("invalid account delta") } /// Tracks nonce delta. @@ -59,211 +46,13 @@ impl AccountDeltaTracker { self.nonce_delta += value; } - /// Get the vault tracker - pub fn vault_tracker(&mut self) -> &mut AccountVaultDeltaTracker { + /// Get a mutable reference to the current vault delta + pub fn vault_delta(&mut self) -> &mut AccountVaultDelta { &mut self.vault } - /// Get the storage tracker - pub fn storage_tracker(&mut self) -> &mut AccountStorageDeltaTracker { + /// Get a mutable reference to the current storage delta + pub fn storage_delta(&mut self) -> &mut AccountStorageDelta { &mut self.storage } } - -// ACCOUNT STORAGE DELTA TRACKER -// ================================================================================================ - -/// The account storage delta tracker is responsible for tracking changes to the storage of the -/// account the transaction is being executed against. -/// -/// The delta tracker is composed of: -/// - A map which records the latest states for the updated storage slots. -/// - A map which records the latest states for the updates storage maps -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct AccountStorageDeltaTracker { - slot_updates: BTreeMap, - maps_updates: BTreeMap>, -} - -impl AccountStorageDeltaTracker { - /// Consumes `self` and returns the [AccountStorageDelta] that represents the changes to - /// the account's storage. - pub fn into_delta(self) -> AccountStorageDelta { - let mut cleared_items = Vec::new(); - let mut updated_items = Vec::new(); - let mut updated_maps: Vec<(u8, StorageMapDelta)> = Vec::new(); - - for (idx, value) in self.slot_updates { - if value == EMPTY_WORD { - cleared_items.push(idx); - } else { - updated_items.push((idx, value)); - } - } - - for (idx, map_deltas) in self.maps_updates { - let mut updated_leafs = Vec::new(); - let mut cleared_leafs = Vec::new(); - - for map_delta in map_deltas { - if map_delta.1 == EMPTY_WORD { - cleared_leafs.push(map_delta.0); - } else { - updated_leafs.push(map_delta); - } - } - let storage_map_delta = StorageMapDelta::from(cleared_leafs, updated_leafs); - updated_maps.push((idx, storage_map_delta)); - } - - AccountStorageDelta { - cleared_items, - updated_items, - updated_maps, - } - } - - /// Tracks a slot change - pub fn slot_update(&mut self, slot_index: u8, new_slot_value: [Felt; 4]) { - self.slot_updates.insert(slot_index, new_slot_value); - } - - /// Tracks a slot change - pub fn maps_update(&mut self, slot_index: u8, key: [Felt; 4], new_value: [Felt; 4]) { - self.maps_updates.entry(slot_index).or_default().push((key, new_value)); - } -} - -// ACCOUNT VAULT DELTA TRACKER -// ================================================================================================ - -/// The account vault delta tracker is responsible for tracking changes to the vault of the account -/// the transaction is being executed against. -/// -/// The delta tracker is composed of two maps: -/// - Fungible asset map: tracks changes to the vault's fungible assets, where the key is the -/// faucet ID of the asset, and the value is the amount of the asset being added or removed from -/// the vault (positive value for added assets, negative value for removed assets). -/// - Non-fungible asset map: tracks changes to the vault's non-fungible assets, where the key is -/// the non-fungible asset, and the value is either 1 or -1 depending on whether the asset is -/// being added or removed from the vault. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct AccountVaultDeltaTracker { - fungible_assets: BTreeMap, - non_fungible_assets: BTreeMap, -} - -impl AccountVaultDeltaTracker { - // STATE MUTATORS - // -------------------------------------------------------------------------------------------- - - pub fn add_asset(&mut self, asset: Asset) { - match asset { - Asset::Fungible(asset) => { - update_asset_delta( - &mut self.fungible_assets, - asset.faucet_id(), - asset.amount() as i128, - ); - }, - Asset::NonFungible(asset) => { - update_asset_delta(&mut self.non_fungible_assets, asset.vault_key().into(), 1) - }, - } - } - - /// Track asset removal. - pub fn remove_asset(&mut self, asset: Asset) { - match asset { - Asset::Fungible(asset) => { - update_asset_delta( - &mut self.fungible_assets, - asset.faucet_id(), - -(asset.amount() as i128), - ); - }, - Asset::NonFungible(asset) => { - update_asset_delta(&mut self.non_fungible_assets, asset.vault_key().into(), -1) - }, - } - } - - // CONVERSIONS - // -------------------------------------------------------------------------------------------- - - /// Consumes `self` and returns the [AccountVaultDelta] that represents the changes to the - /// account's vault. - pub fn into_delta(self) -> AccountVaultDelta { - let mut added_assets = Vec::new(); - let mut removed_assets = Vec::new(); - - // process fungible assets - for (faucet_id, amount) in self.fungible_assets { - if amount > 0 { - added_assets.push(Asset::Fungible( - FungibleAsset::new( - AccountId::new_unchecked(faucet_id.into()), - amount.unsigned_abs() as u64, - ) - .expect("fungible asset is well formed"), - )); - } else { - removed_assets.push(Asset::Fungible( - FungibleAsset::new( - AccountId::new_unchecked(faucet_id.into()), - amount.unsigned_abs() as u64, - ) - .expect("fungible asset is well formed"), - )); - } - } - - // process non-fungible assets - for (non_fungible_asset, amount) in self.non_fungible_assets { - match amount { - 1 => { - added_assets.push(Asset::NonFungible(unsafe { - NonFungibleAsset::new_unchecked(*non_fungible_asset) - })); - }, - -1 => { - removed_assets.push(Asset::NonFungible(unsafe { - NonFungibleAsset::new_unchecked(*non_fungible_asset) - })); - }, - _ => unreachable!("non-fungible asset amount must be 1 or -1"), - } - } - - AccountVaultDelta { added_assets, removed_assets } - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Updates the provided map with the provided key and amount. If the final amount is 0, the entry -/// is removed from the map. -fn update_asset_delta(delta_map: &mut BTreeMap, key: K, amount: V) -where - V: core::ops::Neg, - V: core::cmp::PartialEq<::Output>, - V: core::ops::AddAssign, - V: Copy, - K: Ord, -{ - use alloc::collections::btree_map::Entry; - - match delta_map.entry(key) { - Entry::Occupied(mut entry) => { - if entry.get() == &-amount { - entry.remove(); - } else { - *entry.get_mut() += amount; - } - }, - Entry::Vacant(entry) => { - entry.insert(amount); - }, - } -} diff --git a/miden-tx/src/host/account_procs.rs b/miden-tx/src/host/account_procs.rs index 21d72c73d..9598f411f 100644 --- a/miden-tx/src/host/account_procs.rs +++ b/miden-tx/src/host/account_procs.rs @@ -1,7 +1,10 @@ +use alloc::string::ToString; + use miden_lib::transaction::TransactionKernelError; -use miden_objects::accounts::AccountCode; +use miden_objects::accounts::{AccountCode, AccountProcedureInfo}; -use super::{AdviceProvider, BTreeMap, Digest, NodeIndex, ProcessState}; +use super::{AdviceProvider, BTreeMap, Digest, Felt, ProcessState}; +use crate::error::TransactionHostError; // ACCOUNT PROCEDURE INDEX MAP // ================================================================================================ @@ -12,29 +15,66 @@ pub struct AccountProcedureIndexMap(BTreeMap); impl AccountProcedureIndexMap { /// Returns a new [AccountProcedureIndexMap] instantiated with account procedures present in /// the provided advice provider. - /// - /// This function assumes that the account procedure tree (or a part thereof) is loaded into the - /// Merkle store of the provided advice provider. - pub fn new(account_code_root: Digest, adv_provider: &A) -> Self { - // get the Merkle store with the procedure tree from the advice provider - let proc_store = adv_provider.get_store_subset([account_code_root].iter()); + pub fn new( + account_code_commitment: Digest, + adv_provider: &impl AdviceProvider, + ) -> Result { + // get the account procedures from the advice_map + let proc_data = + adv_provider.get_mapped_values(&account_code_commitment).ok_or_else(|| { + TransactionHostError::AccountProcedureIndexMapError( + "Failed to read account procedure data from the advice provider".to_string(), + ) + })?; - // iterate over all possible procedure indexes let mut result = BTreeMap::new(); - for i in 0..AccountCode::MAX_NUM_PROCEDURES { - let index = NodeIndex::new(AccountCode::PROCEDURE_TREE_DEPTH, i as u64) - .expect("procedure tree index is valid"); - // if the node at the current index does not exist, skip it and try the next node;this - // situation is valid if not all account procedures are loaded into the advice provider - if let Ok(proc_root) = proc_store.get_node(account_code_root, index) { - // if we got an empty digest, this means we got to the end of the procedure list - if proc_root == Digest::default() { - break; - } - result.insert(proc_root, i as u8); - } + + // sanity checks + + // check that there are procedures in the account code + if proc_data.is_empty() { + return Err(TransactionHostError::AccountProcedureIndexMapError( + "The account code does not contain any procedures.".to_string(), + )); + } + + let num_procs = proc_data[0].as_int() as usize; + + // check that the account code does not contain too many procedures + if num_procs > AccountCode::MAX_NUM_PROCEDURES { + return Err(TransactionHostError::AccountProcedureIndexMapError( + "The account code contains too many procedures.".to_string(), + )); + } + + // check that the stored number of procedures matches the length of the procedures array + if num_procs * AccountProcedureInfo::NUM_ELEMENTS_PER_PROC != proc_data.len() - 1 { + return Err(TransactionHostError::AccountProcedureIndexMapError( + "Invalid number of procedures.".to_string(), + )); } - Self(result) + + // we skip proc_data[0] because it's the number of procedures + for (proc_idx, proc_info) in proc_data[1..] + .chunks_exact(AccountProcedureInfo::NUM_ELEMENTS_PER_PROC) + .enumerate() + { + let proc_info_array: [Felt; AccountProcedureInfo::NUM_ELEMENTS_PER_PROC] = + proc_info.try_into().expect("Failed conversion into procedure info array."); + + let procedure = AccountProcedureInfo::try_from(proc_info_array).map_err(|e| { + TransactionHostError::AccountProcedureIndexMapError(format!( + "Failed to create AccountProcedureInfo: {:?}", + e + )) + })?; + + let proc_idx = u8::try_from(proc_idx).expect("Invalid procedure index."); + + result.insert(*procedure.mast_root(), proc_idx); + } + + Ok(Self(result)) } /// Returns index of the procedure whose root is currently at the top of the operand stack in @@ -43,11 +83,18 @@ impl AccountProcedureIndexMap { /// # Errors /// Returns an error if the procedure at the top of the operand stack is not present in this /// map. - pub fn get_proc_index( + pub fn get_proc_index( &self, - process: &S, + process: &impl ProcessState, ) -> Result { let proc_root = process.get_stack_word(0).into(); + + // mock account method for testing from root context + // TODO: figure out if we can get rid of this + if proc_root == Digest::default() { + return Ok(255); + } + self.0 .get(&proc_root) .cloned() diff --git a/miden-tx/src/host/mod.rs b/miden-tx/src/host/mod.rs index 6f20330f0..27190ae27 100644 --- a/miden-tx/src/host/mod.rs +++ b/miden-tx/src/host/mod.rs @@ -1,25 +1,26 @@ -use alloc::{collections::BTreeMap, rc::Rc, string::ToString, vec::Vec}; +use alloc::{collections::BTreeMap, rc::Rc, string::ToString, sync::Arc, vec::Vec}; use miden_lib::transaction::{ - memory::CURRENT_CONSUMED_NOTE_PTR, TransactionEvent, TransactionKernelError, TransactionTrace, + memory::CURRENT_INPUT_NOTE_PTR, TransactionEvent, TransactionKernelError, TransactionTrace, }; use miden_objects::{ - accounts::{AccountDelta, AccountId, AccountStorage, AccountStub}, + accounts::{AccountDelta, AccountStorage, AccountStub}, assets::Asset, notes::NoteId, - transaction::OutputNote, + transaction::{OutputNote, TransactionMeasurements}, + vm::RowIndex, Digest, Hasher, }; use vm_processor::{ - crypto::NodeIndex, AdviceExtractor, AdviceInjector, AdviceProvider, AdviceSource, ContextId, - ExecutionError, Felt, Host, HostResponse, ProcessState, + AdviceExtractor, AdviceInjector, AdviceProvider, AdviceSource, ContextId, ExecutionError, Felt, + Host, HostResponse, MastForest, MastForestStore, ProcessState, }; mod account_delta_tracker; use account_delta_tracker::AccountDeltaTracker; mod account_procs; -use account_procs::AccountProcedureIndexMap; +pub use account_procs::AccountProcedureIndexMap; mod note_builder; use note_builder::OutputNoteBuilder; @@ -27,7 +28,10 @@ use note_builder::OutputNoteBuilder; mod tx_progress; pub use tx_progress::TransactionProgress; -use crate::{auth::TransactionAuthenticator, KERNEL_ERRORS}; +use crate::{ + auth::TransactionAuthenticator, error::TransactionHostError, executor::TransactionMastStore, + KERNEL_ERRORS, +}; // CONSTANTS // ================================================================================================ @@ -38,42 +42,67 @@ pub const STORAGE_TREE_DEPTH: Felt = Felt::new(AccountStorage::STORAGE_TREE_DEPT // ================================================================================================ /// Transaction host is responsible for handling [Host] requests made by a transaction kernel. +/// +/// Transaction hosts are created on per-transaction basis. That is a transaction host is meant to +/// support execution of a single transaction, and is discarded after the transaction finishes +/// execution. pub struct TransactionHost { /// Advice provider which is used to provide non-deterministic inputs to the transaction /// runtime. adv_provider: A, - /// Accumulates the state changes notified via events. + /// MAST store which contains the code required to execute the transaction. + mast_store: Rc, + + /// Account state changes accumulated during transaction execution. + /// + /// This field is updated by the [TransactionHost::on_event()] handler. account_delta: AccountDeltaTracker, - /// A map for the account's procedures. + /// A map of the account's procedure MAST roots to the corresponding procedure indexes in the + /// account code. acct_procedure_index_map: AccountProcedureIndexMap, /// The list of notes created while executing a transaction stored as note_ptr |-> note_builder /// map. output_notes: BTreeMap, - /// Provides a way to get a signature for a message into a transaction + /// Serves signature generation requests from the transaction runtime for signatures which are + /// not present in the `generated_signatures` field. authenticator: Option>, - /// Contains the information about the number of cycles for each of the transaction execution - /// stages. - tx_progress: TransactionProgress, - - /// Contains generated signatures for messages + /// Contains previously generated signatures (as a message |-> signature map) required for + /// transaction execution. + /// + /// If a required signature is not present in this map, the host will attempt to generate the + /// signature using the transaction authenticator. generated_signatures: BTreeMap>, - /// Contains mappings from error codes to the related error messages + /// Tracks the number of cycles for each of the transaction execution stages. + /// + /// This field is updated by the [TransactionHost::on_trace()] handler. + tx_progress: TransactionProgress, + + /// Contains mappings from error codes to the related error messages. + /// + /// This map is initialized at construction time from the [KERNEL_ERRORS] array. error_messages: BTreeMap, } impl TransactionHost { /// Returns a new [TransactionHost] instance with the provided [AdviceProvider]. - pub fn new(account: AccountStub, adv_provider: A, authenticator: Option>) -> Self { - let proc_index_map = AccountProcedureIndexMap::new(account.code_root(), &adv_provider); + pub fn new( + account: AccountStub, + adv_provider: A, + mast_store: Rc, + authenticator: Option>, + ) -> Result { + let proc_index_map = + AccountProcedureIndexMap::new(account.code_commitment(), &adv_provider)?; let kernel_assertion_errors = BTreeMap::from(KERNEL_ERRORS); - Self { + Ok(Self { adv_provider, + mast_store, account_delta: AccountDeltaTracker::new(&account), acct_procedure_index_map: proc_index_map, output_notes: BTreeMap::default(), @@ -81,11 +110,20 @@ impl TransactionHost { tx_progress: TransactionProgress::default(), generated_signatures: BTreeMap::new(), error_messages: kernel_assertion_errors, - } + }) } - /// Consumes `self` and returns the advice provider and account vault delta. - pub fn into_parts(self) -> (A, AccountDelta, Vec, BTreeMap>) { + /// Consumes `self` and returns the advice provider, account vault delta, output notes and + /// signatures generated during the transaction execution. + pub fn into_parts( + self, + ) -> ( + A, + AccountDelta, + Vec, + BTreeMap>, + TransactionProgress, + ) { let output_notes = self.output_notes.into_values().map(|builder| builder.build()).collect(); ( @@ -93,10 +131,11 @@ impl TransactionHost { self.account_delta.into_delta(), output_notes, self.generated_signatures, + self.tx_progress, ) } - /// Returns a reference to the `tx_progress` field of the [`TransactionHost`]. + /// Returns a reference to the `tx_progress` field of this transaction host. pub fn tx_progress(&self) -> &TransactionProgress { &self.tx_progress } @@ -107,13 +146,13 @@ impl TransactionHost { /// Crates a new [OutputNoteBuilder] from the data on the operand stack and stores it into the /// `output_notes` field of this [TransactionHost]. /// - /// Expected stack state: `[aux, note_type, sender_acct_id, tag, note_ptr, RECIPIENT, ...]` + /// Expected stack state: `[NOTE_METADATA, RECIPIENT, ...]` fn on_note_after_created( &mut self, process: &S, ) -> Result<(), TransactionKernelError> { let stack = process.get_stack_state(); - // # => [aux, note_type, sender_acct_id, tag, note_ptr, RECIPIENT, note_idx] + // # => [NOTE_METADATA] let note_idx: usize = stack[9].as_int() as usize; @@ -215,7 +254,7 @@ impl TransactionHost { // update the delta tracker only if the current and new values are different if current_slot_value != new_slot_value { let slot_index = slot_index.as_int() as u8; - self.account_delta.storage_tracker().slot_update(slot_index, new_slot_value); + self.account_delta.storage_delta().set_item(slot_index, new_slot_value); } Ok(()) @@ -252,9 +291,11 @@ impl TransactionHost { ]; let slot_index = slot_index.as_int() as u8; - self.account_delta - .storage_tracker() - .maps_update(slot_index, new_map_key, new_map_value); + self.account_delta.storage_delta().set_map_item( + slot_index, + new_map_key.into(), + new_map_value, + ); Ok(()) } @@ -275,7 +316,10 @@ impl TransactionHost { .try_into() .map_err(TransactionKernelError::MalformedAssetOnAccountVaultUpdate)?; - self.account_delta.vault_tracker().add_asset(asset); + self.account_delta + .vault_delta() + .add_asset(asset) + .map_err(TransactionKernelError::AccountDeltaError)?; Ok(()) } @@ -292,7 +336,10 @@ impl TransactionHost { .try_into() .map_err(TransactionKernelError::MalformedAssetOnAccountVaultUpdate)?; - self.account_delta.vault_tracker().remove_asset(asset); + self.account_delta + .vault_delta() + .remove_asset(asset) + .map_err(TransactionKernelError::AccountDeltaError)?; Ok(()) } @@ -351,7 +398,7 @@ impl TransactionHost { /// greater than `u32::MAX`). fn get_current_note_id(process: &S) -> Result, ExecutionError> { // get the word where note address is stored - let note_address_word = process.get_mem_value(process.ctx(), CURRENT_CONSUMED_NOTE_PTR); + let note_address_word = process.get_mem_value(process.ctx(), CURRENT_INPUT_NOTE_PTR); // get the note address in `Felt` from or return `None` if the address hasn't been accessed // previously. let note_address_felt = match note_address_word { @@ -371,6 +418,9 @@ impl TransactionHost { } } +// HOST IMPLEMENTATION FOR TRANSACTION HOST +// ================================================================================================ + impl Host for TransactionHost { fn get_advice( &mut self, @@ -391,6 +441,10 @@ impl Host for TransactionHost Option> { + self.mast_store.get(node_digest) + } + fn on_event( &mut self, process: &S, diff --git a/miden-tx/src/host/note_builder.rs b/miden-tx/src/host/note_builder.rs index 15bef7185..2fe4e9f10 100644 --- a/miden-tx/src/host/note_builder.rs +++ b/miden-tx/src/host/note_builder.rs @@ -2,13 +2,10 @@ use alloc::vec::Vec; use miden_objects::{ assets::Asset, - notes::{ - Note, NoteAssets, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType, - PartialNote, - }, + notes::{Note, NoteAssets, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, PartialNote}, }; -use super::{AccountId, AdviceProvider, Digest, Felt, OutputNote, TransactionKernelError}; +use super::{AdviceProvider, Digest, Felt, OutputNote, TransactionKernelError}; // OUTPUT NOTE BUILDER // ================================================================================================ @@ -29,7 +26,7 @@ impl OutputNoteBuilder { /// /// The stack is expected to be in the following state: /// - /// [aux, note_type, sender_acct_id, tag, note_ptr, RECIPIENT] + /// [NOTE_METADATA, RECIPIENT] /// /// Detailed note info such as assets and recipient (when available) are retrieved from the /// advice provider. @@ -47,14 +44,9 @@ impl OutputNoteBuilder { adv_provider: &A, ) -> Result { // read note metadata info from the stack and build the metadata object - let aux = stack[0]; - let note_type = - NoteType::try_from(stack[1]).map_err(TransactionKernelError::MalformedNoteType)?; - let sender = - AccountId::try_from(stack[2]).map_err(TransactionKernelError::MalformedAccountId)?; - let tag = NoteTag::try_from(stack[3]) - .map_err(|_| TransactionKernelError::MalformedTag(stack[3]))?; - let metadata = NoteMetadata::new(sender, note_type, tag, aux) + let metadata_word = [stack[3], stack[2], stack[1], stack[0]]; + let metadata: NoteMetadata = metadata_word + .try_into() .map_err(TransactionKernelError::MalformedNoteMetadata)?; // read recipient digest from the stack and try to build note recipient object if there is @@ -107,13 +99,12 @@ impl OutputNoteBuilder { let recipient = NoteRecipient::new(serial_num, script, inputs); Some(recipient) - } else if metadata.is_offchain() { + } else if metadata.is_private() { None } else { // if there are no recipient details and the note is not private, return an error return Err(TransactionKernelError::MissingNoteDetails(metadata, recipient_digest)); }; - Ok(Self { metadata, recipient_digest, diff --git a/miden-tx/src/host/tx_progress.rs b/miden-tx/src/host/tx_progress.rs index 3e4cab823..3ef9c170e 100644 --- a/miden-tx/src/host/tx_progress.rs +++ b/miden-tx/src/host/tx_progress.rs @@ -1,13 +1,13 @@ pub use alloc::vec::Vec; -use miden_objects::notes::NoteId; +use super::{NoteId, RowIndex, TransactionMeasurements}; // TRANSACTION PROGRESS // ================================================================================================ /// Contains the information about the number of cycles for each of the transaction execution /// stages. -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct TransactionProgress { prologue: CycleInterval, notes_processing: CycleInterval, @@ -43,78 +43,104 @@ impl TransactionProgress { // STATE MUTATORS // -------------------------------------------------------------------------------------------- - pub fn start_prologue(&mut self, cycle: u32) { + pub fn start_prologue(&mut self, cycle: RowIndex) { self.prologue.set_start(cycle); } - pub fn end_prologue(&mut self, cycle: u32) { + pub fn end_prologue(&mut self, cycle: RowIndex) { self.prologue.set_end(cycle); } - pub fn start_notes_processing(&mut self, cycle: u32) { + pub fn start_notes_processing(&mut self, cycle: RowIndex) { self.notes_processing.set_start(cycle); } - pub fn end_notes_processing(&mut self, cycle: u32) { + pub fn end_notes_processing(&mut self, cycle: RowIndex) { self.notes_processing.set_end(cycle); } - pub fn start_note_execution(&mut self, cycle: u32, note_id: NoteId) { + pub fn start_note_execution(&mut self, cycle: RowIndex, note_id: NoteId) { self.note_execution.push((note_id, CycleInterval::new(cycle))); } - pub fn end_note_execution(&mut self, cycle: u32) { + pub fn end_note_execution(&mut self, cycle: RowIndex) { if let Some((_, interval)) = self.note_execution.last_mut() { interval.set_end(cycle) } } - pub fn start_tx_script_processing(&mut self, cycle: u32) { + pub fn start_tx_script_processing(&mut self, cycle: RowIndex) { self.tx_script_processing.set_start(cycle); } - pub fn end_tx_script_processing(&mut self, cycle: u32) { + pub fn end_tx_script_processing(&mut self, cycle: RowIndex) { self.tx_script_processing.set_end(cycle); } - pub fn start_epilogue(&mut self, cycle: u32) { + pub fn start_epilogue(&mut self, cycle: RowIndex) { self.epilogue.set_start(cycle); } - pub fn end_epilogue(&mut self, cycle: u32) { + pub fn end_epilogue(&mut self, cycle: RowIndex) { self.epilogue.set_end(cycle); } } +impl From for TransactionMeasurements { + fn from(tx_progress: TransactionProgress) -> Self { + let prologue = tx_progress.prologue().len(); + + let notes_processing = tx_progress.notes_processing().len(); + + let note_execution = tx_progress + .note_execution() + .iter() + .map(|(note_id, interval)| (*note_id, interval.len())) + .collect(); + + let tx_script_processing = tx_progress.tx_script_processing().len(); + + let epilogue = tx_progress.epilogue().len(); + + Self { + prologue, + notes_processing, + note_execution, + tx_script_processing, + epilogue, + } + } +} + /// Stores the cycles corresponding to the start and the end of an interval. -#[derive(Clone, Default)] +#[derive(Clone, Default, Debug)] pub struct CycleInterval { - start: Option, - end: Option, + start: Option, + end: Option, } impl CycleInterval { - pub fn new(start: u32) -> Self { + pub fn new(start: RowIndex) -> Self { Self { start: Some(start), end: None } } - pub fn set_start(&mut self, s: u32) { + pub fn set_start(&mut self, s: RowIndex) { self.start = Some(s); } - pub fn set_end(&mut self, e: u32) { + pub fn set_end(&mut self, e: RowIndex) { self.end = Some(e); } /// Calculate the length of the interval - pub fn len(&self) -> Option { + pub fn len(&self) -> usize { if let Some(start) = self.start { if let Some(end) = self.end { if end >= start { - return Some(end - start); + return end - start; } } } - None + 0 } } diff --git a/miden-tx/src/lib.rs b/miden-tx/src/lib.rs index c19720f76..6c1c03475 100644 --- a/miden-tx/src/lib.rs +++ b/miden-tx/src/lib.rs @@ -6,22 +6,10 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -use miden_lib::transaction::TransactionKernel; pub use miden_objects::transaction::TransactionInputs; -use miden_objects::{ - accounts::{AccountCode, AccountId}, - notes::{NoteId, NoteScript}, - transaction::{ExecutedTransaction, PreparedTransaction}, - vm::{CodeBlock, Program}, - AccountError, Digest, -}; -use vm_processor::{ExecutionError, RecAdviceProvider}; - -mod compiler; -pub use compiler::{ScriptTarget, TransactionCompiler}; mod executor; -pub use executor::{DataStore, TransactionExecutor}; +pub use executor::{DataStore, TransactionExecutor, TransactionMastStore}; pub mod host; pub use host::{TransactionHost, TransactionProgress}; diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 030f1ac6c..1bdaab7fe 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -1,6 +1,6 @@ -use alloc::vec::Vec; +use alloc::{rc::Rc, vec::Vec}; -use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; +use miden_lib::transaction::TransactionKernel; use miden_objects::{ accounts::delta::AccountUpdateDetails, transaction::{OutputNote, ProvenTransaction, ProvenTransactionBuilder, TransactionWitness}, @@ -10,12 +10,15 @@ pub use miden_prover::ProvingOptions; use vm_processor::MemAdviceProvider; use super::{TransactionHost, TransactionProverError}; +use crate::executor::TransactionMastStore; /// Transaction prover is a stateless component which is responsible for proving transactions. /// /// Transaction prover exposes the `prove_transaction` method which takes a [TransactionWitness], -/// or anything that can be converted into a [TransactionWitness], and returns a [ProvenTransaction]. +/// or anything that can be converted into a [TransactionWitness], and returns a +/// [ProvenTransaction]. pub struct TransactionProver { + mast_store: Rc, proof_options: ProvingOptions, } @@ -24,7 +27,10 @@ impl TransactionProver { // -------------------------------------------------------------------------------------------- /// Creates a new [TransactionProver] instance. pub fn new(proof_options: ProvingOptions) -> Self { - Self { proof_options } + Self { + mast_store: Rc::new(TransactionMastStore::new()), + proof_options, + } } // TRANSACTION PROVER @@ -33,30 +39,38 @@ impl TransactionProver { /// Proves the provided transaction and returns a [ProvenTransaction]. /// /// # Errors - /// - If the consumed note data in the transaction witness is corrupt. + /// - If the input note data in the transaction witness is corrupt. /// - If the transaction program cannot be proven. /// - If the transaction result is corrupt. - pub fn prove_transaction>( + pub fn prove_transaction( &self, - transaction: T, + transaction: impl Into, ) -> Result { let tx_witness: TransactionWitness = transaction.into(); + let TransactionWitness { tx_inputs, tx_args, advice_witness } = tx_witness; - let input_notes = tx_witness.tx_inputs().input_notes(); - let account_id = tx_witness.account().id(); - let block_hash = tx_witness.block_header().hash(); + let account = tx_inputs.account(); + let input_notes = tx_inputs.input_notes(); + let block_hash = tx_inputs.block_header().hash(); // execute and prove - let (stack_inputs, advice_inputs) = tx_witness.get_kernel_inputs(); + let (stack_inputs, advice_inputs) = + TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_witness)); let advice_provider: MemAdviceProvider = advice_inputs.into(); + + // load the store with account/note/tx_script MASTs + self.mast_store.load_transaction_code(&tx_inputs, &tx_args); + let mut host: TransactionHost<_, ()> = - TransactionHost::new(tx_witness.account().into(), advice_provider, None); + TransactionHost::new(account.into(), advice_provider, self.mast_store.clone(), None) + .map_err(TransactionProverError::TransactionHostCreationFailed)?; let (stack_outputs, proof) = - prove(tx_witness.program(), stack_inputs, &mut host, self.proof_options.clone()) + prove(&TransactionKernel::main(), stack_inputs, &mut host, self.proof_options.clone()) .map_err(TransactionProverError::ProveTransactionProgramFailed)?; // extract transaction outputs and process transaction data - let (advice_provider, account_delta, output_notes, _signatures) = host.into_parts(); + let (advice_provider, account_delta, output_notes, _signatures, _tx_progress) = + host.into_parts(); let (_, map, _) = advice_provider.into_parts(); let tx_outputs = TransactionKernel::from_transaction_parts(&stack_outputs, &map.into(), output_notes) @@ -66,8 +80,8 @@ impl TransactionProver { let output_notes: Vec<_> = tx_outputs.output_notes.iter().map(OutputNote::shrink).collect(); let builder = ProvenTransactionBuilder::new( - account_id, - tx_witness.account().init_hash(), + account.id(), + account.init_hash(), tx_outputs.account.hash(), block_hash, proof, @@ -75,10 +89,10 @@ impl TransactionProver { .add_input_notes(input_notes) .add_output_notes(output_notes); - let builder = match account_id.is_on_chain() { + let builder = match account.is_on_chain() { true => { - let account_update_details = if tx_witness.account().is_new() { - let mut account = tx_witness.account().clone(); + let account_update_details = if account.is_new() { + let mut account = account.clone(); account .apply_delta(&account_delta) .map_err(TransactionProverError::InvalidAccountDelta)?; diff --git a/miden-tx/src/testing/account_procs.rs b/miden-tx/src/testing/account_procs.rs deleted file mode 100644 index e52d24738..000000000 --- a/miden-tx/src/testing/account_procs.rs +++ /dev/null @@ -1,62 +0,0 @@ -use alloc::collections::BTreeMap; - -use miden_lib::transaction::TransactionKernelError; -use miden_objects::accounts::AccountCode; -use vm_processor::{crypto::NodeIndex, AdviceProvider, Digest, ProcessState}; - -// ACCOUNT PROCEDURE INDEX MAP -// ================================================================================================ - -/// A map of proc_root |-> proc_index for all known procedures of an account interface. -pub struct AccountProcedureIndexMap(BTreeMap); - -impl AccountProcedureIndexMap { - /// Returns a new [AccountProcedureIndexMap] instantiated with account procedures present in - /// the provided advice provider. - /// - /// This function assumes that the account procedure tree (or a part thereof) is loaded into the - /// Merkle store of the provided advice provider. - pub fn new(account_code_root: Digest, adv_provider: &A) -> Self { - // get the Merkle store with the procedure tree from the advice provider - let proc_store = adv_provider.get_store_subset([account_code_root].iter()); - - // iterate over all possible procedure indexes - let mut result = BTreeMap::new(); - for i in 0..AccountCode::MAX_NUM_PROCEDURES { - let index = NodeIndex::new(AccountCode::PROCEDURE_TREE_DEPTH, i as u64) - .expect("procedure tree index is valid"); - // if the node at the current index does not exist, skip it and try the next node;this - // situation is valid if not all account procedures are loaded into the advice provider - if let Ok(proc_root) = proc_store.get_node(account_code_root, index) { - // if we got an empty digest, this means we got to the end of the procedure list - if proc_root == Digest::default() { - break; - } - result.insert(proc_root, i as u8); - } - } - Self(result) - } - - /// Returns index of the procedure whose root is currently at the top of the operand stack in - /// the provided process. - /// - /// # Errors - /// Returns an error if the procedure at the top of the operand stack is not present in this - /// map. - pub fn get_proc_index( - &self, - process: &S, - ) -> Result { - let proc_root = process.get_stack_word(0).into(); - // mock account method for testing from root context - // TODO: figure out if we can get rid of this - if proc_root == Digest::default() { - return Ok(255); - } - self.0 - .get(&proc_root) - .cloned() - .ok_or(TransactionKernelError::UnknownAccountProcedure(proc_root)) - } -} diff --git a/miden-tx/src/testing/executor.rs b/miden-tx/src/testing/executor.rs index c4acb6662..c8352e189 100644 --- a/miden-tx/src/testing/executor.rs +++ b/miden-tx/src/testing/executor.rs @@ -1,4 +1,3 @@ -#[cfg(not(target_family = "wasm"))] use miden_lib::transaction::TransactionKernel; #[cfg(feature = "std")] use vm_processor::{ @@ -37,14 +36,8 @@ impl CodeExecutor { } /// Compiles and runs the desired code in the host and returns the [Process] state - /// - /// If a module file path was set, its contents will be inserted between `self.imports` and - /// `code` before execution. - /// Otherwise, `self.imports` and `code` will be concatenated and the result will be executed. pub fn run(self, code: &str) -> Result, ExecutionError> { - let assembler = TransactionKernel::assembler().with_debug_mode(true); - - let program = assembler.compile(code).unwrap(); + let program = TransactionKernel::assembler_testing().assemble_program(code).unwrap(); self.execute_program(program) } @@ -65,7 +58,9 @@ where A: AdviceProvider, { pub fn with_advice_provider(adv_provider: A) -> Self { - let host = DefaultHost::new(adv_provider); + let mut host = DefaultHost::new(adv_provider); + let test_lib = TransactionKernel::kernel_as_library(); + host.load_mast_forest(test_lib.mast_forest().clone()); CodeExecutor::new(host) } } diff --git a/miden-tx/src/testing/mock_chain.rs b/miden-tx/src/testing/mock_chain.rs new file mode 100644 index 000000000..cd38683ac --- /dev/null +++ b/miden-tx/src/testing/mock_chain.rs @@ -0,0 +1,660 @@ +use alloc::{collections::BTreeMap, vec::Vec}; +use core::fmt; + +use miden_lib::{notes::create_p2id_note, transaction::TransactionKernel}; +use miden_objects::{ + accounts::{ + delta::AccountUpdateDetails, Account, AccountDelta, AccountId, AccountType, AuthSecretKey, + SlotItem, + }, + assets::{Asset, FungibleAsset, TokenSymbol}, + block::{compute_tx_hash, Block, BlockAccountUpdate, BlockNoteIndex, BlockNoteTree, NoteBatch}, + crypto::merkle::{Mmr, MmrError, PartialMmr, Smt}, + notes::{Note, NoteId, NoteInclusionProof, NoteType, Nullifier}, + testing::account::AccountBuilder, + transaction::{ + ChainMmr, ExecutedTransaction, InputNote, InputNotes, OutputNote, ToInputNoteCommitments, + TransactionId, TransactionInputs, + }, + AccountError, BlockHeader, FieldElement, NoteError, ACCOUNT_TREE_DEPTH, +}; +use rand::{rngs::StdRng, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use vm_processor::{ + crypto::{RpoRandomCoin, SimpleSmt}, + Digest, Felt, Word, ZERO, +}; + +use super::TransactionContextBuilder; +use crate::auth::BasicAuthenticator; + +// CONSTANTS +// ================================================================================================ + +/// Initial timestamp value +const TIMESTAMP_START: u32 = 1693348223; +/// Timestamp of timestamp on each new block +const TIMESTAMP_STEP: u32 = 10; + +pub type MockAuthenticator = BasicAuthenticator; + +// MOCK FUNGIBLE FAUCET +// ================================================================================================ + +/// Represents a fungible faucet that exists on the MockChain. +pub struct MockFungibleFaucet(Account); + +impl MockFungibleFaucet { + pub fn account(&self) -> &Account { + &self.0 + } + + pub fn mint(&self, amount: u64) -> Asset { + FungibleAsset::new(self.0.id(), amount).unwrap().into() + } +} + +// MOCK ACCOUNT +// ================================================================================================ + +/// Represents a mock account that exists on the MockChain. +/// It optionally includes the seed, and an authenticator that can be used for generating +/// valid transaction contexts. +#[derive(Clone, Debug)] +struct MockAccount { + account: Account, + seed: Option, + authenticator: Option>, +} + +impl MockAccount { + pub fn new( + account: Account, + seed: Option, + authenticator: Option>, + ) -> Self { + MockAccount { account, seed, authenticator } + } + + #[allow(dead_code)] + pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> { + self.account.apply_delta(delta) + } + + pub fn account(&self) -> &Account { + &self.account + } + + pub fn seed(&self) -> Option<&Word> { + self.seed.as_ref() + } + + pub fn authenticator(&self) -> &Option> { + &self.authenticator + } +} + +// PENDING OBJECTS +// ================================================================================================ + +/// Aggregates all entities that were added to the blockchain in the last block (not yet finalized) +#[derive(Default, Debug, Clone)] +struct PendingObjects { + /// Account updates for the block. + updated_accounts: Vec, + + /// Note batches created in transactions in the block. + output_note_batches: Vec, + + /// Nullifiers produced in transactions in the block. + created_nullifiers: Vec, + + /// Transaction IDs added to the block. + included_transactions: Vec<(TransactionId, AccountId)>, +} + +impl PendingObjects { + pub fn new() -> PendingObjects { + PendingObjects { + updated_accounts: vec![], + output_note_batches: vec![], + created_nullifiers: vec![], + included_transactions: vec![], + } + } + + /// Creates a [BlockNoteTree] tree from the `notes`. + /// + /// The root of the tree is a commitment to all notes created in the block. The commitment + /// is not for all fields of the [Note] struct, but only for note metadata + core fields of + /// a note (i.e., vault, inputs, script, and serial number). + pub fn build_notes_tree(&self) -> BlockNoteTree { + let entries = + self.output_note_batches.iter().enumerate().flat_map(|(batch_index, batch)| { + batch.iter().enumerate().map(move |(note_index, note)| { + ( + BlockNoteIndex::new(batch_index, note_index), + note.id().into(), + *note.metadata(), + ) + }) + }); + + BlockNoteTree::with_entries(entries).unwrap() + } +} + +#[derive(Debug)] +pub enum MockError { + DuplicatedNullifier, + DuplicatedNote, +} + +// AUTH +// ================================================================================================ + +/// Specifies which authentication mechanism is desired for accounts +pub enum Auth { + /// Creates a [SecretKey](miden_objects::crypto::dsa::rpo_falcon512::SecretKey) for the + /// account and creates a [BasicAuthenticator] that gets used for authenticating the account + BasicAuth, + + /// Does not create any authentication mechanism for the account. + NoAuth, +} + +impl fmt::Display for MockError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for MockError {} + +// MOCK CHAIN +// ================================================================================================ + +/// Structure chain data, used to build necessary openings and to construct [BlockHeader]. +#[derive(Debug, Clone)] +pub struct MockChain { + /// An append-only structure used to represent the history of blocks produced for this chain. + chain: Mmr, + + /// History of produced blocks. + blocks: Vec, + + /// Tree containing the latest `Nullifier`'s tree. + nullifiers: Smt, + + /// Tree containing the latest hash of each account. + accounts: SimpleSmt, + + /// Objects that have not yet been finalized. + /// + /// These will become available once the block is sealed. + /// + /// Note: + /// - The [Note]s in this container do not have the `proof` set. + pending_objects: PendingObjects, + + /// NoteID |-> InputNote mapping to simplify transaction inputs retrieval + available_notes: BTreeMap, + + /// AccountId |-> Account mapping to simplify transaction creation + available_accounts: BTreeMap, + + removed_notes: Vec, +} + +impl Default for MockChain { + fn default() -> Self { + Self::new() + } +} + +impl MockChain { + // CONSTRUCTORS + // ---------------------------------------------------------------------------------------- + + pub fn new() -> Self { + Self { + chain: Mmr::default(), + blocks: vec![], + nullifiers: Smt::default(), + accounts: SimpleSmt::::new().expect("depth too big for SimpleSmt"), + pending_objects: PendingObjects::new(), + available_notes: BTreeMap::new(), + available_accounts: BTreeMap::new(), + removed_notes: vec![], + } + } + + pub fn add_executed_transaction(&mut self, transaction: ExecutedTransaction) { + let mut account = transaction.initial_account().clone(); + account.apply_delta(transaction.account_delta()).unwrap(); + + // disregard private accounts, so it's easier to retrieve data + let account_update_details = AccountUpdateDetails::New(account.clone()); + + let block_account_update = BlockAccountUpdate::new( + transaction.account_id(), + account.hash(), + account_update_details, + vec![transaction.id()], + ); + self.pending_objects.updated_accounts.push(block_account_update); + + for note in transaction.input_notes().iter() { + // TODO: check that nullifiers are not duplicate + self.pending_objects.created_nullifiers.push(note.nullifier()); + self.removed_notes.push(note.id()); + } + + // TODO: check that notes are not duplicate + let output_notes: Vec = transaction.output_notes().iter().cloned().collect(); + self.pending_objects.output_note_batches.push(output_notes); + self.pending_objects + .included_transactions + .push((transaction.id(), transaction.account_id())); + } + + /// Add a public [Note] to the pending objects. + /// A block has to be created to finalize the new entity. + pub fn add_note(&mut self, note: Note) { + self.pending_objects.output_note_batches.push(vec![OutputNote::Full(note)]); + } + + /// Add a P2ID [Note] to the pending objects and returns it. + /// A block has to be created to finalize the new entity. + pub fn add_p2id_note( + &mut self, + sender_account_id: AccountId, + target_account_id: AccountId, + asset: &[Asset], + note_type: NoteType, + ) -> Result { + let mut rng = RpoRandomCoin::new(Word::default()); + + let note = create_p2id_note( + sender_account_id, + target_account_id, + asset.to_vec(), + note_type, + Default::default(), + &mut rng, + )?; + + self.add_note(note.clone()); + + Ok(note) + } + + /// Mark a [Note] as consumed by inserting its nullifier into the block. + /// A block has to be created to finalize the new entity. + pub fn add_nullifier(&mut self, nullifier: Nullifier) { + self.pending_objects.created_nullifiers.push(nullifier); + } + + // OTHER IMPLEMENTATIONS + // ================================================================================================ + + pub fn add_new_wallet(&mut self, auth_method: Auth, assets: Vec) -> Account { + let account_builder = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .nonce(Felt::ZERO) + .add_assets(assets); + self.add_from_account_builder(auth_method, account_builder) + } + + pub fn add_existing_wallet(&mut self, auth_method: Auth, assets: Vec) -> Account { + let account_builder = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .nonce(Felt::ONE) + .add_assets(assets); + self.add_from_account_builder(auth_method, account_builder) + } + + pub fn add_new_faucet( + &mut self, + auth_method: Auth, + token_symbol: &str, + max_supply: u64, + ) -> MockFungibleFaucet { + let metadata: [Felt; 4] = [ + max_supply.try_into().unwrap(), + Felt::new(10), + TokenSymbol::new(token_symbol).unwrap().into(), + ZERO, + ]; + + let faucet_metadata = SlotItem::new_value(1, 0, metadata); + + let account_builder = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .nonce(Felt::ZERO) + .account_type(AccountType::FungibleFaucet) + .add_storage_item(faucet_metadata); + + let account = self.add_from_account_builder(auth_method, account_builder); + + MockFungibleFaucet(account) + } + + pub fn add_existing_faucet( + &mut self, + auth_method: Auth, + token_symbol: &str, + max_supply: u64, + ) -> MockFungibleFaucet { + let metadata: [Felt; 4] = [ + max_supply.try_into().unwrap(), + Felt::new(10), + TokenSymbol::new(token_symbol).unwrap().into(), + ZERO, + ]; + + let faucet_metadata = SlotItem::new_value(1, 0, metadata); + + let account_builder = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .nonce(Felt::ONE) + .account_type(AccountType::FungibleFaucet) + .add_storage_item(faucet_metadata); + MockFungibleFaucet(self.add_from_account_builder(auth_method, account_builder)) + } + + /// Add a new [Account] from an [AccountBuilder] to the list of pending objects. + /// A block has to be created to finalize the new entity. + pub fn add_from_account_builder( + &mut self, + auth_method: Auth, + account_builder: AccountBuilder, + ) -> Account { + let (account, seed, authenticator) = match auth_method { + Auth::BasicAuth => { + let mut rng = StdRng::from_entropy(); + + let (acc, seed, auth) = account_builder + .build_with_auth(&TransactionKernel::assembler(), &mut rng) + .unwrap(); + + let authenticator = BasicAuthenticator::::new(&[( + auth.public_key().into(), + AuthSecretKey::RpoFalcon512(auth), + )]); + + (acc, seed, Some(authenticator)) + }, + Auth::NoAuth => { + let (account, seed) = + account_builder.build(TransactionKernel::assembler_testing()).unwrap(); + (account, seed, None) + }, + }; + + let seed = account.is_new().then_some(seed); + self.available_accounts + .insert(account.id(), MockAccount::new(account.clone(), seed, authenticator)); + self.add_account(account.clone()); + + account + } + + /// Add a new [Account] to the list of pending objects. + /// A block has to be created to finalize the new entity. + pub fn add_account(&mut self, account: Account) { + self.pending_objects.updated_accounts.push(BlockAccountUpdate::new( + account.id(), + account.hash(), + AccountUpdateDetails::New(account), + vec![], + )); + } + + pub fn build_tx_context(&self, account_id: AccountId) -> TransactionContextBuilder { + let mock_account = self.available_accounts.get(&account_id).unwrap(); + + TransactionContextBuilder::new(mock_account.account().clone()) + .authenticator(mock_account.authenticator().clone()) + .account_seed(mock_account.seed().cloned()) + .mock_chain(self.clone()) + } + + pub fn get_transaction_inputs( + &self, + account: Account, + account_seed: Option, + notes: &[NoteId], + ) -> TransactionInputs { + let block_header = self.blocks.last().unwrap().header(); + + let mut input_notes = vec![]; + let mut block_headers_map: BTreeMap = BTreeMap::new(); + for note in notes { + let input_note = self.available_notes.get(note).unwrap().clone(); + block_headers_map.insert( + input_note.location().unwrap().block_num(), + self.blocks + .get(input_note.location().unwrap().block_num() as usize) + .unwrap() + .header(), + ); + input_notes.push(input_note); + } + + let block_headers: Vec = block_headers_map.values().cloned().collect(); + let mmr = mmr_to_chain_mmr(&self.chain, &block_headers).unwrap(); + + TransactionInputs::new( + account, + account_seed, + block_header, + mmr, + InputNotes::new(input_notes).unwrap(), + ) + .unwrap() + } + + // MODIFIERS + // ========================================================================================= + + /// Creates the next block. + /// + /// This will also make all the objects currently pending available for use. + /// If `block_num` is `Some(number)`, `number` will be used as the new block's number + pub fn seal_block(&mut self, block_num: Option) -> Block { + let next_block_num = self.blocks.last().map_or(0, |b| b.header().block_num() + 1); + let block_num: u32 = if let Some(input_block_num) = block_num { + if input_block_num < next_block_num { + panic!("Input block number should be higher than the last block number"); + } + input_block_num + } else { + next_block_num + }; + + for update in self.pending_objects.updated_accounts.iter() { + self.accounts.insert(update.account_id().into(), *update.new_state_hash()); + + if let Some(mock_account) = self.available_accounts.get(&update.account_id()) { + let account = match update.details() { + AccountUpdateDetails::New(acc) => acc.clone(), + _ => panic!("The mockchain should have full account details"), + }; + self.available_accounts.insert( + update.account_id(), + MockAccount::new( + account, + mock_account.seed, + mock_account.authenticator.clone(), + ), + ); + } + } + + // TODO: + // - resetting the nullifier tree once defined at the protocol level. + // - inserting only nullifier from transactions included in the batches, once the batch + // kernel has been implemented. + for nullifier in self.pending_objects.created_nullifiers.iter() { + self.nullifiers.insert(nullifier.inner(), [block_num.into(), ZERO, ZERO, ZERO]); + } + let notes_tree = self.pending_objects.build_notes_tree(); + + let version = 0; + let previous = self.blocks.last(); + let peaks = self.chain.peaks(self.chain.forest()).unwrap(); + let chain_root: Digest = peaks.hash_peaks(); + let account_root = self.accounts.root(); + let prev_hash = previous.map_or(Digest::default(), |block| block.hash()); + let nullifier_root = self.nullifiers.root(); + let note_root = notes_tree.root(); + let timestamp = + previous.map_or(TIMESTAMP_START, |block| block.header().timestamp() + TIMESTAMP_STEP); + let tx_hash = + compute_tx_hash(self.pending_objects.included_transactions.clone().into_iter()); + + // TODO: Set `proof_hash` to the correct value once the kernel is available. + let proof_hash = Digest::default(); + + let header = BlockHeader::new( + version, + prev_hash, + block_num, + chain_root, + account_root, + nullifier_root, + note_root, + tx_hash, + proof_hash, + timestamp, + ); + + let block = Block::new( + header, + self.pending_objects.updated_accounts.clone(), + self.pending_objects.output_note_batches.clone(), + self.pending_objects.created_nullifiers.clone(), + ) + .unwrap(); + + for (batch_index, note_batch) in self.pending_objects.output_note_batches.iter().enumerate() + { + for (note_index, note) in note_batch.iter().enumerate() { + // All note details should be OutputNote::Full at this point + match note { + OutputNote::Full(note) => { + let block_note_index = BlockNoteIndex::new(batch_index, note_index); + let note_path = notes_tree.get_note_path(block_note_index).unwrap(); + let note_inclusion_proof = NoteInclusionProof::new( + block.header().block_num(), + block_note_index.to_absolute_index(), + note_path, + ) + .unwrap(); + + self.available_notes.insert( + note.id(), + InputNote::authenticated(note.clone(), note_inclusion_proof), + ); + }, + _ => continue, + } + } + } + + for removed_note in self.removed_notes.iter() { + self.available_notes.remove(removed_note); + } + + self.blocks.push(block.clone()); + self.chain.add(header.hash()); + self.reset_pending(); + + block + } + + fn reset_pending(&mut self) { + self.pending_objects = PendingObjects::new(); + self.removed_notes = vec![]; + } + + // ACCESSORS + // ========================================================================================= + + /// Get the latest [ChainMmr]. + pub fn chain(&self) -> ChainMmr { + let block_headers: Vec = self.blocks.iter().map(|b| b.header()).collect(); + mmr_to_chain_mmr(&self.chain, &block_headers).unwrap() + } + + /// Get a reference to [BlockHeader] with `block_number`. + pub fn block_header(&self, block_number: usize) -> BlockHeader { + self.blocks[block_number].header() + } + + /// Get a reference to the nullifier tree. + pub fn nullifiers(&self) -> &Smt { + &self.nullifiers + } + + pub fn available_notes(&self) -> Vec { + self.available_notes.values().cloned().collect() + } +} + +// MOCK CHAIN BUILDER +// ================================================================================================ + +#[derive(Default)] +pub struct MockChainBuilder { + accounts: Vec, + notes: Vec, + starting_block_num: u32, +} + +impl MockChainBuilder { + pub fn accounts(mut self, accounts: Vec) -> Self { + self.accounts = accounts; + self + } + + pub fn notes(mut self, notes: Vec) -> Self { + self.notes = notes; + self + } + + pub fn starting_block_num(mut self, block_num: u32) -> Self { + self.starting_block_num = block_num; + self + } + + /// Returns a [MockChain] with a single block + pub fn build(self) -> MockChain { + let mut chain = MockChain::new(); + for account in self.accounts { + chain.add_account(account); + } + + for note in self.notes { + chain.add_note(note); + } + + chain.seal_block(Some(self.starting_block_num)); + chain + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Converts the MMR into partial MMR by copying all leaves from MMR to partial MMR. +fn mmr_to_chain_mmr(mmr: &Mmr, blocks: &[BlockHeader]) -> Result { + let target_forest = mmr.forest() - 1; + let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks(target_forest)?); + + for i in 0..target_forest { + let node = mmr.get(i)?; + let path = mmr.open(i, target_forest)?.merkle_path; + partial_mmr.track(i, node, &path)?; + } + + Ok(ChainMmr::new(partial_mmr, blocks.to_vec()).unwrap()) +} diff --git a/miden-tx/src/testing/mock_host.rs b/miden-tx/src/testing/mock_host.rs index 7580e6e2c..a4d742c35 100644 --- a/miden-tx/src/testing/mock_host.rs +++ b/miden-tx/src/testing/mock_host.rs @@ -1,17 +1,20 @@ // MOCK HOST // ================================================================================================ -use alloc::string::ToString; +use alloc::{rc::Rc, string::ToString, sync::Arc}; use miden_lib::transaction::TransactionEvent; -use miden_objects::accounts::{AccountStub, AccountVaultDelta}; +use miden_objects::{ + accounts::{AccountStub, AccountVaultDelta}, + Digest, +}; use vm_processor::{ AdviceExtractor, AdviceInjector, AdviceInputs, AdviceProvider, AdviceSource, ContextId, - ExecutionError, Host, HostResponse, MemAdviceProvider, ProcessState, + ExecutionError, Host, HostResponse, MastForest, MastForestStore, MemAdviceProvider, + ProcessState, }; -use super::account_procs::AccountProcedureIndexMap; - +use crate::{host::AccountProcedureIndexMap, TransactionMastStore}; // MOCK HOST // ================================================================================================ @@ -22,16 +25,23 @@ use super::account_procs::AccountProcedureIndexMap; pub struct MockHost { adv_provider: MemAdviceProvider, acct_procedure_index_map: AccountProcedureIndexMap, + mast_store: Rc, } impl MockHost { /// Returns a new [MockHost] instance with the provided [AdviceInputs]. - pub fn new(account: AccountStub, advice_inputs: AdviceInputs) -> Self { + pub fn new( + account: AccountStub, + advice_inputs: AdviceInputs, + mast_store: Rc, + ) -> Self { let adv_provider: MemAdviceProvider = advice_inputs.into(); - let proc_index_map = AccountProcedureIndexMap::new(account.code_root(), &adv_provider); + let proc_index_map = + AccountProcedureIndexMap::new(account.code_commitment(), &adv_provider); Self { adv_provider, - acct_procedure_index_map: proc_index_map, + acct_procedure_index_map: proc_index_map.unwrap(), + mast_store, } } @@ -73,6 +83,10 @@ impl Host for MockHost { self.adv_provider.set_advice(process, &injector) } + fn get_mast_forest(&self, node_digest: &Digest) -> Option> { + self.mast_store.get(node_digest) + } + fn on_event( &mut self, process: &S, diff --git a/miden-tx/src/testing/mod.rs b/miden-tx/src/testing/mod.rs index 9d8cf2670..d7dadadb3 100644 --- a/miden-tx/src/testing/mod.rs +++ b/miden-tx/src/testing/mod.rs @@ -1,9 +1,10 @@ -mod account_procs; pub mod executor; pub use mock_host::MockHost; mod mock_host; +pub mod mock_chain; + pub use tx_context::{TransactionContext, TransactionContextBuilder}; mod tx_context; diff --git a/miden-tx/src/testing/tx_context.rs b/miden-tx/src/testing/tx_context/builder.rs similarity index 78% rename from miden-tx/src/testing/tx_context.rs rename to miden-tx/src/testing/tx_context/builder.rs index 010d8268c..08623ce22 100644 --- a/miden-tx/src/testing/tx_context.rs +++ b/miden-tx/src/testing/tx_context/builder.rs @@ -1,21 +1,23 @@ -use alloc::vec::Vec; +// TRANSACTION CONTEXT BUILDER +// ================================================================================================ -use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; +use std::{collections::BTreeMap, vec::Vec}; + +use miden_lib::transaction::TransactionKernel; use miden_objects::{ accounts::{ account_id::testing::{ ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_SENDER, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, ACCOUNT_ID_SENDER, }, - Account, AccountCode, AccountId, + Account, AccountId, }, - assembly::{Assembler, ModuleAst}, + assembly::Assembler, assets::{Asset, FungibleAsset}, - notes::{Note, NoteId, NoteType}, + notes::{Note, NoteExecutionHint, NoteId, NoteType}, testing::{ - account_code::{ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT, ACCOUNT_CREATE_NOTE_MAST_ROOT}, - block::{MockChain, MockChainBuilder}, + account_code::ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT, constants::{ CONSUMED_ASSET_1_AMOUNT, CONSUMED_ASSET_2_AMOUNT, CONSUMED_ASSET_3_AMOUNT, NON_FUNGIBLE_ASSET_DATA_2, @@ -24,179 +26,130 @@ use miden_objects::{ prepare_word, storage::prepare_assets, }, - transaction::{ - InputNote, InputNotes, OutputNote, PreparedTransaction, TransactionArgs, TransactionInputs, - }, + transaction::{OutputNote, TransactionArgs, TransactionScript}, }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; -use vm_processor::{AdviceInputs, ExecutionError, Felt, Process, Word}; -use winter_maybe_async::maybe_async; - -use super::{executor::CodeExecutor, MockHost}; -use crate::{DataStore, DataStoreError}; - -// TRANSACTION CONTEXT -// ================================================================================================ - -#[derive(Debug, Clone)] -pub struct TransactionContext { - mock_chain: MockChain, - expected_output_notes: Vec, - tx_args: TransactionArgs, - tx_inputs: TransactionInputs, - advice_inputs: AdviceInputs, -} - -impl TransactionContext { - pub fn execute_code(&self, code: &str) -> Result, ExecutionError> { - let assembler = TransactionKernel::assembler().with_debug_mode(true); - let program = assembler.compile(code).unwrap(); - let tx = PreparedTransaction::new(program, self.tx_inputs.clone(), self.tx_args.clone()); - let (stack_inputs, mut advice_inputs) = tx.get_kernel_inputs(); - advice_inputs.extend(self.advice_inputs.clone()); - - CodeExecutor::new(MockHost::new(tx.account().into(), advice_inputs)) - .stack_inputs(stack_inputs) - .run(code) - } - - pub fn account(&self) -> &Account { - self.tx_inputs.account() - } - - pub fn expected_output_notes(&self) -> &[Note] { - &self.expected_output_notes - } - - pub fn mock_chain(&self) -> &MockChain { - &self.mock_chain - } - - pub fn input_notes(&self) -> InputNotes { - InputNotes::new(self.mock_chain.available_notes().clone()).unwrap() - } - - pub fn tx_args(&self) -> &TransactionArgs { - &self.tx_args - } - - pub fn set_tx_args(&mut self, tx_args: TransactionArgs) { - self.tx_args = tx_args; - } - - pub fn tx_inputs(&self) -> &TransactionInputs { - &self.tx_inputs - } -} - -impl DataStore for TransactionContext { - #[maybe_async] - fn get_transaction_inputs( - &self, - account_id: AccountId, - block_num: u32, - notes: &[NoteId], - ) -> Result { - assert_eq!(account_id, self.tx_inputs.account().id()); - assert_eq!(block_num, self.tx_inputs.block_header().block_num()); - assert_eq!(notes.len(), self.tx_inputs.input_notes().num_notes()); - - Ok(self.tx_inputs.clone()) - } - - #[maybe_async] - fn get_account_code(&self, account_id: AccountId) -> Result { - assert_eq!(account_id, self.tx_inputs.account().id()); - Ok(self.tx_inputs.account().code().module().clone()) - } -} +use vm_processor::{AdviceInputs, AdviceMap, Felt, Word}; -// TRANSACTION CONTEXT BUILDER -// ================================================================================================ +use super::TransactionContext; +use crate::testing::mock_chain::{MockAuthenticator, MockChain, MockChainBuilder}; pub struct TransactionContextBuilder { assembler: Assembler, account: Account, account_seed: Option, - advice_inputs: Option, + advice_map: Option, + advice_inputs: AdviceInputs, + authenticator: Option, input_notes: Vec, expected_output_notes: Vec, - tx_args: TransactionArgs, + tx_script: Option, + note_args: BTreeMap, rng: ChaCha20Rng, + mock_chain: Option, } impl TransactionContextBuilder { pub fn new(account: Account) -> Self { - let tx_args = TransactionArgs::default(); Self { - assembler: TransactionKernel::assembler().with_debug_mode(true), + assembler: TransactionKernel::assembler_testing(), account, account_seed: None, input_notes: Vec::new(), expected_output_notes: Vec::new(), - tx_args, - advice_inputs: None, + advice_map: None, rng: ChaCha20Rng::from_seed([0_u8; 32]), + tx_script: None, + authenticator: None, + advice_inputs: Default::default(), + note_args: BTreeMap::new(), + mock_chain: None, } } - pub fn with_standard_account(account_id: u64, nonce: Felt) -> Self { - let assembler = TransactionKernel::assembler().with_debug_mode(true); - let account = Account::mock(account_id, nonce, AccountCode::mock_wallet(&assembler)); + pub fn with_standard_account(nonce: Felt) -> Self { + let assembler = TransactionKernel::assembler_testing(); + let account = Account::mock( + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, + nonce, + assembler.clone(), + ); Self { - assembler, + assembler: assembler.clone(), account, account_seed: None, + authenticator: None, input_notes: Vec::new(), expected_output_notes: Vec::new(), - tx_args: TransactionArgs::default(), - advice_inputs: None, + advice_map: None, + advice_inputs: Default::default(), rng: ChaCha20Rng::from_seed([0_u8; 32]), + tx_script: None, + note_args: BTreeMap::new(), + mock_chain: None, } } pub fn with_fungible_faucet(acct_id: u64, nonce: Felt, initial_balance: Felt) -> Self { - let assembler = TransactionKernel::assembler().with_debug_mode(true); - let account = Account::mock_fungible_faucet(acct_id, nonce, initial_balance, &assembler); + let assembler = TransactionKernel::assembler_testing(); + let account = + Account::mock_fungible_faucet(acct_id, nonce, initial_balance, assembler.clone()); Self { assembler, account, account_seed: None, + authenticator: None, input_notes: Vec::new(), expected_output_notes: Vec::new(), - tx_args: TransactionArgs::default(), - advice_inputs: None, + advice_inputs: Default::default(), + advice_map: None, rng: ChaCha20Rng::from_seed([0_u8; 32]), + tx_script: None, + note_args: BTreeMap::new(), + mock_chain: None, } } pub fn with_non_fungible_faucet(acct_id: u64, nonce: Felt, empty_reserved_slot: bool) -> Self { - let assembler = TransactionKernel::assembler().with_debug_mode(true); - let account = - Account::mock_non_fungible_faucet(acct_id, nonce, empty_reserved_slot, &assembler); + let assembler = TransactionKernel::assembler_testing(); + let account = Account::mock_non_fungible_faucet( + acct_id, + nonce, + empty_reserved_slot, + assembler.clone(), + ); Self { assembler, account, account_seed: None, + authenticator: None, input_notes: Vec::new(), expected_output_notes: Vec::new(), - tx_args: TransactionArgs::default(), - advice_inputs: None, + advice_map: None, + advice_inputs: Default::default(), rng: ChaCha20Rng::from_seed([0_u8; 32]), + tx_script: None, + note_args: BTreeMap::new(), + mock_chain: None, } } - pub fn account_seed(mut self, account_seed: Word) -> Self { - self.account_seed = Some(account_seed); + pub fn account_seed(mut self, account_seed: Option) -> Self { + self.account_seed = account_seed; self } pub fn advice_inputs(mut self, advice_inputs: AdviceInputs) -> Self { - self.advice_inputs = Some(advice_inputs); + self.advice_inputs = advice_inputs; + self + } + + pub fn authenticator(mut self, authenticator: Option) -> Self { + self.authenticator = authenticator; self } @@ -205,6 +158,11 @@ impl TransactionContextBuilder { self } + pub fn tx_script(mut self, tx_script: TransactionScript) -> Self { + self.tx_script = Some(tx_script); + self + } + pub fn expected_notes(mut self, output_notes: Vec) -> Self { let output_notes = output_notes.into_iter().filter_map(|n| match n { OutputNote::Full(note) => Some(note), @@ -254,18 +212,21 @@ impl TransactionContextBuilder { inputs: impl IntoIterator, output: &Note, ) -> Note { - let code = format!( + let var_name = format!( " + use.miden::contracts::wallets::basic->wallet + begin # NOTE # --------------------------------------------------------------------------------- push.{recipient} + push.{execution_hint_always} push.{PUBLIC_NOTE} push.{aux} push.{tag} - call.{ACCOUNT_CREATE_NOTE_MAST_ROOT} + call.wallet::create_note - push.{asset} movup.4 + push.{asset} call.{ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT} dropw dropw dropw end @@ -275,7 +236,9 @@ impl TransactionContextBuilder { aux = output.metadata().aux(), tag = output.metadata().tag(), asset = prepare_assets(output.assets())[0], + execution_hint_always = Felt::from(NoteExecutionHint::always()) ); + let code = var_name; NoteBuilder::new(sender, ChaCha20Rng::from_seed(self.rng.gen())) .note_inputs(inputs) @@ -296,28 +259,36 @@ impl TransactionContextBuilder { ) -> Note { let code = format!( " + use.miden::contracts::wallets::basic->wallet + begin + # NOTE 0 # --------------------------------------------------------------------------------- + push.{recipient0} + push.{execution_hint_always} push.{PUBLIC_NOTE} push.{aux0} push.{tag0} - call.{ACCOUNT_CREATE_NOTE_MAST_ROOT} - push.{asset0} movup.4 + + call.wallet::create_note + + push.{asset0} call.{ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT} dropw dropw dropw # NOTE 1 # --------------------------------------------------------------------------------- push.{recipient1} + push.{execution_hint_always} push.{PUBLIC_NOTE} push.{aux1} push.{tag1} - call.{ACCOUNT_CREATE_NOTE_MAST_ROOT} + call.wallet::create_note - push.{asset1} movup.4 + push.{asset1} call.{ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT} dropw dropw dropw end @@ -331,6 +302,7 @@ impl TransactionContextBuilder { aux1 = output1.metadata().aux(), tag1 = output1.metadata().tag(), asset1 = prepare_assets(output1.assets())[0], + execution_hint_always = Felt::from(NoteExecutionHint::always()) ); NoteBuilder::new(sender, ChaCha20Rng::from_seed(self.rng.gen())) @@ -581,11 +553,21 @@ impl TransactionContextBuilder { self.input_notes(vec![input_note1, input_note2, input_note4]) } - pub fn build(mut self) -> TransactionContext { - let mut mock_chain = MockChainBuilder::new().notes(self.input_notes.clone()).build(); - mock_chain.seal_block(); - mock_chain.seal_block(); - mock_chain.seal_block(); + pub fn build(self) -> TransactionContext { + let mut mock_chain = if let Some(mock_chain) = self.mock_chain { + mock_chain + } else { + MockChainBuilder::default().notes(self.input_notes.clone()).build() + }; + for _ in 0..4 { + mock_chain.seal_block(None); + } + + let mut tx_args = TransactionArgs::new( + self.tx_script, + Some(self.note_args), + self.advice_map.unwrap_or_default(), + ); let input_note_ids: Vec = mock_chain.available_notes().iter().map(|n| n.id()).collect(); @@ -596,14 +578,21 @@ impl TransactionContextBuilder { &input_note_ids, ); - self.tx_args.extend_expected_output_notes(self.expected_output_notes.clone()); + tx_args.extend_expected_output_notes(self.expected_output_notes.clone()); TransactionContext { mock_chain, expected_output_notes: self.expected_output_notes, - tx_args: self.tx_args, + tx_args, tx_inputs, - advice_inputs: self.advice_inputs.unwrap_or_default(), + authenticator: self.authenticator, + advice_inputs: self.advice_inputs, + assembler: self.assembler, } } + + pub fn mock_chain(mut self, mock_chain: MockChain) -> TransactionContextBuilder { + self.mock_chain = Some(mock_chain); + self + } } diff --git a/miden-tx/src/testing/tx_context/mod.rs b/miden-tx/src/testing/tx_context/mod.rs new file mode 100644 index 000000000..f2eae5e23 --- /dev/null +++ b/miden-tx/src/testing/tx_context/mod.rs @@ -0,0 +1,133 @@ +use alloc::{rc::Rc, vec::Vec}; +use std::sync::Arc; + +use miden_lib::transaction::TransactionKernel; +use miden_objects::{ + accounts::{Account, AccountId}, + assembly::Assembler, + notes::{Note, NoteId}, + transaction::{ExecutedTransaction, InputNote, InputNotes, TransactionArgs, TransactionInputs}, +}; +use vm_processor::{AdviceInputs, ExecutionError, Process}; +use winter_maybe_async::{maybe_async, maybe_await}; + +use super::{ + executor::CodeExecutor, + mock_chain::{MockAuthenticator, MockChain}, + MockHost, +}; +use crate::{ + DataStore, DataStoreError, TransactionExecutor, TransactionExecutorError, TransactionMastStore, +}; + +mod builder; +pub use builder::TransactionContextBuilder; + +// TRANSACTION CONTEXT +// ================================================================================================ + +#[derive(Clone)] +/// Represents all needed data for executing a transaction, or arbitrary code. +/// +/// It implements [DataStore], so transactions may be executed with +/// [TransactionExecutor](crate::TransactionExecutor) +pub struct TransactionContext { + mock_chain: MockChain, + expected_output_notes: Vec, + tx_args: TransactionArgs, + tx_inputs: TransactionInputs, + advice_inputs: AdviceInputs, + authenticator: Option, + assembler: Assembler, +} +impl TransactionContext { + /// Executes arbitrary code within the context of a mocked transaction environment and returns + /// the resulting [Process]. + /// + /// The code is compiled with the assembler attached to this context and executed with advice + /// inputs constructed from the data stored in the context. The program is run on a [MockHost] + /// which is loaded with the procedures exposed by the transaction kernel, and also individual + /// kernel functions (not normally exposed). + /// + /// # Errors + /// Returns an error if the assembly of execution of the provided code fails. + pub fn execute_code(&self, code: &str) -> Result, ExecutionError> { + let (stack_inputs, mut advice_inputs) = TransactionKernel::prepare_inputs( + &self.tx_inputs, + &self.tx_args, + Some(self.advice_inputs.clone()), + ); + advice_inputs.extend(self.advice_inputs.clone()); + + let mast_store = Rc::new(TransactionMastStore::new()); + + let test_lib = TransactionKernel::kernel_as_library(); + mast_store.insert(Arc::new(test_lib.mast_forest().clone())); + + let program = self.assembler.clone().assemble_program(code).unwrap(); + mast_store.insert(Arc::new(program.mast_forest().clone())); + mast_store.load_transaction_code(&self.tx_inputs, &self.tx_args); + + CodeExecutor::new(MockHost::new(self.tx_inputs.account().into(), advice_inputs, mast_store)) + .stack_inputs(stack_inputs) + .execute_program(program) + } + + /// Executes the transaction through a [TransactionExecutor] + #[maybe_async] + pub fn execute(self) -> Result { + let mock_data_store = self.clone(); + + let account_id = self.account().id(); + let block_num = mock_data_store.tx_inputs.block_header().block_num(); + let tx_executor = + TransactionExecutor::new(mock_data_store, self.authenticator.map(Rc::new)); + let notes: Vec = self.tx_inputs.input_notes().into_iter().map(|n| n.id()).collect(); + + maybe_await!(tx_executor.execute_transaction(account_id, block_num, ¬es, self.tx_args)) + } + + pub fn account(&self) -> &Account { + self.tx_inputs.account() + } + + pub fn expected_output_notes(&self) -> &[Note] { + &self.expected_output_notes + } + + pub fn mock_chain(&self) -> &MockChain { + &self.mock_chain + } + + pub fn input_notes(&self) -> InputNotes { + InputNotes::new(self.mock_chain.available_notes().clone()).unwrap() + } + + pub fn tx_args(&self) -> &TransactionArgs { + &self.tx_args + } + + pub fn set_tx_args(&mut self, tx_args: TransactionArgs) { + self.tx_args = tx_args; + } + + pub fn tx_inputs(&self) -> &TransactionInputs { + &self.tx_inputs + } +} + +impl DataStore for TransactionContext { + #[maybe_async] + fn get_transaction_inputs( + &self, + account_id: AccountId, + block_num: u32, + notes: &[NoteId], + ) -> Result { + assert_eq!(account_id, self.tx_inputs.account().id()); + assert_eq!(block_num, self.tx_inputs.block_header().block_num()); + assert_eq!(notes.len(), self.tx_inputs.input_notes().num_notes()); + + Ok(self.tx_inputs.clone()) + } +} diff --git a/miden-tx/src/testing/utils.rs b/miden-tx/src/testing/utils.rs index 90e49c09f..be8ce357f 100644 --- a/miden-tx/src/testing/utils.rs +++ b/miden-tx/src/testing/utils.rs @@ -3,6 +3,6 @@ use miden_lib::transaction::memory; // TEST HELPERS // ================================================================================================ -pub fn consumed_note_data_ptr(note_idx: u32) -> memory::MemoryAddress { - memory::CONSUMED_NOTE_DATA_SECTION_OFFSET + note_idx * memory::NOTE_MEM_SIZE +pub fn input_note_data_ptr(note_idx: u32) -> memory::MemoryAddress { + memory::INPUT_NOTE_DATA_SECTION_OFFSET + note_idx * memory::NOTE_MEM_SIZE } diff --git a/miden-tx/src/tests/kernel_tests/mod.rs b/miden-tx/src/tests/kernel_tests/mod.rs index 6b218f4a0..202c0b484 100644 --- a/miden-tx/src/tests/kernel_tests/mod.rs +++ b/miden-tx/src/tests/kernel_tests/mod.rs @@ -1,9 +1,8 @@ use alloc::string::String; use miden_lib::transaction::memory::{ - CREATED_NOTE_ASSETS_OFFSET, CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_NUM_ASSETS_OFFSET, - CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_SECTION_OFFSET, NOTE_MEM_SIZE, - NUM_CREATED_NOTES_PTR, + NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, OUTPUT_NOTE_METADATA_OFFSET, + OUTPUT_NOTE_NUM_ASSETS_OFFSET, OUTPUT_NOTE_RECIPIENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, }; use miden_objects::{ notes::Note, @@ -56,45 +55,45 @@ pub fn output_notes_data_procedure(notes: &[Note]) -> String { # populate note 0 push.{note_0_metadata} - push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_METADATA_OFFSET} add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{OUTPUT_NOTE_METADATA_OFFSET} add mem_storew dropw push.{note_0_recipient} - push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_RECIPIENT_OFFSET} add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{OUTPUT_NOTE_RECIPIENT_OFFSET} add mem_storew dropw push.{note_0_num_assets} - push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_NUM_ASSETS_OFFSET} add mem_store + push.{OUTPUT_NOTE_SECTION_OFFSET}.{OUTPUT_NOTE_NUM_ASSETS_OFFSET} add mem_store push.{} - push.{CREATED_NOTE_SECTION_OFFSET}.{CREATED_NOTE_ASSETS_OFFSET} add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{OUTPUT_NOTE_ASSETS_OFFSET} add mem_storew dropw # populate note 1 push.{note_1_metadata} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_METADATA_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{OUTPUT_NOTE_METADATA_OFFSET} add add mem_storew dropw push.{note_1_recipient} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{OUTPUT_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw push.{note_1_num_assets} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_NUM_ASSETS_OFFSET} add add mem_store + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{OUTPUT_NOTE_NUM_ASSETS_OFFSET} add add mem_store push.{} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{CREATED_NOTE_ASSETS_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_1_OFFSET}.{OUTPUT_NOTE_ASSETS_OFFSET} add add mem_storew dropw # populate note 2 push.{note_2_metadata} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_METADATA_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{OUTPUT_NOTE_METADATA_OFFSET} add add mem_storew dropw push.{note_2_recipient} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{OUTPUT_NOTE_RECIPIENT_OFFSET} add add mem_storew dropw push.{note_2_num_assets} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_NUM_ASSETS_OFFSET} add add mem_store + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{OUTPUT_NOTE_NUM_ASSETS_OFFSET} add add mem_store push.{} - push.{CREATED_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{CREATED_NOTE_ASSETS_OFFSET} add add mem_storew dropw + push.{OUTPUT_NOTE_SECTION_OFFSET}.{NOTE_2_OFFSET}.{OUTPUT_NOTE_ASSETS_OFFSET} add add mem_storew dropw - # set num created notes - push.{}.{NUM_CREATED_NOTES_PTR} mem_store + # set num output notes + push.{}.{NUM_OUTPUT_NOTES_PTR} mem_store end ", note_0_assets[0], diff --git a/miden-tx/src/tests/kernel_tests/test_account.rs b/miden-tx/src/tests/kernel_tests/test_account.rs index a378b29d1..ace2250cb 100644 --- a/miden-tx/src/tests/kernel_tests/test_account.rs +++ b/miden-tx/src/tests/kernel_tests/test_account.rs @@ -1,4 +1,4 @@ -use miden_lib::transaction::memory::{ACCT_CODE_ROOT_PTR, ACCT_NEW_CODE_ROOT_PTR}; +use miden_lib::transaction::memory::{ACCT_CODE_COMMITMENT_PTR, ACCT_NEW_CODE_COMMITMENT_PTR}; use miden_objects::{ accounts::{ account_id::testing::{ @@ -25,14 +25,10 @@ use crate::{ #[test] pub fn test_set_code_is_not_immediate() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = " - use.miden::kernels::tx::prologue - use.miden::account + use.kernel::prologue + use.kernel::account begin exec.prologue::prepare_transaction push.1.2.3.4 @@ -43,35 +39,32 @@ pub fn test_set_code_is_not_immediate() { let process = tx_context.execute_code(code).unwrap(); assert_eq!( - read_root_mem_value(&process, ACCT_CODE_ROOT_PTR), - tx_context.account().code().root().as_elements(), - "the code root must not change immediatelly", + read_root_mem_value(&process, ACCT_CODE_COMMITMENT_PTR), + tx_context.account().code().commitment().as_elements(), + "the code commitment must not change immediately", ); assert_eq!( - read_root_mem_value(&process, ACCT_NEW_CODE_ROOT_PTR), + read_root_mem_value(&process, ACCT_NEW_CODE_COMMITMENT_PTR), [ONE, Felt::new(2), Felt::new(3), Felt::new(4)], - "the code root must be cached", + "the code commitment must be cached", ); } #[test] pub fn test_set_code_succeeds() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let output_notes_data_procedure = output_notes_data_procedure(tx_context.expected_output_notes()); let code = format!( " - use.miden::account - use.miden::kernels::tx::prologue - use.miden::kernels::tx::epilogue + use.kernel::account + use.kernel::prologue + use.kernel::epilogue {output_notes_data_procedure} begin @@ -93,9 +86,9 @@ pub fn test_set_code_succeeds() { let process = tx_context.execute_code(&code).unwrap(); assert_eq!( - read_root_mem_value(&process, ACCT_CODE_ROOT_PTR), + read_root_mem_value(&process, ACCT_CODE_COMMITMENT_PTR), [ZERO, ONE, Felt::new(2), Felt::new(3)], - "the code root must change after the epilogue", + "the code commitment must change after the epilogue", ); } @@ -126,8 +119,7 @@ pub fn test_account_type() { let code = format!( " - use.miden::kernels::tx::memory - use.miden::kernels::tx::account + use.kernel::account begin exec.account::{} @@ -164,7 +156,7 @@ pub fn test_account_type() { fn test_validate_id_fails_on_insufficient_ones() { let code = format!( " - use.miden::kernels::tx::account + use.kernel::account begin push.{ACCOUNT_ID_INSUFFICIENT_ONES} @@ -192,7 +184,7 @@ fn test_is_faucet_procedure() { let code = format!( " - use.miden::kernels::tx::account + use.kernel::account begin push.{account_id} @@ -222,23 +214,18 @@ fn test_is_faucet_procedure() { #[test] fn test_get_item() { for storage_item in [AccountStorage::mock_item_0(), AccountStorage::mock_item_1()] { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::account - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::prologue begin exec.prologue::prepare_transaction - # push the account storage item index push.{item_index} - + # assert the item value is correct exec.account::get_item push.{item_value} @@ -255,11 +242,7 @@ fn test_get_item() { #[test] fn test_set_item() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); // copy the initial account slots (SMT) let mut account_smt = tx_context.account().storage().slots().clone(); @@ -273,9 +256,9 @@ fn test_set_item() { let code = format!( " - use.miden::account - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::memory + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -287,10 +270,13 @@ fn test_set_item() { # assert old value was empty padw assert_eqw - + dropw # assert the new item value is properly stored exec.memory::get_acct_storage_root push.{new_root} assert_eqw + dropw dropw + dropw dropw + end ", new_value = prepare_word(&new_item_value), @@ -309,16 +295,12 @@ fn test_get_storage_data_type() { AccountStorage::mock_item_1(), AccountStorage::mock_item_2(), ] { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::kernels::tx::account - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -361,18 +343,14 @@ fn test_get_storage_data_type() { #[test] fn test_get_map_item() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let storage_item = AccountStorage::mock_item_2(); for (key, value) in STORAGE_LEAVES_2 { let code = format!( " use.miden::account - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -396,17 +374,17 @@ fn test_get_map_item() { assert_eq!( Word::default(), process.get_stack_word(1), - "The the rest of the stack must be cleared", + "The rest of the stack must be cleared", ); assert_eq!( Word::default(), process.get_stack_word(2), - "The the rest of the stack must be cleared", + "The rest of the stack must be cleared", ); assert_eq!( Word::default(), process.get_stack_word(3), - "The the rest of the stack must be cleared", + "The rest of the stack must be cleared", ); } } @@ -418,18 +396,14 @@ fn test_set_map_item() { [Felt::new(9_u64), Felt::new(10_u64), Felt::new(11_u64), Felt::new(12_u64)], ); - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let storage_item = AccountStorage::mock_item_2(); let code = format!( " use.miden::account - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -472,17 +446,13 @@ fn test_set_map_item() { #[test] fn test_get_vault_commitment() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let account = tx_context.account(); let code = format!( " use.miden::account - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -504,33 +474,26 @@ fn test_get_vault_commitment() { #[test] fn test_authenticate_procedure() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let account = tx_context.tx_inputs().account(); - let proc0_index = LeafIndex::new(0).unwrap(); - let proc1_index = LeafIndex::new(1).unwrap(); + let tc_0: [Felt; 4] = + account.code().procedures()[0].mast_root().as_elements().try_into().unwrap(); + let tc_1: [Felt; 4] = + account.code().procedures()[1].mast_root().as_elements().try_into().unwrap(); + let tc_2: [Felt; 4] = + account.code().procedures()[2].mast_root().as_elements().try_into().unwrap(); - let test_cases = vec![ - (account.code().procedure_tree().get_leaf(&proc0_index), true), - (account.code().procedure_tree().get_leaf(&proc1_index), true), - ([ONE, ZERO, ONE, ZERO], false), - ]; + let test_cases = + vec![(tc_0, true), ([ONE, ZERO, ONE, ZERO], false), (tc_1, true), (tc_2, true)]; for (root, valid) in test_cases.into_iter() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::kernels::tx::account - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::prologue begin exec.prologue::prepare_transaction diff --git a/miden-tx/src/tests/kernel_tests/test_asset.rs b/miden-tx/src/tests/kernel_tests/test_asset.rs index e22e65a28..614ae45a9 100644 --- a/miden-tx/src/tests/kernel_tests/test_asset.rs +++ b/miden-tx/src/tests/kernel_tests/test_asset.rs @@ -26,7 +26,7 @@ fn test_create_fungible_asset_succeeds() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::asset begin @@ -66,7 +66,7 @@ fn test_create_non_fungible_asset_succeeds() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::asset begin @@ -100,7 +100,7 @@ fn test_validate_non_fungible_asset() { let code = format!( " - use.miden::kernels::tx::asset + use.kernel::asset begin push.{asset} exec.asset::validate_non_fungible_asset diff --git a/miden-tx/src/tests/kernel_tests/test_asset_vault.rs b/miden-tx/src/tests/kernel_tests/test_asset_vault.rs index 3cda16280..529ae674b 100644 --- a/miden-tx/src/tests/kernel_tests/test_asset_vault.rs +++ b/miden-tx/src/tests/kernel_tests/test_asset_vault.rs @@ -4,7 +4,6 @@ use miden_objects::{ account_id::testing::{ ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, }, AccountId, }, @@ -21,16 +20,12 @@ use crate::{testing::TransactionContextBuilder, tests::kernel_tests::read_root_m #[test] fn test_get_balance() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -51,14 +46,10 @@ fn test_get_balance() { #[test] fn test_get_balance_non_fungible_fails() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -76,16 +67,12 @@ fn test_get_balance_non_fungible_fails() { #[test] fn test_has_non_fungible_asset() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let non_fungible_asset = tx_context.account().vault().assets().next().unwrap(); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -104,11 +91,7 @@ fn test_has_non_fungible_asset() { #[test] fn test_add_fungible_asset_success() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let mut account_vault = tx_context.account().vault().clone(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let amount = FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT; @@ -117,7 +100,7 @@ fn test_add_fungible_asset_success() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -144,11 +127,7 @@ fn test_add_fungible_asset_success() { #[test] fn test_add_non_fungible_asset_fail_overflow() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let mut account_vault = tx_context.account().vault().clone(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); @@ -158,7 +137,7 @@ fn test_add_non_fungible_asset_fail_overflow() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -178,11 +157,7 @@ fn test_add_non_fungible_asset_fail_overflow() { #[test] fn test_add_non_fungible_asset_success() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let faucet_id: AccountId = ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); let add_non_fungible_asset = Asset::NonFungible( @@ -194,7 +169,7 @@ fn test_add_non_fungible_asset_success() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -221,11 +196,7 @@ fn test_add_non_fungible_asset_success() { #[test] fn test_add_non_fungible_asset_fail_duplicate() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let faucet_id: AccountId = ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); let non_fungible_asset_details = @@ -235,7 +206,7 @@ fn test_add_non_fungible_asset_fail_duplicate() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -255,11 +226,7 @@ fn test_add_non_fungible_asset_fail_duplicate() { #[test] fn test_remove_fungible_asset_success_no_balance_remaining() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let mut account_vault = tx_context.account().vault().clone(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); @@ -269,13 +236,10 @@ fn test_remove_fungible_asset_success_no_balance_remaining() { let code = format!( " - use.miden::kernels::tx::prologue - use.miden::account - begin - exec.prologue::prepare_transaction + exec.::kernel::prologue::prepare_transaction push.{FUNGIBLE_ASSET} - exec.account::remove_asset + exec.::miden::account::remove_asset end ", FUNGIBLE_ASSET = prepare_word(&remove_fungible_asset.into()) @@ -296,11 +260,7 @@ fn test_remove_fungible_asset_success_no_balance_remaining() { #[test] fn test_remove_fungible_asset_fail_remove_too_much() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT + 1; let remove_fungible_asset = @@ -308,7 +268,7 @@ fn test_remove_fungible_asset_fail_remove_too_much() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -327,11 +287,7 @@ fn test_remove_fungible_asset_fail_remove_too_much() { #[test] fn test_remove_fungible_asset_success_balance_remaining() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let mut account_vault = tx_context.account().vault().clone(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); @@ -341,7 +297,7 @@ fn test_remove_fungible_asset_success_balance_remaining() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -368,11 +324,7 @@ fn test_remove_fungible_asset_success_balance_remaining() { #[test] fn test_remove_inexisting_non_fungible_asset_fails() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let faucet_id: AccountId = ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); @@ -389,7 +341,7 @@ fn test_remove_inexisting_non_fungible_asset_fails() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin @@ -413,11 +365,7 @@ fn test_remove_inexisting_non_fungible_asset_fails() { #[test] fn test_remove_non_fungible_asset_success() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let faucet_id: AccountId = ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let mut account_vault = tx_context.account().vault().clone(); let non_fungible_asset_details = @@ -427,7 +375,7 @@ fn test_remove_non_fungible_asset_success() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::account begin diff --git a/miden-tx/src/tests/kernel_tests/test_epilogue.rs b/miden-tx/src/tests/kernel_tests/test_epilogue.rs index 447cb94cd..abb47e553 100644 --- a/miden-tx/src/tests/kernel_tests/test_epilogue.rs +++ b/miden-tx/src/tests/kernel_tests/test_epilogue.rs @@ -1,10 +1,11 @@ use alloc::vec::Vec; -use miden_lib::transaction::memory::{ - CREATED_NOTE_ASSET_HASH_OFFSET, CREATED_NOTE_SECTION_OFFSET, NOTE_MEM_SIZE, +use miden_lib::transaction::{ + memory::{NOTE_MEM_SIZE, OUTPUT_NOTE_ASSET_HASH_OFFSET, OUTPUT_NOTE_SECTION_OFFSET}, + TransactionKernel, }; use miden_objects::{ - accounts::{account_id::testing::ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, Account}, + accounts::Account, transaction::{OutputNote, OutputNotes}, }; use vm_processor::ONE; @@ -14,21 +15,18 @@ use crate::{testing::TransactionContextBuilder, tests::kernel_tests::read_root_m #[test] fn test_epilogue() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let output_notes_data_procedure = output_notes_data_procedure(tx_context.expected_output_notes()); let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::account - use.miden::kernels::tx::epilogue + use.kernel::prologue + use.kernel::account + use.kernel::epilogue {output_notes_data_procedure} @@ -50,7 +48,7 @@ fn test_epilogue() { let final_account = Account::mock( tx_context.account().id().into(), tx_context.account().nonce() + ONE, - tx_context.account().code().clone(), + TransactionKernel::assembler(), ); let output_notes = OutputNotes::new( @@ -82,13 +80,10 @@ fn test_epilogue() { } #[test] -fn test_compute_created_note_id() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); +fn test_compute_output_note_id() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let output_notes_data_procedure = output_notes_data_procedure(tx_context.expected_output_notes()); @@ -96,8 +91,8 @@ fn test_compute_created_note_id() { for (note, i) in tx_context.expected_output_notes().iter().zip(0u32..) { let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::epilogue + use.kernel::prologue + use.kernel::epilogue {output_notes_data_procedure} @@ -115,14 +110,14 @@ fn test_compute_created_note_id() { note.assets().commitment().as_elements(), read_root_mem_value( &process, - CREATED_NOTE_SECTION_OFFSET + i * NOTE_MEM_SIZE + CREATED_NOTE_ASSET_HASH_OFFSET + OUTPUT_NOTE_SECTION_OFFSET + i * NOTE_MEM_SIZE + OUTPUT_NOTE_ASSET_HASH_OFFSET ), "ASSET_HASH didn't match expected value", ); assert_eq!( note.id().as_elements(), - &read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + i * NOTE_MEM_SIZE), + &read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + i * NOTE_MEM_SIZE), "NOTE_ID didn't match expected value", ); } @@ -130,21 +125,18 @@ fn test_compute_created_note_id() { #[test] fn test_epilogue_asset_preservation_violation_too_few_input() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_too_few_input() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_too_few_input() + .build(); let output_notes_data_procedure = output_notes_data_procedure(tx_context.expected_output_notes()); let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::account - use.miden::kernels::tx::epilogue + use.kernel::prologue + use.miden::account + use.kernel::epilogue {output_notes_data_procedure} @@ -164,21 +156,18 @@ fn test_epilogue_asset_preservation_violation_too_few_input() { #[test] fn test_epilogue_asset_preservation_violation_too_many_fungible_input() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_too_many_fungible_input() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_too_many_fungible_input() + .build(); let output_notes_data_procedure = output_notes_data_procedure(tx_context.expected_output_notes()); let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::account - use.miden::kernels::tx::epilogue + use.kernel::prologue + use.miden::account + use.kernel::epilogue {output_notes_data_procedure} @@ -198,21 +187,18 @@ fn test_epilogue_asset_preservation_violation_too_many_fungible_input() { #[test] fn test_epilogue_increment_nonce_success() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let output_notes_data_procedure = output_notes_data_procedure(tx_context.expected_output_notes()); let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::account - use.miden::kernels::tx::epilogue + use.kernel::prologue + use.miden::account + use.kernel::epilogue {output_notes_data_procedure} @@ -240,21 +226,18 @@ fn test_epilogue_increment_nonce_success() { #[test] fn test_epilogue_increment_nonce_violation() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let output_notes_data_procedure = output_notes_data_procedure(tx_context.expected_output_notes()); let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::account - use.miden::kernels::tx::epilogue + use.kernel::prologue + use.miden::account + use.kernel::epilogue {output_notes_data_procedure} diff --git a/miden-tx/src/tests/kernel_tests/test_faucet.rs b/miden-tx/src/tests/kernel_tests/test_faucet.rs index 97d0814a0..43eb4f423 100644 --- a/miden-tx/src/tests/kernel_tests/test_faucet.rs +++ b/miden-tx/src/tests/kernel_tests/test_faucet.rs @@ -2,7 +2,6 @@ use miden_objects::{ accounts::account_id::testing::{ ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, }, assets::{Asset, FungibleAsset}, testing::{ @@ -33,10 +32,10 @@ fn test_mint_fungible_asset_succeeds() { let code = format!( " - use.miden::kernels::tx::account - use.miden::kernels::tx::asset_vault - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::asset_vault + use.kernel::memory + use.kernel::prologue use.miden::faucet begin @@ -70,15 +69,11 @@ fn test_mint_fungible_asset_succeeds() { #[test] fn test_mint_fungible_asset_fails_not_faucet_account() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -96,15 +91,11 @@ fn test_mint_fungible_asset_fails_not_faucet_account() { #[test] fn test_mint_fungible_asset_inconsistent_faucet_id() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -122,15 +113,11 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() { #[test] fn test_mint_fungible_asset_fails_saturate_max_amount() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -168,10 +155,10 @@ fn test_mint_non_fungible_asset_succeeds() { " use.std::collections::smt - use.miden::kernels::tx::account - use.miden::kernels::tx::asset_vault - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::asset_vault + use.kernel::memory + use.kernel::prologue use.miden::faucet begin @@ -207,18 +194,14 @@ fn test_mint_non_fungible_asset_succeeds() { #[test] fn test_mint_non_fungible_asset_fails_not_faucet_account() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let non_fungible_asset = Asset::mock_non_fungible(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, &[1, 2, 3, 4]); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -237,18 +220,14 @@ fn test_mint_non_fungible_asset_fails_not_faucet_account() { #[test] fn test_mint_non_fungible_asset_fails_inconsistent_faucet_id() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let non_fungible_asset = Asset::mock_non_fungible(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1, &[1, 2, 3, 4]); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -281,7 +260,7 @@ fn test_mint_non_fungible_asset_fails_asset_already_exists() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -313,10 +292,10 @@ fn test_burn_fungible_asset_succeeds() { let code = format!( " - use.miden::kernels::tx::account - use.miden::kernels::tx::asset_vault - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::asset_vault + use.kernel::memory + use.kernel::prologue use.miden::faucet begin @@ -351,15 +330,11 @@ fn test_burn_fungible_asset_succeeds() { #[test] fn test_burn_fungible_asset_fails_not_faucet_account() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -386,7 +361,7 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -412,7 +387,7 @@ fn test_burn_fungible_asset_insufficient_input_amount() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin @@ -450,10 +425,10 @@ fn test_burn_non_fungible_asset_succeeds() { " use.std::collections::smt - use.miden::kernels::tx::account - use.miden::kernels::tx::asset_vault - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::asset_vault + use.kernel::memory + use.kernel::prologue use.miden::faucet begin @@ -503,10 +478,10 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() { " use.std::collections::smt - use.miden::kernels::tx::account - use.miden::kernels::tx::asset_vault - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::asset_vault + use.kernel::memory + use.kernel::prologue use.miden::faucet begin @@ -526,11 +501,7 @@ fn test_burn_non_fungible_asset_fails_does_not_exist() { #[test] fn test_burn_non_fungible_asset_fails_not_faucet_account() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let non_fungible_asset_burnt = Asset::mock_non_fungible(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, &[1, 2, 3]); @@ -539,10 +510,10 @@ fn test_burn_non_fungible_asset_fails_not_faucet_account() { " use.std::collections::smt - use.miden::kernels::tx::account - use.miden::kernels::tx::asset_vault - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::asset_vault + use.kernel::memory + use.kernel::prologue use.miden::faucet begin @@ -576,10 +547,10 @@ fn test_burn_non_fungible_asset_fails_inconsistent_faucet_id() { " use.std::collections::smt - use.miden::kernels::tx::account - use.miden::kernels::tx::asset_vault - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::account + use.kernel::asset_vault + use.kernel::memory + use.kernel::prologue use.miden::faucet begin @@ -611,7 +582,7 @@ fn test_get_total_issuance_succeeds() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::faucet begin diff --git a/miden-tx/src/tests/kernel_tests/test_note.rs b/miden-tx/src/tests/kernel_tests/test_note.rs index 6b11cde0c..e9629a73c 100644 --- a/miden-tx/src/tests/kernel_tests/test_note.rs +++ b/miden-tx/src/tests/kernel_tests/test_note.rs @@ -1,8 +1,7 @@ use alloc::{collections::BTreeMap, string::String}; -use miden_lib::transaction::memory::CURRENT_CONSUMED_NOTE_PTR; +use miden_lib::transaction::memory::CURRENT_INPUT_NOTE_PTR; use miden_objects::{ - accounts::account_id::testing::ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, notes::Note, testing::prepare_word, transaction::TransactionArgs, Hasher, WORD_SIZE, }; use vm_processor::{ProcessState, EMPTY_WORD, ONE}; @@ -10,29 +9,25 @@ use vm_processor::{ProcessState, EMPTY_WORD, ONE}; use super::{Felt, Process, ZERO}; use crate::{ testing::{ - utils::consumed_note_data_ptr, MockHost, TransactionContext, TransactionContextBuilder, + utils::input_note_data_ptr, MockHost, TransactionContext, TransactionContextBuilder, }, tests::kernel_tests::read_root_mem_value, }; #[test] fn test_get_sender_no_sender() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); // calling get_sender should return sender let code = " - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::memory + use.kernel::prologue use.miden::note begin exec.prologue::prepare_transaction - # force the current consumed note pointer to 0 - push.0 exec.memory::set_current_consumed_note_ptr + # force the current input note pointer to 0 + push.0 exec.memory::set_current_input_note_ptr # get the sender exec.note::get_sender @@ -46,17 +41,14 @@ fn test_get_sender_no_sender() { #[test] fn test_get_sender() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); // calling get_sender should return sender let code = " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::note->note_internal + use.kernel::prologue + use.kernel::note->note_internal use.miden::note begin @@ -75,20 +67,17 @@ fn test_get_sender() { #[test] fn test_get_vault_data() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let notes = tx_context.input_notes(); - // calling get_vault_info should return vault info + // calling get_assets_info should return assets info let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::note + use.kernel::prologue + use.kernel::note begin exec.prologue::prepare_transaction @@ -96,23 +85,23 @@ fn test_get_vault_data() { # prepare note 0 exec.note::prepare_note - # get the vault data - exec.note::get_vault_info + # get the assets info + exec.note::get_assets_info - # assert the vault data is correct + # assert the assets data is correct push.{note_0_asset_hash} assert_eqw push.{note_0_num_assets} assert_eq - # increment current consumed note pointer - exec.note::increment_current_consumed_note_ptr + # increment current input note pointer + exec.note::increment_current_input_note_ptr # prepare note 1 exec.note::prepare_note - # get the vault data - exec.note::get_vault_info + # get the assets data + exec.note::get_assets_info - # assert the vault data is correct + # assert the assets data is correct push.{note_1_asset_hash} assert_eqw push.{note_1_num_assets} assert_eq end @@ -127,12 +116,9 @@ fn test_get_vault_data() { } #[test] fn test_get_assets() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let notes = tx_context.input_notes(); @@ -156,8 +142,8 @@ fn test_get_assets() { // calling get_assets should return assets at the specified address let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::note->note_internal + use.kernel::prologue + use.kernel::note->note_internal use.miden::note proc.process_note_0 @@ -216,8 +202,8 @@ fn test_get_assets() { # process note 0 call.process_note_0 - # increment current consumed note pointer - exec.note_internal::increment_current_consumed_note_ptr + # increment current input note pointer + exec.note_internal::increment_current_input_note_ptr # prepare note 1 exec.note_internal::prepare_note @@ -237,12 +223,9 @@ fn test_get_assets() { #[test] fn test_get_inputs() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let notes = tx_context.mock_chain().available_notes(); @@ -267,8 +250,8 @@ fn test_get_inputs() { let code = format!( " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::note->note_internal + use.kernel::prologue + use.kernel::note->note_internal use.miden::note begin @@ -311,16 +294,13 @@ fn test_get_inputs() { #[test] fn test_note_setup() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let code = " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::note + use.kernel::prologue + use.kernel::note begin exec.prologue::prepare_transaction @@ -341,23 +321,20 @@ fn test_note_script_and_note_args() { [Felt::new(92), Felt::new(92), Felt::new(92), Felt::new(92)], ]; - let mut tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let mut tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let code = " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::memory - use.miden::kernels::tx::note + use.kernel::prologue + use.kernel::memory + use.kernel::note begin exec.prologue::prepare_transaction - exec.memory::get_total_num_consumed_notes push.2 assert_eq + exec.memory::get_num_input_notes push.2 assert_eq exec.note::prepare_note dropw - exec.note::increment_current_consumed_note_ptr drop + exec.note::increment_current_input_note_ptr drop exec.note::prepare_note dropw end "; @@ -367,8 +344,11 @@ fn test_note_script_and_note_args() { (tx_context.input_notes().get_note(1).note().id(), note_args[0]), ]); - let tx_args = - TransactionArgs::new(None, Some(note_args_map), tx_context.tx_args().advice_map().clone()); + let tx_args = TransactionArgs::new( + None, + Some(note_args_map), + tx_context.tx_args().advice_inputs().clone().map, + ); tx_context.set_tx_args(tx_args); let process = tx_context.execute_code(code).unwrap(); @@ -393,25 +373,22 @@ fn note_setup_stack_assertions(process: &Process, inputs: &Transaction fn note_setup_memory_assertions(process: &Process) { // assert that the correct pointer is stored in bookkeeping memory assert_eq!( - read_root_mem_value(process, CURRENT_CONSUMED_NOTE_PTR)[0], - Felt::from(consumed_note_data_ptr(0)) + read_root_mem_value(process, CURRENT_INPUT_NOTE_PTR)[0], + Felt::from(input_note_data_ptr(0)) ); } #[test] fn test_get_note_serial_number() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); // calling get_serial_number should return the serial number of the note let code = " - use.miden::kernels::tx::prologue - use.miden::kernels::tx::note->note_internal - use.miden::note + use.kernel::prologue + use.kernel::note->note_internal + use.kernel::note begin exec.prologue::prepare_transaction @@ -429,12 +406,9 @@ fn test_get_note_serial_number() { #[test] fn test_get_inputs_hash() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let code = " use.miden::note @@ -515,7 +489,7 @@ fn test_get_inputs_hash() { .to_vec(); expected_15.reverse(); - let mut expected_stack = vec![Felt::new(0), Felt::new(0), Felt::new(0), Felt::new(0)]; + let mut expected_stack = vec![ZERO, ZERO, ZERO, ZERO]; expected_stack.extend_from_slice(&expected_15); expected_stack.extend_from_slice(&expected_8); expected_stack.extend_from_slice(&expected_5); diff --git a/miden-tx/src/tests/kernel_tests/test_prologue.rs b/miden-tx/src/tests/kernel_tests/test_prologue.rs index 436eda374..cbbd229c6 100644 --- a/miden-tx/src/tests/kernel_tests/test_prologue.rs +++ b/miden-tx/src/tests/kernel_tests/test_prologue.rs @@ -2,71 +2,66 @@ use alloc::{collections::BTreeMap, vec::Vec}; use miden_lib::transaction::{ memory::{ - MemoryOffset, ACCT_CODE_ROOT_PTR, ACCT_DB_ROOT_PTR, ACCT_ID_AND_NONCE_PTR, ACCT_ID_PTR, - ACCT_STORAGE_ROOT_PTR, ACCT_STORAGE_SLOT_TYPE_DATA_OFFSET, ACCT_VAULT_ROOT_PTR, - BLK_HASH_PTR, BLOCK_METADATA_PTR, BLOCK_NUMBER_IDX, CHAIN_MMR_NUM_LEAVES_PTR, - CHAIN_MMR_PEAKS_PTR, CHAIN_ROOT_PTR, CONSUMED_NOTE_ARGS_OFFSET, - CONSUMED_NOTE_ASSETS_HASH_OFFSET, CONSUMED_NOTE_ASSETS_OFFSET, CONSUMED_NOTE_ID_OFFSET, - CONSUMED_NOTE_INPUTS_HASH_OFFSET, CONSUMED_NOTE_METADATA_OFFSET, - CONSUMED_NOTE_NUM_ASSETS_OFFSET, CONSUMED_NOTE_SCRIPT_ROOT_OFFSET, - CONSUMED_NOTE_SECTION_OFFSET, CONSUMED_NOTE_SERIAL_NUM_OFFSET, INIT_ACCT_HASH_PTR, - INIT_NONCE_PTR, INPUT_NOTES_COMMITMENT_PTR, NOTE_ROOT_PTR, NULLIFIER_DB_ROOT_PTR, - PREV_BLOCK_HASH_PTR, PROOF_HASH_PTR, PROTOCOL_VERSION_IDX, TIMESTAMP_IDX, TX_HASH_PTR, - TX_SCRIPT_ROOT_PTR, + MemoryOffset, ACCT_CODE_COMMITMENT_PTR, ACCT_DB_ROOT_PTR, ACCT_ID_AND_NONCE_PTR, + ACCT_ID_PTR, ACCT_PROCEDURES_SECTION_OFFSET, ACCT_STORAGE_ROOT_PTR, + ACCT_STORAGE_SLOT_TYPE_DATA_OFFSET, ACCT_VAULT_ROOT_PTR, BLK_HASH_PTR, BLOCK_METADATA_PTR, + BLOCK_NUMBER_IDX, CHAIN_MMR_NUM_LEAVES_PTR, CHAIN_MMR_PEAKS_PTR, CHAIN_ROOT_PTR, + INIT_ACCT_HASH_PTR, INIT_NONCE_PTR, INPUT_NOTES_COMMITMENT_PTR, INPUT_NOTE_ARGS_OFFSET, + INPUT_NOTE_ASSETS_HASH_OFFSET, INPUT_NOTE_ASSETS_OFFSET, INPUT_NOTE_ID_OFFSET, + INPUT_NOTE_INPUTS_HASH_OFFSET, INPUT_NOTE_METADATA_OFFSET, INPUT_NOTE_NUM_ASSETS_OFFSET, + INPUT_NOTE_SCRIPT_ROOT_OFFSET, INPUT_NOTE_SECTION_OFFSET, INPUT_NOTE_SERIAL_NUM_OFFSET, + NOTE_ROOT_PTR, NULLIFIER_DB_ROOT_PTR, NUM_ACCT_PROCEDURES_PTR, PREV_BLOCK_HASH_PTR, + PROOF_HASH_PTR, PROTOCOL_VERSION_IDX, TIMESTAMP_IDX, TX_HASH_PTR, TX_SCRIPT_ROOT_PTR, }, TransactionKernel, }; use miden_objects::{ - accounts::account_id::testing::ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - assembly::ProgramAst, testing::{ + account::AccountBuilder, constants::FUNGIBLE_FAUCET_INITIAL_BALANCE, storage::{generate_account_seed, AccountSeedType}, }, transaction::{TransactionArgs, TransactionScript}, Digest, FieldElement, }; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; use vm_processor::{AdviceInputs, ONE}; use super::{Felt, Process, Word, ZERO}; use crate::{ testing::{ - utils::consumed_note_data_ptr, MockHost, TransactionContext, TransactionContextBuilder, + utils::input_note_data_ptr, MockHost, TransactionContext, TransactionContextBuilder, }, tests::kernel_tests::read_root_mem_value, }; #[test] fn test_transaction_prologue() { - let mut tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let mut tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let code = " - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction end "; - let mock_tx_script_code = ProgramAst::parse( - " + let mock_tx_script_code = " begin push.1.2.3.4 dropw end - ", - ) - .unwrap(); - let (tx_script, _) = TransactionScript::new( - mock_tx_script_code, - vec![], - &TransactionKernel::assembler().with_debug_mode(true), - ) - .unwrap(); + "; + + let mock_tx_script_program = TransactionKernel::assembler() + .with_debug_mode(true) + .assemble_program(mock_tx_script_code) + .unwrap(); + + let tx_script = TransactionScript::new(mock_tx_script_program, vec![]); let note_args = [ [Felt::new(91), Felt::new(91), Felt::new(91), Felt::new(91)], @@ -81,7 +76,7 @@ fn test_transaction_prologue() { let tx_args = TransactionArgs::new( Some(tx_script), Some(note_args_map), - tx_context.tx_args().advice_map().clone(), + tx_context.tx_args().advice_inputs().clone().map, ); tx_context.set_tx_args(tx_args); @@ -91,7 +86,7 @@ fn test_transaction_prologue() { block_data_memory_assertions(&process, &tx_context); chain_mmr_memory_assertions(&process, &tx_context); account_data_memory_assertions(&process, &tx_context); - consumed_notes_memory_assertions(&process, &tx_context, ¬e_args); + input_notes_memory_assertions(&process, &tx_context, ¬e_args); } fn global_input_memory_assertions(process: &Process, inputs: &TransactionContext) { @@ -127,7 +122,7 @@ fn global_input_memory_assertions(process: &Process, inputs: &Transact assert_eq!( read_root_mem_value(process, TX_SCRIPT_ROOT_PTR), - **inputs.tx_args().tx_script().as_ref().unwrap().hash(), + *inputs.tx_args().tx_script().as_ref().unwrap().hash(), "The transaction script root should be stored at the TX_SCRIPT_ROOT_PTR" ); } @@ -240,8 +235,8 @@ fn account_data_memory_assertions(process: &Process, inputs: &Transact ); assert_eq!( - read_root_mem_value(process, ACCT_CODE_ROOT_PTR), - inputs.account().code().root().as_elements(), + read_root_mem_value(process, ACCT_CODE_COMMITMENT_PTR), + inputs.account().code().commitment().as_elements(), "account code commitment should be stored at (ACCOUNT_DATA_OFFSET + 4)" ); @@ -258,72 +253,91 @@ fn account_data_memory_assertions(process: &Process, inputs: &Transact "The account types data should be stored in (ACCT_STORAGE_SLOT_TYPE_DATA_OFFSET..ACCT_STORAGE_SLOT_TYPE_DATA_OFFSET + 64)" ); } + + assert_eq!( + read_root_mem_value(process, NUM_ACCT_PROCEDURES_PTR), + [ + u16::try_from(inputs.account().code().procedures().len()).unwrap().into(), + ZERO, + ZERO, + ZERO + ], + "The number of procedures should be stored at NUM_ACCT_PROCEDURES_PTR" + ); + + for (i, elements) in inputs.account().code().as_elements().chunks(4).enumerate() { + assert_eq!( + read_root_mem_value(process, ACCT_PROCEDURES_SECTION_OFFSET + i as u32), + Word::try_from(elements).unwrap(), + "The account procedures and storage offsets should be stored starting at ACCT_PROCEDURES_SECTION_OFFSET" + ); + } } -fn consumed_notes_memory_assertions( +fn input_notes_memory_assertions( process: &Process, inputs: &TransactionContext, note_args: &[[Felt; 4]], ) { assert_eq!( - read_root_mem_value(process, CONSUMED_NOTE_SECTION_OFFSET), + read_root_mem_value(process, INPUT_NOTE_SECTION_OFFSET), [Felt::new(inputs.input_notes().num_notes() as u64), ZERO, ZERO, ZERO], - "number of consumed notes should be stored at the CONSUMED_NOTES_OFFSET" + "number of input notes should be stored at the INPUT_NOTES_OFFSET" ); for (input_note, note_idx) in inputs.input_notes().iter().zip(0_u32..) { let note = input_note.note(); assert_eq!( - read_root_mem_value(process, CONSUMED_NOTE_SECTION_OFFSET + 1 + note_idx), + read_root_mem_value(process, INPUT_NOTE_SECTION_OFFSET + 1 + note_idx), note.nullifier().as_elements(), "note nullifier should be computer and stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_ID_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_ID_OFFSET), note.id().as_elements(), "ID hash should be computed and stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_SERIAL_NUM_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_SERIAL_NUM_OFFSET), note.serial_num(), "note serial num should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_SCRIPT_ROOT_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_SCRIPT_ROOT_OFFSET), note.script().hash().as_elements(), "note script hash should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_INPUTS_HASH_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_INPUTS_HASH_OFFSET), note.inputs().commitment().as_elements(), "note input hash should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_ASSETS_HASH_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_ASSETS_HASH_OFFSET), note.assets().commitment().as_elements(), "note asset hash should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_METADATA_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_METADATA_OFFSET), Word::from(note.metadata()), "note metadata should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_ARGS_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_ARGS_OFFSET), Word::from(note_args[note_idx as usize]), "note args should be stored at the correct offset" ); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_NUM_ASSETS_OFFSET), + read_note_element(process, note_idx, INPUT_NOTE_NUM_ASSETS_OFFSET), [Felt::from(note.assets().num_assets() as u32), ZERO, ZERO, ZERO], "number of assets should be stored at the correct offset" ); @@ -331,8 +345,9 @@ fn consumed_notes_memory_assertions( for (asset, asset_idx) in note.assets().iter().cloned().zip(0_u32..) { let word: Word = asset.into(); assert_eq!( - read_note_element(process, note_idx, CONSUMED_NOTE_ASSETS_OFFSET + asset_idx), - word, "assets should be stored at (CONSUMED_NOTES_OFFSET + (note_index + 1) * 1024 + 7..)" + read_note_element(process, note_idx, INPUT_NOTE_ASSETS_OFFSET + asset_idx), + word, + "assets should be stored at (INPUT_NOTES_OFFSET + (note_index + 1) * 1024 + 7..)" ); } } @@ -341,19 +356,16 @@ fn consumed_notes_memory_assertions( #[cfg_attr(not(feature = "testing"), ignore)] #[test] pub fn test_prologue_create_account() { - let (acct_id, account_seed) = generate_account_seed( - AccountSeedType::RegularAccountUpdatableCodeOnChain, - &TransactionKernel::assembler().with_debug_mode(true), - ); - let tx_context = TransactionContextBuilder::with_standard_account(acct_id.into(), ZERO) - .account_seed(account_seed) - .build(); + let (account, seed) = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .build(TransactionKernel::assembler_testing()) + .unwrap(); + let tx_context = TransactionContextBuilder::new(account).account_seed(Some(seed)).build(); let code = " - use.miden::kernels::tx::prologue + use.kernel::prologue begin - exec.prologue::prepare_transaction + call.prologue::prepare_transaction end "; @@ -365,16 +377,16 @@ pub fn test_prologue_create_account() { pub fn test_prologue_create_account_valid_fungible_faucet_reserved_slot() { let (acct_id, account_seed) = generate_account_seed( AccountSeedType::FungibleFaucetValidInitialBalance, - &TransactionKernel::assembler().with_debug_mode(true), + TransactionKernel::assembler().with_debug_mode(true), ); let tx_context = TransactionContextBuilder::with_fungible_faucet(acct_id.into(), Felt::ZERO, ZERO) - .account_seed(account_seed) + .account_seed(Some(account_seed)) .build(); let code = " - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -390,7 +402,7 @@ pub fn test_prologue_create_account_valid_fungible_faucet_reserved_slot() { pub fn test_prologue_create_account_invalid_fungible_faucet_reserved_slot() { let (acct_id, account_seed) = generate_account_seed( AccountSeedType::FungibleFaucetInvalidInitialBalance, - &TransactionKernel::assembler().with_debug_mode(true), + TransactionKernel::assembler().with_debug_mode(true), ); let tx_context = TransactionContextBuilder::with_fungible_faucet( @@ -398,11 +410,11 @@ pub fn test_prologue_create_account_invalid_fungible_faucet_reserved_slot() { Felt::ZERO, Felt::new(FUNGIBLE_FAUCET_INITIAL_BALANCE), ) - .account_seed(account_seed) + .account_seed(Some(account_seed)) .build(); let code = " - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -418,16 +430,16 @@ pub fn test_prologue_create_account_invalid_fungible_faucet_reserved_slot() { pub fn test_prologue_create_account_valid_non_fungible_faucet_reserved_slot() { let (acct_id, account_seed) = generate_account_seed( AccountSeedType::NonFungibleFaucetValidReservedSlot, - &TransactionKernel::assembler().with_debug_mode(true), + TransactionKernel::assembler().with_debug_mode(true), ); let tx_context = TransactionContextBuilder::with_non_fungible_faucet(acct_id.into(), Felt::ZERO, true) - .account_seed(account_seed) + .account_seed(Some(account_seed)) .build(); let code = " - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -444,16 +456,16 @@ pub fn test_prologue_create_account_valid_non_fungible_faucet_reserved_slot() { pub fn test_prologue_create_account_invalid_non_fungible_faucet_reserved_slot() { let (acct_id, account_seed) = generate_account_seed( AccountSeedType::NonFungibleFaucetInvalidReservedSlot, - &TransactionKernel::assembler().with_debug_mode(true), + TransactionKernel::assembler().with_debug_mode(true), ); let tx_context = TransactionContextBuilder::with_non_fungible_faucet(acct_id.into(), Felt::ZERO, false) - .account_seed(account_seed) + .account_seed(Some(account_seed)) .build(); let code = " - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -468,13 +480,13 @@ pub fn test_prologue_create_account_invalid_non_fungible_faucet_reserved_slot() #[cfg_attr(not(feature = "testing"), ignore)] #[test] pub fn test_prologue_create_account_invalid_seed() { - let (acct_id, account_seed) = generate_account_seed( - AccountSeedType::RegularAccountUpdatableCodeOnChain, - &TransactionKernel::assembler().with_debug_mode(true), - ); + let (acct, account_seed) = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .account_type(miden_objects::accounts::AccountType::RegularAccountUpdatableCode) + .build(TransactionKernel::assembler_testing()) + .unwrap(); let code = " - use.miden::kernels::tx::prologue + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -482,29 +494,24 @@ pub fn test_prologue_create_account_invalid_seed() { "; // override the seed with an invalid seed to ensure the kernel fails - let account_seed_key = [acct_id.into(), ZERO, ZERO, ZERO]; + let account_seed_key = [acct.id().into(), ZERO, ZERO, ZERO]; let adv_inputs = AdviceInputs::default().with_map([(Digest::from(account_seed_key), vec![ZERO; 4])]); - let tx_context = TransactionContextBuilder::with_standard_account(acct_id.into(), ZERO) - .account_seed(account_seed) + let tx_context = TransactionContextBuilder::new(acct) + .account_seed(Some(account_seed)) .advice_inputs(adv_inputs) .build(); - let process = tx_context.execute_code(code); assert!(process.is_err()); } #[test] fn test_get_blk_version() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = " - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::memory + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -519,14 +526,10 @@ fn test_get_blk_version() { #[test] fn test_get_blk_timestamp() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let code = " - use.miden::kernels::tx::memory - use.miden::kernels::tx::prologue + use.kernel::memory + use.kernel::prologue begin exec.prologue::prepare_transaction @@ -543,5 +546,5 @@ fn test_get_blk_timestamp() { // ================================================================================================ fn read_note_element(process: &Process, note_idx: u32, offset: MemoryOffset) -> Word { - read_root_mem_value(process, consumed_note_data_ptr(note_idx) + offset) + read_root_mem_value(process, input_note_data_ptr(note_idx) + offset) } diff --git a/miden-tx/src/tests/kernel_tests/test_tx.rs b/miden-tx/src/tests/kernel_tests/test_tx.rs index 7d8ad4730..f5256ba15 100644 --- a/miden-tx/src/tests/kernel_tests/test_tx.rs +++ b/miden-tx/src/tests/kernel_tests/test_tx.rs @@ -1,17 +1,18 @@ use alloc::vec::Vec; use miden_lib::transaction::memory::{ - CREATED_NOTE_ASSETS_OFFSET, CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_RECIPIENT_OFFSET, - CREATED_NOTE_SECTION_OFFSET, NOTE_MEM_SIZE, NUM_CREATED_NOTES_PTR, + NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, OUTPUT_NOTE_METADATA_OFFSET, + OUTPUT_NOTE_RECIPIENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, }; use miden_objects::{ accounts::account_id::testing::{ ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, }, assets::Asset, - notes::{Note, NoteAssets, NoteInputs, NoteMetadata, NoteRecipient, NoteType}, + notes::{ + Note, NoteAssets, NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, NoteType, + }, testing::{constants::NON_FUNGIBLE_ASSET_DATA_2, prepare_word}, transaction::{OutputNote, OutputNotes}, }; @@ -24,11 +25,7 @@ use crate::{ #[test] fn test_create_note() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let account_id = tx_context.account().id(); let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; @@ -37,13 +34,14 @@ fn test_create_note() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::tx begin exec.prologue::prepare_transaction push.{recipient} + push.{note_execution_hint} push.{PUBLIC_NOTE} push.{aux} push.{tag} @@ -53,56 +51,69 @@ fn test_create_note() { ", recipient = prepare_word(&recipient), PUBLIC_NOTE = NoteType::Public as u8, + note_execution_hint = Felt::from(NoteExecutionHint::after_block(23)), tag = tag, ); let process = tx_context.execute_code(&code).unwrap(); assert_eq!( - read_root_mem_value(&process, NUM_CREATED_NOTES_PTR), + read_root_mem_value(&process, NUM_OUTPUT_NOTES_PTR), [ONE, ZERO, ZERO, ZERO], - "number of created notes must increment by 1", + "number of output notes must increment by 1", ); assert_eq!( - read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_RECIPIENT_OFFSET), + read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), recipient, "recipient must be stored at the correct memory location", ); + let expected_note_metadata: Word = NoteMetadata::new( + account_id, + NoteType::Public, + tag.try_into().unwrap(), + NoteExecutionHint::after_block(23), + Felt::new(27), + ) + .unwrap() + .into(); + assert_eq!( - read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_METADATA_OFFSET), - [tag, Felt::from(account_id), NoteType::Public.into(), Felt::new(27)], + read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET), + [ + expected_note_metadata[0], + expected_note_metadata[1], + expected_note_metadata[2], + expected_note_metadata[3] + ], "metadata must be stored at the correct memory location", ); assert_eq!( process.stack.get(0), ZERO, - "top item on the stack is the index of the created note" + "top item on the stack is the index of the output note" ); } #[test] fn test_create_note_with_invalid_tag() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; let tag = Felt::new((NoteType::Public as u64) << 62); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::tx begin exec.prologue::prepare_transaction push.{recipient} + push.{note_execution_hint} push.{PUBLIC_NOTE} push.{tag} @@ -110,6 +121,7 @@ fn test_create_note_with_invalid_tag() { end ", recipient = prepare_word(&recipient), + note_execution_hint = Felt::from(NoteExecutionHint::always()), PUBLIC_NOTE = NoteType::Public as u8, tag = tag, ); @@ -126,13 +138,13 @@ fn test_create_note_too_many_notes() { let code = format!( " - use.miden::kernels::tx::constants - use.miden::kernels::tx::memory + use.kernel::constants + use.kernel::memory use.miden::tx begin - exec.constants::get_max_num_created_notes - exec.memory::set_num_created_notes + exec.constants::get_max_num_output_notes + exec.memory::set_num_output_notes push.{recipient} push.{PUBLIC_NOTE} @@ -154,12 +166,9 @@ fn test_create_note_too_many_notes() { #[test] fn test_get_output_notes_hash() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); // extract input note data let input_note_1 = tx_context.tx_inputs().input_notes().get_note(0).note(); @@ -175,6 +184,7 @@ fn test_get_output_notes_hash() { tx_context.tx_inputs().account().id(), NoteType::Public, output_tag_1, + NoteExecutionHint::Always, ZERO, ) .unwrap(); @@ -190,6 +200,7 @@ fn test_get_output_notes_hash() { tx_context.tx_inputs().account().id(), NoteType::Public, output_tag_2, + NoteExecutionHint::after_block(123), ZERO, ) .unwrap(); @@ -207,7 +218,7 @@ fn test_get_output_notes_hash() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::tx begin @@ -217,39 +228,43 @@ fn test_get_output_notes_hash() { # create output note 1 push.{recipient_1} + push.{NOTE_EXECUTION_HINT_1} push.{PUBLIC_NOTE} push.{aux_1} push.{tag_1} exec.tx::create_note # => [note_idx] - push.{asset_1} movup.4 + push.{asset_1} exec.tx::add_asset_to_note - - - drop + # => [ASSET, note_idx] + + dropw drop # => [] # create output note 2 push.{recipient_2} + push.{NOTE_EXECUTION_HINT_2} push.{PUBLIC_NOTE} push.{aux_2} push.{tag_2} exec.tx::create_note # => [note_idx] - push.{asset_2} movup.4 + push.{asset_2} exec.tx::add_asset_to_note + # => [ASSET, note_idx] - drop + dropw drop # => [] # compute the output notes hash exec.tx::get_output_notes_hash - # => [COMM] + # => [COM] end ", PUBLIC_NOTE = NoteType::Public as u8, + NOTE_EXECUTION_HINT_1 = Felt::from(output_note_1.metadata().execution_hint()), recipient_1 = prepare_word(&output_note_1.recipient().digest()), tag_1 = output_note_1.metadata().tag(), aux_1 = output_note_1.metadata().aux(), @@ -257,6 +272,7 @@ fn test_get_output_notes_hash() { **output_note_1.assets().iter().take(1).collect::>().first().unwrap() )), recipient_2 = prepare_word(&output_note_2.recipient().digest()), + NOTE_EXECUTION_HINT_2 = Felt::from(output_note_2.metadata().execution_hint()), tag_2 = output_note_2.metadata().tag(), aux_2 = output_note_2.metadata().aux(), asset_2 = prepare_word(&Word::from( @@ -267,14 +283,14 @@ fn test_get_output_notes_hash() { let process = tx_context.execute_code(&code).unwrap(); assert_eq!( - read_root_mem_value(&process, NUM_CREATED_NOTES_PTR), + read_root_mem_value(&process, NUM_OUTPUT_NOTES_PTR), [Felt::new(2), ZERO, ZERO, ZERO], "The test creates two notes", ); assert_eq!( NoteMetadata::try_from(read_root_mem_value( &process, - CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_METADATA_OFFSET + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET )) .unwrap(), *output_note_1.metadata(), @@ -283,7 +299,7 @@ fn test_get_output_notes_hash() { assert_eq!( NoteMetadata::try_from(read_root_mem_value( &process, - CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_METADATA_OFFSET + NOTE_MEM_SIZE + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET + NOTE_MEM_SIZE )) .unwrap(), *output_note_2.metadata(), @@ -295,11 +311,7 @@ fn test_get_output_notes_hash() { #[test] fn test_create_note_and_add_asset() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; let aux = Felt::new(27); @@ -308,14 +320,15 @@ fn test_create_note_and_add_asset() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::tx - use.miden::kernels::tx::memory + use.kernel::memory begin exec.prologue::prepare_transaction push.{recipient} + push.{NOTE_EXECUTION_HINT} push.{PUBLIC_NOTE} push.{aux} push.{tag} @@ -323,13 +336,17 @@ fn test_create_note_and_add_asset() { exec.tx::create_note # => [note_idx] - push.{asset} movup.4 + push.{asset} exec.tx::add_asset_to_note + # => [ASSET, note_idx] + dropw + # => [note_idx] end ", recipient = prepare_word(&recipient), PUBLIC_NOTE = NoteType::Public as u8, + NOTE_EXECUTION_HINT = Felt::from(NoteExecutionHint::always()), tag = tag, asset = prepare_word(&asset), ); @@ -337,7 +354,7 @@ fn test_create_note_and_add_asset() { let process = tx_context.execute_code(&code).unwrap(); assert_eq!( - read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_ASSETS_OFFSET), + read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), asset, "asset must be stored at the correct memory location", ); @@ -345,17 +362,13 @@ fn test_create_note_and_add_asset() { assert_eq!( process.stack.get(0), ZERO, - "top item on the stack is the index to the created note" + "top item on the stack is the index to the output note" ); } #[test] fn test_create_note_and_add_multiple_assets() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; let aux = Felt::new(27); @@ -373,7 +386,7 @@ fn test_create_note_and_add_multiple_assets() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::tx begin @@ -387,20 +400,20 @@ fn test_create_note_and_add_multiple_assets() { exec.tx::create_note # => [note_idx] - push.{asset} movup.4 - exec.tx::add_asset_to_note + push.{asset} + exec.tx::add_asset_to_note dropw # => [note_idx] - push.{asset_2} movup.4 - exec.tx::add_asset_to_note + push.{asset_2} + exec.tx::add_asset_to_note dropw # => [note_idx] - push.{asset_3} movup.4 - exec.tx::add_asset_to_note + push.{asset_3} + exec.tx::add_asset_to_note dropw # => [note_idx] - push.{nft} movup.4 - exec.tx::add_asset_to_note + push.{nft} + exec.tx::add_asset_to_note dropw # => [note_idx] end ", @@ -416,19 +429,19 @@ fn test_create_note_and_add_multiple_assets() { let process = tx_context.execute_code(&code).unwrap(); assert_eq!( - read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_ASSETS_OFFSET), + read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET), asset, "asset must be stored at the correct memory location", ); assert_eq!( - read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_ASSETS_OFFSET + 1), + read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 1), asset_2_and_3, "asset_2 and asset_3 must be stored at the same correct memory location", ); assert_eq!( - read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_ASSETS_OFFSET + 2), + read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ASSETS_OFFSET + 2), Word::from(non_fungible_asset_encoded), "non_fungible_asset must be stored at the correct memory location", ); @@ -436,17 +449,13 @@ fn test_create_note_and_add_multiple_assets() { assert_eq!( process.stack.get(0), ZERO, - "top item on the stack is the index to the created note" + "top item on the stack is the index to the output note" ); } #[test] fn test_create_note_and_add_same_nft_twice() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; let tag = Felt::new(4); @@ -456,7 +465,7 @@ fn test_create_note_and_add_same_nft_twice() { let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::tx begin @@ -491,12 +500,9 @@ fn test_create_note_and_add_same_nft_twice() { #[test] fn test_build_recipient_hash() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let input_note_1 = tx_context.tx_inputs().input_notes().get_note(0).note(); @@ -511,7 +517,7 @@ fn test_build_recipient_hash() { let recipient = NoteRecipient::new(output_serial_no, input_note_1.script().clone(), inputs); let code = format!( " - use.miden::kernels::tx::prologue + use.kernel::prologue use.miden::tx begin exec.prologue::prepare_transaction @@ -523,6 +529,7 @@ fn test_build_recipient_hash() { push.{output_serial_no} exec.tx::build_recipient_hash + push.{execution_hint} push.{PUBLIC_NOTE} push.{aux} push.{tag} @@ -534,21 +541,22 @@ fn test_build_recipient_hash() { output_serial_no = prepare_word(&output_serial_no), PUBLIC_NOTE = NoteType::Public as u8, tag = tag, + execution_hint = Felt::from(NoteExecutionHint::after_block(2)), aux = aux, ); let process = tx_context.execute_code(&code).unwrap(); assert_eq!( - read_root_mem_value(&process, NUM_CREATED_NOTES_PTR), + read_root_mem_value(&process, NUM_OUTPUT_NOTES_PTR), [ONE, ZERO, ZERO, ZERO], - "number of created notes must increment by 1", + "number of output notes must increment by 1", ); let recipient_digest: Vec = recipient.clone().digest().to_vec(); assert_eq!( - read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_RECIPIENT_OFFSET), + read_root_mem_value(&process, OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_RECIPIENT_OFFSET), recipient_digest.as_slice(), "recipient hash not correct", ); diff --git a/miden-tx/src/tests/mod.rs b/miden-tx/src/tests/mod.rs index 1381bf70c..76518a37e 100644 --- a/miden-tx/src/tests/mod.rs +++ b/miden-tx/src/tests/mod.rs @@ -1,34 +1,32 @@ -use alloc::vec::Vec; +use alloc::{collections::BTreeMap, rc::Rc, string::String, vec::Vec}; -use miden_lib::transaction::{ToTransactionKernelInputs, TransactionKernel}; +use miden_lib::transaction::TransactionKernel; use miden_objects::{ accounts::{ account_id::testing::{ ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, }, AccountCode, }, - assembly::{Assembler, ModuleAst, ProgramAst}, assets::{Asset, FungibleAsset}, notes::{ - Note, NoteAssets, NoteExecutionHint, NoteHeader, NoteId, NoteInputs, NoteMetadata, - NoteRecipient, NoteScript, NoteTag, NoteType, + Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteHeader, NoteId, NoteInputs, + NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType, }, testing::{ account_code::{ - ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT, ACCOUNT_CREATE_NOTE_MAST_ROOT, - ACCOUNT_INCR_NONCE_MAST_ROOT, ACCOUNT_REMOVE_ASSET_MAST_ROOT, - ACCOUNT_SET_CODE_MAST_ROOT, ACCOUNT_SET_ITEM_MAST_ROOT, ACCOUNT_SET_MAP_ITEM_MAST_ROOT, + ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT, ACCOUNT_INCR_NONCE_MAST_ROOT, + ACCOUNT_REMOVE_ASSET_MAST_ROOT, ACCOUNT_SET_CODE_MAST_ROOT, ACCOUNT_SET_ITEM_MAST_ROOT, + ACCOUNT_SET_MAP_ITEM_MAST_ROOT, }, constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}, notes::DEFAULT_NOTE_CODE, prepare_word, storage::{STORAGE_INDEX_0, STORAGE_INDEX_2}, }, - transaction::{ProvenTransaction, TransactionArgs, TransactionWitness}, + transaction::{ProvenTransaction, TransactionArgs, TransactionScript}, Felt, Word, MIN_PROOF_SECURITY_LEVEL, }; use miden_prover::ProvingOptions; @@ -38,7 +36,7 @@ use vm_processor::{ }; use super::{TransactionExecutor, TransactionHost, TransactionProver, TransactionVerifier}; -use crate::testing::TransactionContextBuilder; +use crate::{testing::TransactionContextBuilder, TransactionMastStore}; mod kernel_tests; @@ -47,17 +45,13 @@ mod kernel_tests; #[test] fn transaction_executor_witness() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); - let mut executor: TransactionExecutor<_, ()> = - TransactionExecutor::new(tx_context.clone(), None); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); + + let executor: TransactionExecutor<_, ()> = TransactionExecutor::new(tx_context.clone(), None); let account_id = tx_context.account().id(); - executor.load_account(account_id).unwrap(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -70,19 +64,34 @@ fn transaction_executor_witness() { let executed_transaction = executor .execute_transaction(account_id, block_ref, ¬e_ids, tx_context.tx_args().clone()) .unwrap(); - let tx_witness: TransactionWitness = executed_transaction.clone().into(); + + let tx_inputs = executed_transaction.tx_inputs(); + let tx_args = executed_transaction.tx_args(); // use the witness to execute the transaction again - let (stack_inputs, advice_inputs) = tx_witness.get_kernel_inputs(); + let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs( + tx_inputs, + tx_args, + Some(executed_transaction.advice_witness().clone()), + ); let mem_advice_provider: MemAdviceProvider = advice_inputs.into(); - let _authenticator = (); + + // load account/note/tx_script MAST to the mast_store + let mast_store = Rc::new(TransactionMastStore::new()); + mast_store.load_transaction_code(tx_inputs, tx_args); + let mut host: TransactionHost = - TransactionHost::new(tx_witness.account().into(), mem_advice_provider, None); - let result = - vm_processor::execute(tx_witness.program(), stack_inputs, &mut host, Default::default()) + TransactionHost::new(tx_inputs.account().into(), mem_advice_provider, mast_store, None) .unwrap(); + let result = vm_processor::execute( + &TransactionKernel::main(), + stack_inputs, + &mut host, + Default::default(), + ) + .unwrap(); - let (advice_provider, _, output_notes, _signatures) = host.into_parts(); + let (advice_provider, _, output_notes, _signatures, _tx_progress) = host.into_parts(); let (_, map, _) = advice_provider.into_parts(); let tx_outputs = TransactionKernel::from_transaction_parts( result.stack_outputs(), @@ -97,17 +106,12 @@ fn transaction_executor_witness() { #[test] fn executed_transaction_account_delta() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved_with_account_vault_delta() - .build(); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved_with_account_vault_delta() + .build(); - let mut executor: TransactionExecutor<_, ()> = - TransactionExecutor::new(tx_context.clone(), None); + let executor: TransactionExecutor<_, ()> = TransactionExecutor::new(tx_context.clone(), None); let account_id = tx_context.tx_inputs().account().id(); - executor.load_account(account_id).unwrap(); let new_acct_code_src = "\ export.account_proc_1 @@ -115,8 +119,8 @@ fn executed_transaction_account_delta() { dropw end "; - let new_acct_code_ast = ModuleAst::parse(new_acct_code_src).unwrap(); - let new_acct_code = AccountCode::new(new_acct_code_ast.clone(), &Assembler::default()).unwrap(); + let new_acct_code = + AccountCode::compile(new_acct_code_src, TransactionKernel::assembler_testing()).unwrap(); // updated storage let updated_slot_value = [Felt::new(7), Felt::new(9), Felt::new(11), Felt::new(13)]; @@ -146,7 +150,7 @@ fn executed_transaction_account_delta() { let tag1 = NoteTag::from_account_id( ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN.try_into().unwrap(), - NoteExecutionHint::Local, + NoteExecutionMode::Local, ) .unwrap(); let tag2 = NoteTag::for_local_use_case(0, 0).unwrap(); @@ -156,17 +160,17 @@ fn executed_transaction_account_delta() { let aux2 = Felt::new(28); let aux3 = Felt::new(29); - let note_type1 = NoteType::OffChain; - let note_type2 = NoteType::OffChain; - let note_type3 = NoteType::OffChain; + let note_type1 = NoteType::Private; + let note_type2 = NoteType::Private; + let note_type3 = NoteType::Private; assert_eq!(tag1.validate(note_type1), Ok(tag1)); assert_eq!(tag2.validate(note_type2), Ok(tag2)); assert_eq!(tag3.validate(note_type3), Ok(tag3)); - let tx_script = format!( + + let tx_script_src = format!( "\ use.miden::account - use.miden::contracts::wallets::basic->wallet ## ACCOUNT PROCEDURE WRAPPERS ## ======================================================================================== @@ -242,34 +246,43 @@ fn executed_transaction_account_delta() { ## ------------------------------------------------------------------------------------ # partially deplete fungible asset balance push.0.1.2.3 # recipient + push.{EXECUTION_HINT_1} # note_execution_hint push.{NOTETYPE1} # note_type push.{aux1} # aux push.{tag1} # tag push.{REMOVED_ASSET_1} # asset - call.wallet::send_asset dropw dropw dropw dropw + # => [ASSET, tag, aux, note_type, RECIPIENT] + + call.::miden::contracts::wallets::basic::send_asset dropw dropw dropw dropw # => [] # totally deplete fungible asset balance push.0.1.2.3 # recipient + push.{EXECUTION_HINT_2} # note_execution_hint push.{NOTETYPE2} # note_type push.{aux2} # aux push.{tag2} # tag push.{REMOVED_ASSET_2} # asset - call.wallet::send_asset dropw dropw dropw dropw + # => [ASSET, tag, aux, note_type, RECIPIENT] + + call.::miden::contracts::wallets::basic::send_asset dropw dropw dropw dropw # => [] # send non-fungible asset push.0.1.2.3 # recipient + push.{EXECUTION_HINT_3} # note_execution_hint push.{NOTETYPE3} # note_type push.{aux3} # aux push.{tag3} # tag push.{REMOVED_ASSET_3} # asset - call.wallet::send_asset dropw dropw dropw dropw + # => [ASSET, tag, aux, note_type, RECIPIENT] + + call.::miden::contracts::wallets::basic::send_asset dropw dropw dropw dropw # => [] ## Update account code ## ------------------------------------------------------------------------------------ - push.{NEW_ACCOUNT_ROOT} exec.set_code dropw + push.{NEW_ACCOUNT_COMMITMENT} exec.set_code dropw # => [] ## Update the account nonce @@ -278,7 +291,7 @@ fn executed_transaction_account_delta() { # => [] end ", - NEW_ACCOUNT_ROOT = prepare_word(&new_acct_code.root()), + NEW_ACCOUNT_COMMITMENT = prepare_word(&new_acct_code.commitment()), UPDATED_SLOT_VALUE = prepare_word(&Word::from(updated_slot_value)), UPDATED_MAP_VALUE = prepare_word(&Word::from(updated_map_value)), UPDATED_MAP_KEY = prepare_word(&Word::from(updated_map_key)), @@ -288,11 +301,19 @@ fn executed_transaction_account_delta() { NOTETYPE1 = note_type1 as u8, NOTETYPE2 = note_type2 as u8, NOTETYPE3 = note_type3 as u8, + EXECUTION_HINT_1 = Felt::from(NoteExecutionHint::always()), + EXECUTION_HINT_2 = Felt::from(NoteExecutionHint::none()), + EXECUTION_HINT_3 = Felt::from(NoteExecutionHint::on_block_slot(1, 1, 1)), + ); + + let tx_script = + TransactionScript::compile(tx_script_src, [], TransactionKernel::assembler_testing()) + .unwrap(); + let tx_args = TransactionArgs::new( + Some(tx_script), + None, + tx_context.tx_args().advice_inputs().clone().map, ); - let tx_script_code = ProgramAst::parse(&tx_script).unwrap(); - let tx_script = executor.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); - let tx_args = - TransactionArgs::new(Some(tx_script), None, tx_context.tx_args().advice_map().clone()); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -315,24 +336,24 @@ fn executed_transaction_account_delta() { // storage delta // -------------------------------------------------------------------------------------------- // We expect one updated item and one updated map - assert_eq!(executed_transaction.account_delta().storage().updated_items.len(), 1); + assert_eq!(executed_transaction.account_delta().storage().slots().len(), 1); assert_eq!( - executed_transaction.account_delta().storage().updated_items[0].0, - STORAGE_INDEX_0 - ); - assert_eq!( - executed_transaction.account_delta().storage().updated_items[0].1, - updated_slot_value + executed_transaction.account_delta().storage().slots().get(&STORAGE_INDEX_0), + Some(&updated_slot_value) ); - assert_eq!(executed_transaction.account_delta().storage().updated_maps.len(), 1); - assert_eq!( - executed_transaction.account_delta().storage().updated_maps[0].0, - STORAGE_INDEX_2 - ); + assert_eq!(executed_transaction.account_delta().storage().maps().len(), 1); assert_eq!( - executed_transaction.account_delta().storage().updated_maps[0].1.updated_leaves[0], - (updated_map_key, updated_map_value) + executed_transaction + .account_delta() + .storage() + .maps() + .get(&STORAGE_INDEX_2) + .unwrap() + .leaves(), + &Some((updated_map_key.into(), updated_map_value)) + .into_iter() + .collect::>() ); // vault delta @@ -353,40 +374,249 @@ fn executed_transaction_account_delta() { assert!(executed_transaction .account_delta() .vault() - .added_assets - .iter() - .all(|x| added_assets.contains(x))); + .added_assets() + .all(|x| added_assets.contains(&x))); assert_eq!( added_assets.len(), - executed_transaction.account_delta().vault().added_assets.len() + executed_transaction.account_delta().vault().added_assets().count() ); // assert that removed assets are tracked assert!(executed_transaction .account_delta() .vault() - .removed_assets - .iter() - .all(|x| removed_assets.contains(x))); + .removed_assets() + .all(|x| removed_assets.contains(&x))); assert_eq!( removed_assets.len(), - executed_transaction.account_delta().vault().removed_assets.len() + executed_transaction.account_delta().vault().removed_assets().count() ); } #[test] -fn executed_transaction_output_notes() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, +fn test_empty_delta_nonce_update() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); + + let executor: TransactionExecutor<_, ()> = TransactionExecutor::new(tx_context.clone(), None); + let account_id = tx_context.tx_inputs().account().id(); + + let tx_script_src = format!( + "\ + begin + push.1 + + call.{ACCOUNT_INCR_NONCE_MAST_ROOT} + # => [0, 1] + + drop drop + # => [] + end + " + ); + + let tx_script = + TransactionScript::compile(tx_script_src, [], TransactionKernel::assembler_testing()) + .unwrap(); + let tx_args = TransactionArgs::new( + Some(tx_script), + None, + tx_context.tx_args().advice_inputs().clone().map, + ); + + let block_ref = tx_context.tx_inputs().block_header().block_num(); + let note_ids = tx_context + .tx_inputs() + .input_notes() + .iter() + .map(|note| note.id()) + .collect::>(); + + // expected delta + // -------------------------------------------------------------------------------------------- + // execute the transaction and get the witness + let executed_transaction = + executor.execute_transaction(account_id, block_ref, ¬e_ids, tx_args).unwrap(); + + // nonce delta + // -------------------------------------------------------------------------------------------- + assert_eq!(executed_transaction.account_delta().nonce(), Some(Felt::new(2))); + + // storage delta + // -------------------------------------------------------------------------------------------- + // We expect one updated item and one updated map + assert_eq!(executed_transaction.account_delta().storage().slots().len(), 0); + + assert_eq!(executed_transaction.account_delta().storage().maps().len(), 0); +} + +#[test] +fn test_send_note_proc() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved_with_account_vault_delta() + .build(); + + let executor: TransactionExecutor<_, ()> = + TransactionExecutor::new(tx_context.clone(), None).with_debug_mode(true); + let account_id = tx_context.tx_inputs().account().id(); + + // removed assets + let removed_asset_1 = Asset::Fungible( + FungibleAsset::new( + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().expect("id is valid"), + FUNGIBLE_ASSET_AMOUNT / 2, + ) + .expect("asset is valid"), + ); + let removed_asset_2 = Asset::Fungible( + FungibleAsset::new( + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2.try_into().expect("id is valid"), + FUNGIBLE_ASSET_AMOUNT, + ) + .expect("asset is valid"), + ); + let removed_asset_3 = + Asset::mock_non_fungible(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, &NON_FUNGIBLE_ASSET_DATA); + + let tag = NoteTag::from_account_id( + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN.try_into().unwrap(), + NoteExecutionMode::Local, ) - .with_mock_notes_preserved_with_account_vault_delta() - .build(); + .unwrap(); + let aux = Felt::new(27); + let note_type = NoteType::Private; + + assert_eq!(tag.validate(note_type), Ok(tag)); + + // prepare the asset vector to be removed for each test variant + let assets_matrix = vec![ + vec![], + vec![removed_asset_1], + vec![removed_asset_1, removed_asset_2], + vec![removed_asset_1, removed_asset_2, removed_asset_3], + ]; + + for removed_assets in assets_matrix { + // Prepare the string containing the procedures required for adding assets to the note. + // Depending on the number of the assets to remove, the resulting string will be extended + // with the corresponding number of procedure "blocks" + let mut assets_to_remove = String::new(); + for asset in removed_assets.iter() { + assets_to_remove.push_str(&format!( + "\n + # prepare the stack for the next call + dropw + + # push the asset to be removed + push.{ASSET} + # => [ASSET, note_idx, GARBAGE(11)] + + call.wallet::move_asset_to_note + # => [ASSET, note_idx, GARBAGE(11)]\n", + ASSET = prepare_word(&asset.into()) + )) + } + + let tx_script_src = format!( + "\ + use.miden::account + use.miden::contracts::wallets::basic->wallet + use.miden::tx + + ## ACCOUNT PROCEDURE WRAPPERS + ## ======================================================================================== + proc.incr_nonce + call.{ACCOUNT_INCR_NONCE_MAST_ROOT} + # => [0] + + drop + # => [] + end + + ## TRANSACTION SCRIPT + ## ======================================================================================== + begin + # prepare the values for note creation + push.1.2.3.4 # recipient + push.1 # note_execution_hint (NoteExecutionHint::Always) + push.{note_type} # note_type + push.{aux} # aux + push.{tag} # tag + # => [tag, aux, note_type, RECIPIENT, ...] + + # pad the stack with zeros before calling the `create_note`. + padw padw swapdw + # => [tag, aux, execution_hint, note_type, RECIPIENT, PAD(8) ...] + + call.wallet::create_note + # => [note_idx, GARBAGE(15)] + + movdn.4 + # => [GARBAGE(4), note_idx, GARBAGE(11)] + + {assets_to_remove} + + dropw dropw dropw dropw + + ## Update the account nonce + ## ------------------------------------------------------------------------------------ + push.1 exec.incr_nonce drop + # => [] + end + ", + note_type = note_type as u8, + ); + + let tx_script = + TransactionScript::compile(tx_script_src, [], TransactionKernel::assembler_testing()) + .unwrap(); + let tx_args = TransactionArgs::new( + Some(tx_script), + None, + tx_context.tx_args().advice_inputs().clone().map, + ); + + let block_ref = tx_context.tx_inputs().block_header().block_num(); + let note_ids = tx_context + .tx_inputs() + .input_notes() + .iter() + .map(|note| note.id()) + .collect::>(); + + // expected delta + // -------------------------------------------------------------------------------------------- + // execute the transaction and get the witness + let executed_transaction = + executor.execute_transaction(account_id, block_ref, ¬e_ids, tx_args).unwrap(); + + // nonce delta + // -------------------------------------------------------------------------------------------- + assert_eq!(executed_transaction.account_delta().nonce(), Some(Felt::new(2))); + + // vault delta + // -------------------------------------------------------------------------------------------- + // assert that removed assets are tracked + assert!(executed_transaction + .account_delta() + .vault() + .removed_assets() + .all(|x| removed_assets.contains(&x))); + assert_eq!( + removed_assets.len(), + executed_transaction.account_delta().vault().removed_assets().count() + ); + } +} + +#[test] +fn executed_transaction_output_notes() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved_with_account_vault_delta() + .build(); - let mut executor: TransactionExecutor<_, ()> = + let executor: TransactionExecutor<_, ()> = TransactionExecutor::new(tx_context.clone(), None).with_debug_mode(true); let account_id = tx_context.tx_inputs().account().id(); - executor.load_account(account_id).unwrap(); // removed assets let removed_asset_1 = Asset::Fungible( @@ -422,16 +652,16 @@ fn executed_transaction_output_notes() { let tag1 = NoteTag::from_account_id( ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN.try_into().unwrap(), - NoteExecutionHint::Local, + NoteExecutionMode::Local, ) .unwrap(); - let tag2 = NoteTag::for_public_use_case(0, 0, NoteExecutionHint::Local).unwrap(); - let tag3 = NoteTag::for_public_use_case(0, 0, NoteExecutionHint::Local).unwrap(); + let tag2 = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local).unwrap(); + let tag3 = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local).unwrap(); let aux1 = Felt::new(27); let aux2 = Felt::new(28); let aux3 = Felt::new(29); - let note_type1 = NoteType::OffChain; + let note_type1 = NoteType::Private; let note_type2 = NoteType::Public; let note_type3 = NoteType::Public; @@ -439,52 +669,78 @@ fn executed_transaction_output_notes() { assert_eq!(tag2.validate(note_type2), Ok(tag2)); assert_eq!(tag3.validate(note_type3), Ok(tag3)); - // In this test we create 3 notes. Note 1 is private, Note 2 is public and Note 3 is public without assets. + // In this test we create 3 notes. Note 1 is private, Note 2 is public and Note 3 is public + // without assets. // Create the expected output note for Note 2 which is public let serial_num_2 = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); - let note_program_ast_2 = ProgramAst::parse(DEFAULT_NOTE_CODE).unwrap(); - let (note_script_2, _) = NoteScript::new(note_program_ast_2, &Assembler::default()).unwrap(); + let note_script_2 = + NoteScript::compile(DEFAULT_NOTE_CODE, TransactionKernel::assembler_testing()).unwrap(); let inputs_2 = NoteInputs::new(vec![]).unwrap(); - let metadata_2 = NoteMetadata::new(account_id, note_type2, tag2, aux2).unwrap(); + let metadata_2 = + NoteMetadata::new(account_id, note_type2, tag2, NoteExecutionHint::none(), aux2).unwrap(); let vault_2 = NoteAssets::new(vec![removed_asset_3, removed_asset_4]).unwrap(); let recipient_2 = NoteRecipient::new(serial_num_2, note_script_2, inputs_2); let expected_output_note_2 = Note::new(vault_2, metadata_2, recipient_2); // Create the expected output note for Note 3 which is public let serial_num_3 = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]); - let note_program_ast_3 = ProgramAst::parse(DEFAULT_NOTE_CODE).unwrap(); - let (note_script_3, _) = NoteScript::new(note_program_ast_3, &Assembler::default()).unwrap(); + let note_script_3 = + NoteScript::compile(DEFAULT_NOTE_CODE, TransactionKernel::assembler_testing()).unwrap(); let inputs_3 = NoteInputs::new(vec![]).unwrap(); - let metadata_3 = NoteMetadata::new(account_id, note_type3, tag3, aux3).unwrap(); + let metadata_3 = NoteMetadata::new( + account_id, + note_type3, + tag3, + NoteExecutionHint::on_block_slot(1, 2, 3), + aux3, + ) + .unwrap(); let vault_3 = NoteAssets::new(vec![]).unwrap(); let recipient_3 = NoteRecipient::new(serial_num_3, note_script_3, inputs_3); let expected_output_note_3 = Note::new(vault_3, metadata_3, recipient_3); - let tx_script = format!( + let tx_script_src = format!( "\ use.miden::account use.miden::contracts::wallets::basic->wallet ## ACCOUNT PROCEDURE WRAPPERS ## ======================================================================================== - #TODO: Move this into an account library proc.create_note - call.{ACCOUNT_CREATE_NOTE_MAST_ROOT} + # pad the stack before the syscall to prevent accidental modification of the deeper stack + # elements + padw padw swapdw + # => [tag, aux, execution_hint, note_type, RECIPIENT, PAD(8)] + + call.wallet::create_note + # => [note_idx, PAD(15)] - swapw dropw swapw dropw swapw dropw + # remove excess PADs from the stack + swapdw dropw dropw movdn.7 dropw drop drop drop # => [note_idx] end proc.add_asset_to_note + # pad the stack before the syscall to prevent accidental modification of the deeper stack + # elements + push.0.0.0 padw padw swapdw movup.7 swapw + # => [ASSET, note_idx, PAD(11)] + call.{ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT} - swapw dropw + # => [ASSET, note_idx, PAD(11)] + + # remove excess PADs from the stack + swapdw dropw dropw swapw movdn.7 drop drop drop + # => [ASSET, note_idx] + + dropw # => [note_idx] end proc.remove_asset - call.{ACCOUNT_REMOVE_ASSET_MAST_ROOT} - # => [note_ptr] + call.{ACCOUNT_REMOVE_ASSET_MAST_ROOT} + # => [note_ptr] end proc.incr_nonce @@ -502,26 +758,27 @@ fn executed_transaction_output_notes() { ## ------------------------------------------------------------------------------------ # partially deplete fungible asset balance push.0.1.2.3 # recipient + push.{EXECUTION_HINT_1} # note execution hint push.{NOTETYPE1} # note_type push.{aux1} # aux push.{tag1} # tag exec.create_note # => [note_idx] - push.{REMOVED_ASSET_1} # asset exec.remove_asset - movup.4 exec.add_asset_to_note + # => [ASSET, note_ptr] + exec.add_asset_to_note # => [note_idx] - push.{REMOVED_ASSET_2} # asset_2 exec.remove_asset # => [ASSET, note_ptr] - movup.4 exec.add_asset_to_note drop + exec.add_asset_to_note drop # => [] # send non-fungible asset push.{RECIPIENT2} # recipient + push.{EXECUTION_HINT_2} # note execution hint push.{NOTETYPE2} # note_type push.{aux2} # aux push.{tag2} # tag @@ -530,27 +787,27 @@ fn executed_transaction_output_notes() { push.{REMOVED_ASSET_3} # asset_3 exec.remove_asset - movup.4 exec.add_asset_to_note + exec.add_asset_to_note # => [note_idx] push.{REMOVED_ASSET_4} # asset_4 exec.remove_asset # => [ASSET, note_idx] - movup.4 exec.add_asset_to_note drop + exec.add_asset_to_note drop # => [] # create a public note without assets push.{RECIPIENT3} # recipient + push.{EXECUTION_HINT_3} # note execution hint push.{NOTETYPE3} # note_type push.{aux3} # aux push.{tag3} # tag - exec.create_note - - drop + exec.create_note drop + # => [] ## Update the account nonce ## ------------------------------------------------------------------------------------ - push.1 exec.incr_nonce drop + push.1 exec.incr_nonce # => [] end ", @@ -563,11 +820,19 @@ fn executed_transaction_output_notes() { NOTETYPE1 = note_type1 as u8, NOTETYPE2 = note_type2 as u8, NOTETYPE3 = note_type3 as u8, + EXECUTION_HINT_1 = Felt::from(NoteExecutionHint::always()), + EXECUTION_HINT_2 = Felt::from(NoteExecutionHint::none()), + EXECUTION_HINT_3 = Felt::from(NoteExecutionHint::on_block_slot(11, 22, 33)), + ); + + let tx_script = + TransactionScript::compile(tx_script_src, [], TransactionKernel::assembler_testing()) + .unwrap(); + let mut tx_args = TransactionArgs::new( + Some(tx_script), + None, + tx_context.tx_args().advice_inputs().clone().map, ); - let tx_script_code = ProgramAst::parse(&tx_script).unwrap(); - let tx_script = executor.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); - let mut tx_args = - TransactionArgs::new(Some(tx_script), None, tx_context.tx_args().advice_map().clone()); tx_args.add_expected_output_note(&expected_output_note_2); tx_args.add_expected_output_note(&expected_output_note_3); @@ -579,9 +844,11 @@ fn executed_transaction_output_notes() { .iter() .map(|note| note.id()) .collect::>(); + // expected delta // -------------------------------------------------------------------------------------------- // execute the transaction and get the witness + let executed_transaction = executor.execute_transaction(account_id, block_ref, ¬e_ids, tx_args).unwrap(); @@ -593,37 +860,31 @@ fn executed_transaction_output_notes() { // NOTE: the mock state already contains 3 output notes assert_eq!(output_notes.num_notes(), 6); - let created_note_id_3 = executed_transaction.output_notes().get_note(3).id(); + let output_note_id_3 = executed_transaction.output_notes().get_note(3).id(); let recipient_3 = Digest::from([Felt::new(0), Felt::new(1), Felt::new(2), Felt::new(3)]); let note_assets_3 = NoteAssets::new(vec![combined_asset]).unwrap(); let expected_note_id_3 = NoteId::new(recipient_3, note_assets_3.commitment()); - assert_eq!(created_note_id_3, expected_note_id_3); + assert_eq!(output_note_id_3, expected_note_id_3); // assert that the expected output note 2 is present - let created_note = executed_transaction.output_notes().get_note(4); + let output_note = executed_transaction.output_notes().get_note(4); let note_id = expected_output_note_2.id(); let note_metadata = expected_output_note_2.metadata(); - assert_eq!(NoteHeader::from(created_note), NoteHeader::new(note_id, *note_metadata)); + assert_eq!(NoteHeader::from(output_note), NoteHeader::new(note_id, *note_metadata)); // assert that the expected output note 3 is present and has no assets - let created_note_3 = executed_transaction.output_notes().get_note(5); - assert_eq!(expected_output_note_3.id(), created_note_3.id()); - assert_eq!(expected_output_note_3.assets(), created_note_3.assets().unwrap()); + let output_note_3 = executed_transaction.output_notes().get_note(5); + assert_eq!(expected_output_note_3.id(), output_note_3.id()); + assert_eq!(expected_output_note_3.assets(), output_note_3.assets().unwrap()); } #[test] fn prove_witness_and_verify() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); - let mut executor: TransactionExecutor<_, ()> = - TransactionExecutor::new(tx_context.clone(), None); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); let account_id = tx_context.tx_inputs().account().id(); - executor.load_account(account_id).unwrap(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -633,6 +894,7 @@ fn prove_witness_and_verify() { .map(|note| note.id()) .collect::>(); + let executor: TransactionExecutor<_, ()> = TransactionExecutor::new(tx_context.clone(), None); let executed_transaction = executor .execute_transaction(account_id, block_ref, ¬e_ids, tx_context.tx_args().clone()) .unwrap(); @@ -644,8 +906,8 @@ fn prove_witness_and_verify() { assert_eq!(proven_transaction.id(), executed_transaction_id); - let serialised_transaction = proven_transaction.to_bytes(); - let proven_transaction = ProvenTransaction::read_from_bytes(&serialised_transaction).unwrap(); + let serialized_transaction = proven_transaction.to_bytes(); + let proven_transaction = ProvenTransaction::read_from_bytes(&serialized_transaction).unwrap(); let verifier = TransactionVerifier::new(MIN_PROOF_SECURITY_LEVEL); assert!(verifier.verify(proven_transaction).is_ok()); } @@ -655,17 +917,12 @@ fn prove_witness_and_verify() { #[test] fn test_tx_script() { - let tx_context = TransactionContextBuilder::with_standard_account( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ONE, - ) - .with_mock_notes_preserved() - .build(); - let mut executor: TransactionExecutor<_, ()> = - TransactionExecutor::new(tx_context.clone(), None); + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); + let executor: TransactionExecutor<_, ()> = TransactionExecutor::new(tx_context.clone(), None); let account_id = tx_context.tx_inputs().account().id(); - executor.load_account(account_id).unwrap(); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -677,14 +934,15 @@ fn test_tx_script() { let tx_script_input_key = [Felt::new(9999), Felt::new(8888), Felt::new(9999), Felt::new(8888)]; let tx_script_input_value = [Felt::new(9), Felt::new(8), Felt::new(7), Felt::new(6)]; - let tx_script_source = format!( + let tx_script_src = format!( " begin # push the tx script input key onto the stack push.{key} # load the tx script input value from the map and read it onto the stack - adv.push_mapval adv_loadw + adv.push_mapval push.16073 drop # FIX: wrap the decorator to ensure MAST uniqueness + adv_loadw # assert that the value is correct push.{value} assert_eqw @@ -693,16 +951,18 @@ fn test_tx_script() { key = prepare_word(&tx_script_input_key), value = prepare_word(&tx_script_input_value) ); - let tx_script_code = ProgramAst::parse(&tx_script_source).unwrap(); - let tx_script = executor - .compile_tx_script( - tx_script_code, - vec![(tx_script_input_key, tx_script_input_value.into())], - vec![], - ) - .unwrap(); - let tx_args = - TransactionArgs::new(Some(tx_script), None, tx_context.tx_args().advice_map().clone()); + + let tx_script = TransactionScript::compile( + tx_script_src, + [(tx_script_input_key, tx_script_input_value.into())], + TransactionKernel::assembler_testing(), + ) + .unwrap(); + let tx_args = TransactionArgs::new( + Some(tx_script), + None, + tx_context.tx_args().advice_inputs().clone().map, + ); let executed_transaction = executor.execute_transaction(account_id, block_ref, ¬e_ids, tx_args); diff --git a/miden-tx/tests/integration/assets/test.masm b/miden-tx/tests/integration/assets/test.masm index dbeffc726..8e407b9c8 100644 --- a/miden-tx/tests/integration/assets/test.masm +++ b/miden-tx/tests/integration/assets/test.masm @@ -5,10 +5,10 @@ use.miden::kernels::utils # CONSTANTS # ================================================================================================= -const.CREATED_NOTES_OFFSET=10000 +const.OUTPUT_NOTES_OFFSET=10000 -# The memory address at the number of created notes is stored. -const.NUM_CREATED_NOTES_PTR=2 +# The memory address at which the number of output notes is stored. +const.NUM_OUTPUT_NOTES_PTR=2 # TEST UTILS # ================================================================================================= @@ -68,7 +68,7 @@ proc.create_mock_notes push.100.0.0.12033618204333965332 push.10000.2048.7 add add mem_storew dropw - # set num created notes + # set num output notes push.3.2 mem_store end diff --git a/miden-tx/tests/integration/main.rs b/miden-tx/tests/integration/main.rs index 1c7f7e408..3b74b5e06 100644 --- a/miden-tx/tests/integration/main.rs +++ b/miden-tx/tests/integration/main.rs @@ -7,11 +7,11 @@ use miden_objects::{ account_id::testing::ACCOUNT_ID_SENDER, Account, AccountCode, AccountId, AccountStorage, SlotItem, }, - assembly::{ModuleAst, ProgramAst}, assets::{Asset, AssetVault, FungibleAsset}, crypto::{dsa::rpo_falcon512::SecretKey, utils::Serializable}, notes::{Note, NoteAssets, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteType}, - transaction::{ExecutedTransaction, ProvenTransaction}, + testing::account_code::DEFAULT_AUTH_SCRIPT, + transaction::{ExecutedTransaction, ProvenTransaction, TransactionArgs, TransactionScript}, Felt, Word, ZERO, }; use miden_prover::ProvingOptions; @@ -76,10 +76,9 @@ pub fn get_account_with_default_account_code( use miden_objects::testing::account_code::DEFAULT_ACCOUNT_CODE; let account_code_src = DEFAULT_ACCOUNT_CODE; - let account_code_ast = ModuleAst::parse(account_code_src).unwrap(); - let account_assembler = TransactionKernel::assembler().with_debug_mode(true); + let assembler = TransactionKernel::assembler().with_debug_mode(true); - let account_code = AccountCode::new(account_code_ast.clone(), &account_assembler).unwrap(); + let account_code = AccountCode::compile(account_code_src, assembler).unwrap(); let account_storage = AccountStorage::new(vec![SlotItem::new_value(0, 0, public_key)], BTreeMap::new()).unwrap(); @@ -94,17 +93,33 @@ pub fn get_account_with_default_account_code( #[cfg(test)] pub fn get_note_with_fungible_asset_and_script( fungible_asset: FungibleAsset, - note_script: ProgramAst, + note_script: &str, ) -> Note { - let note_assembler = TransactionKernel::assembler().with_debug_mode(true); - let (note_script, _) = NoteScript::new(note_script, ¬e_assembler).unwrap(); + use miden_objects::notes::NoteExecutionHint; + + let assembler = TransactionKernel::assembler().with_debug_mode(true); + let note_script = NoteScript::compile(note_script, assembler).unwrap(); const SERIAL_NUM: Word = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); let vault = NoteAssets::new(vec![fungible_asset.into()]).unwrap(); - let metadata = NoteMetadata::new(sender_id, NoteType::Public, 1.into(), ZERO).unwrap(); + let metadata = + NoteMetadata::new(sender_id, NoteType::Public, 1.into(), NoteExecutionHint::Always, ZERO) + .unwrap(); let inputs = NoteInputs::new(vec![]).unwrap(); let recipient = NoteRecipient::new(SERIAL_NUM, note_script, inputs); Note::new(vault, metadata, recipient) } + +#[cfg(test)] +pub fn build_default_auth_script() -> TransactionScript { + TransactionScript::compile(DEFAULT_AUTH_SCRIPT, [], TransactionKernel::assembler()).unwrap() +} + +#[cfg(test)] +pub fn build_tx_args_from_script(script_source: &str) -> TransactionArgs { + let tx_script = + TransactionScript::compile(script_source, [], TransactionKernel::assembler()).unwrap(); + TransactionArgs::with_tx_script(tx_script) +} diff --git a/miden-tx/tests/integration/scripts/faucet.rs b/miden-tx/tests/integration/scripts/faucet.rs index 7dd56230c..758801436 100644 --- a/miden-tx/tests/integration/scripts/faucet.rs +++ b/miden-tx/tests/integration/scripts/faucet.rs @@ -2,32 +2,30 @@ extern crate alloc; use std::collections::BTreeMap; -use miden_lib::{ - accounts::faucets::create_basic_fungible_faucet, - transaction::{memory::FAUCET_STORAGE_DATA_SLOT, TransactionKernel}, - AuthScheme, -}; +use miden_lib::transaction::{memory::FAUCET_STORAGE_DATA_SLOT, TransactionKernel}; use miden_objects::{ accounts::{ account_id::testing::ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, Account, AccountCode, AccountId, - AccountStorage, AccountStorageType, SlotItem, + AccountStorage, SlotItem, }, - assembly::{ModuleAst, ProgramAst}, - assets::{Asset, AssetVault, FungibleAsset, TokenSymbol}, - crypto::dsa::rpo_falcon512::SecretKey, - notes::{NoteAssets, NoteId, NoteMetadata, NoteTag, NoteType}, + assets::{Asset, AssetVault, FungibleAsset}, + notes::{NoteAssets, NoteExecutionHint, NoteId, NoteMetadata, NoteTag, NoteType}, testing::prepare_word, - transaction::TransactionArgs, - Felt, Word, ZERO, + Felt, Word, }; use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; -use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use crate::{ - get_new_pk_and_authenticator, get_note_with_fungible_asset_and_script, - prove_and_verify_transaction, + build_tx_args_from_script, get_new_pk_and_authenticator, + get_note_with_fungible_asset_and_script, prove_and_verify_transaction, }; +const FUNGIBLE_FAUCET_SOURCE: &str = " +export.::miden::contracts::faucets::basic_fungible::distribute +export.::miden::contracts::faucets::basic_fungible::burn +export.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 +"; + // TESTS MINT FUNGIBLE ASSET // ================================================================================================ @@ -41,8 +39,7 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { // -------------------------------------------------------------------------------------------- let tx_context = TransactionContextBuilder::new(faucet_account.clone()).build(); - let mut executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - executor.load_account(faucet_account.id()).unwrap(); + let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -55,42 +52,37 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { let recipient = [Felt::new(0), Felt::new(1), Felt::new(2), Felt::new(3)]; let tag = NoteTag::for_local_use_case(0, 0).unwrap(); let aux = Felt::new(27); - let note_type = NoteType::OffChain; + let note_execution_hint = NoteExecutionHint::on_block_slot(5, 6, 7); + let note_type = NoteType::Private; let amount = Felt::new(100); assert_eq!(tag.validate(note_type), Ok(tag)); - let tx_script_code = ProgramAst::parse( - format!( - " - use.miden::contracts::faucets::basic_fungible->faucet - use.miden::contracts::auth::basic->auth_tx - + let tx_script_code = format!( + " begin push.{recipient} + push.{note_execution_hint} push.{note_type} push.{aux} push.{tag} push.{amount} - call.faucet::distribute + call.::miden::contracts::faucets::basic_fungible::distribute - call.auth_tx::auth_tx_rpo_falcon512 - dropw dropw + call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 + dropw dropw drop end ", - note_type = note_type as u8, - recipient = prepare_word(&recipient), - aux = aux, - tag = u32::from(tag), - ) - .as_str(), - ) - .unwrap(); + note_type = note_type as u8, + recipient = prepare_word(&recipient), + aux = aux, + tag = u32::from(tag), + note_execution_hint = Felt::from(note_execution_hint) + ); - let tx_script = executor.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); - let tx_args = TransactionArgs::with_tx_script(tx_script); + let tx_args = build_tx_args_from_script(&tx_script_code); let executed_transaction = executor .execute_transaction(faucet_account.id(), block_ref, ¬e_ids, tx_args) @@ -101,15 +93,16 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { let fungible_asset: Asset = FungibleAsset::new(faucet_account.id(), amount.into()).unwrap().into(); - let created_note = executed_transaction.output_notes().get_note(0).clone(); + let output_note = executed_transaction.output_notes().get_note(0).clone(); let assets = NoteAssets::new(vec![fungible_asset]).unwrap(); let id = NoteId::new(recipient.into(), assets.commitment()); - assert_eq!(created_note.id(), id); + assert_eq!(output_note.id(), id); assert_eq!( - created_note.metadata(), - &NoteMetadata::new(faucet_account.id(), NoteType::OffChain, tag, aux).unwrap() + output_note.metadata(), + &NoteMetadata::new(faucet_account.id(), NoteType::Private, tag, note_execution_hint, aux) + .unwrap() ); } @@ -123,8 +116,7 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() { // -------------------------------------------------------------------------------------------- let tx_context = TransactionContextBuilder::new(faucet_account.clone()).build(); - let mut executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - executor.load_account(faucet_account.id()).unwrap(); + let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -139,12 +131,8 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() { let tag = Felt::new(4); let amount = Felt::new(250); - let tx_script_code = ProgramAst::parse( - format!( - " - use.miden::contracts::faucets::basic_fungible->faucet - use.miden::contracts::auth::basic->auth_tx - + let tx_script_code = format!( + " begin push.{recipient} @@ -152,22 +140,18 @@ fn faucet_contract_mint_fungible_asset_fails_exceeds_max_supply() { push.{aux} push.{tag} push.{amount} - call.faucet::distribute + call.::miden::contracts::faucets::basic_fungible::distribute - call.auth_tx::auth_tx_rpo_falcon512 + call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 dropw dropw end ", - note_type = NoteType::OffChain as u8, - recipient = prepare_word(&recipient), - ) - .as_str(), - ) - .unwrap(); - let tx_script = executor.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); + note_type = NoteType::Private as u8, + recipient = prepare_word(&recipient), + ); - let tx_args = TransactionArgs::with_tx_script(tx_script); + let tx_args = build_tx_args_from_script(&tx_script_code); // Execute the transaction and get the witness let executed_transaction = @@ -198,21 +182,15 @@ fn prove_faucet_contract_burn_fungible_asset_succeeds() { ); // need to create a note with the fungible asset to be burned - let note_script = ProgramAst::parse( - " - use.miden::contracts::faucets::basic_fungible->faucet_contract - use.miden::note - + let note_script = " # burn the asset begin dropw - exec.note::get_assets drop + exec.::miden::note::get_assets drop mem_loadw - call.faucet_contract::burn + call.::miden::contracts::faucets::basic_fungible::burn end - ", - ) - .unwrap(); + "; let note = get_note_with_fungible_asset_and_script(fungible_asset, note_script); @@ -222,8 +200,7 @@ fn prove_faucet_contract_burn_fungible_asset_succeeds() { .input_notes(vec![note.clone()]) .build(); - let mut executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - executor.load_account(faucet_account.id()).unwrap(); + let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -251,73 +228,18 @@ fn prove_faucet_contract_burn_fungible_asset_succeeds() { assert_eq!(executed_transaction.input_notes().get_note(0).id(), note.id()); } -// TESTS FUNGIBLE CONTRACT CONSTRUCTION +// HELPER FUNCTIONS // ================================================================================================ -#[test] -fn faucet_contract_creation() { - // we need a Falcon Public Key to create the wallet account - let seed = [0_u8; 32]; - let mut rng = ChaCha20Rng::from_seed(seed); - - let sec_key = SecretKey::with_rng(&mut rng); - let pub_key = sec_key.public_key(); - let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key }; - - // we need to use an initial seed to create the wallet account - let init_seed: [u8; 32] = [ - 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85, 183, - 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16, - ]; - - let max_supply = Felt::new(123); - let token_symbol_string = "POL"; - let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap(); - let decimals = 2u8; - let storage_type = AccountStorageType::OffChain; - - let (faucet_account, _) = create_basic_fungible_faucet( - init_seed, - token_symbol, - decimals, - max_supply, - storage_type, - auth_scheme, - ) - .unwrap(); - - // check that max_supply (slot 1) is 123 - assert_eq!( - faucet_account.storage().get_item(1), - [Felt::new(123), Felt::new(2), token_symbol.into(), ZERO].into() - ); - - assert!(faucet_account.is_faucet()); - - let exp_faucet_account_code_src = - include_str!("../../../../miden-lib/asm/miden/contracts/faucets/basic_fungible.masm"); - let exp_faucet_account_code_ast = ModuleAst::parse(exp_faucet_account_code_src).unwrap(); - let account_assembler = TransactionKernel::assembler().with_debug_mode(true); - - let exp_faucet_account_code = - AccountCode::new(exp_faucet_account_code_ast.clone(), &account_assembler).unwrap(); - - assert_eq!(faucet_account.code(), &exp_faucet_account_code); -} - fn get_faucet_account_with_max_supply_and_total_issuance( public_key: Word, max_supply: u64, total_issuance: Option, ) -> Account { let faucet_account_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); - let faucet_account_code_src = - include_str!("../../../../miden-lib/asm/miden/contracts/faucets/basic_fungible.masm"); - let faucet_account_code_ast = ModuleAst::parse(faucet_account_code_src).unwrap(); - let account_assembler = TransactionKernel::assembler().with_debug_mode(true); - let faucet_account_code = - AccountCode::new(faucet_account_code_ast.clone(), &account_assembler).unwrap(); + let assembler = TransactionKernel::assembler(); + let faucet_account_code = AccountCode::compile(FUNGIBLE_FAUCET_SOURCE, assembler).unwrap(); let faucet_storage_slot_1 = [Felt::new(max_supply), Felt::new(0), Felt::new(0), Felt::new(0)]; let mut faucet_account_storage = AccountStorage::new( diff --git a/miden-tx/tests/integration/scripts/p2id.rs b/miden-tx/tests/integration/scripts/p2id.rs index 6474b59a7..579c5fe11 100644 --- a/miden-tx/tests/integration/scripts/p2id.rs +++ b/miden-tx/tests/integration/scripts/p2id.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use miden_lib::{notes::create_p2id_note, transaction::TransactionKernel}; use miden_objects::{ accounts::{ @@ -7,20 +9,29 @@ use miden_objects::{ ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, }, - Account, AccountId, + Account, AccountId, AccountType, SlotItem, }, - assembly::ProgramAst, assets::{Asset, AssetVault, FungibleAsset}, crypto::rand::RpoRandomCoin, - notes::{NoteScript, NoteType}, - testing::{account_code::DEFAULT_AUTH_SCRIPT, notes::DEFAULT_NOTE_CODE}, - transaction::TransactionArgs, - Felt, + notes::NoteType, + testing::{account::AccountBuilder, account_code::DEFAULT_AUTH_SCRIPT}, + transaction::{TransactionArgs, TransactionScript}, + Felt, FieldElement, +}; +use miden_tx::{ + auth::BasicAuthenticator, + testing::{ + mock_chain::{Auth, MockChain}, + TransactionContextBuilder, + }, + TransactionExecutor, }; -use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; +use rand::{rngs::StdRng, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use vm_processor::Word; use crate::{ - get_account_with_default_account_code, get_new_pk_and_authenticator, + build_default_auth_script, get_account_with_default_account_code, get_new_pk_and_authenticator, prove_and_verify_transaction, }; @@ -61,8 +72,7 @@ fn prove_p2id_script() { .input_notes(vec![note.clone()]) .build(); - let mut executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); - executor.load_account(target_account_id).unwrap(); + let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth.clone())); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -72,10 +82,7 @@ fn prove_p2id_script() { .map(|note| note.id()) .collect::>(); - let tx_script_code = ProgramAst::parse(DEFAULT_AUTH_SCRIPT).unwrap(); - - let tx_script_target = - executor.compile_tx_script(tx_script_code.clone(), vec![], vec![]).unwrap(); + let tx_script_target = build_default_auth_script(); let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); // Execute the transaction and get the witness @@ -109,11 +116,10 @@ fn prove_p2id_script() { let tx_context_malicious_account = TransactionContextBuilder::new(malicious_account) .input_notes(vec![note]) .build(); - let mut executor_2 = + let executor_2 = TransactionExecutor::new(tx_context_malicious_account.clone(), Some(malicious_falcon_auth)); - executor_2.load_account(malicious_account_id).unwrap(); - let tx_script_malicious = executor.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); + let tx_script_malicious = build_default_auth_script(); let tx_args_malicious = TransactionArgs::with_tx_script(tx_script_malicious); let block_ref = tx_context_malicious_account.tx_inputs().block_header().block_num(); @@ -173,8 +179,7 @@ fn p2id_script_multiple_assets() { .input_notes(vec![note.clone()]) .build(); - let mut executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth)); - executor.load_account(target_account_id).unwrap(); + let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth)); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -184,10 +189,7 @@ fn p2id_script_multiple_assets() { .map(|note| note.id()) .collect::>(); - let tx_script_code = ProgramAst::parse(DEFAULT_AUTH_SCRIPT).unwrap(); - let tx_script_target = - executor.compile_tx_script(tx_script_code.clone(), vec![], vec![]).unwrap(); - + let tx_script_target = build_default_auth_script(); let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); // Execute the transaction and get the witness @@ -218,11 +220,9 @@ fn p2id_script_multiple_assets() { let tx_context_malicious_account = TransactionContextBuilder::new(malicious_account) .input_notes(vec![note]) .build(); - let mut executor_2 = + let executor_2 = TransactionExecutor::new(tx_context_malicious_account.clone(), Some(malicious_falcon_auth)); - executor_2.load_account(malicious_account_id).unwrap(); - let tx_script_malicious = - executor.compile_tx_script(tx_script_code.clone(), vec![], vec![]).unwrap(); + let tx_script_malicious = build_default_auth_script(); let tx_args_malicious = TransactionArgs::with_tx_script(tx_script_malicious); let block_ref = tx_context_malicious_account.tx_inputs().block_header().block_num(); @@ -244,15 +244,125 @@ fn p2id_script_multiple_assets() { assert!(executed_transaction_2.is_err()); } +/// Consumes an existing note with a new account #[test] -fn test_note_script_to_from_felt() { - let assembler = TransactionKernel::assembler().with_debug_mode(true); +fn prove_consume_note_with_new_account() { + let (mut target_account, seed, falcon_auth) = create_new_account(); + let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); + let fungible_asset_1: Asset = FungibleAsset::new(faucet_id, 123).unwrap().into(); - let note_program_ast = ProgramAst::parse(DEFAULT_NOTE_CODE).unwrap(); - let (note_script, _) = NoteScript::new(note_program_ast, &assembler).unwrap(); + // Create the note + let note = create_p2id_note( + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(), + target_account.id(), + vec![fungible_asset_1], + NoteType::Public, + Felt::new(0), + &mut RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), + ) + .unwrap(); + + let tx_context = TransactionContextBuilder::new(target_account.clone()) + .account_seed(Some(seed)) + .input_notes(vec![note.clone()]) + .build(); + + assert!(target_account.is_new()); + + let executor = TransactionExecutor::new(tx_context.clone(), Some(falcon_auth)); - let encoded: Vec = (¬e_script).into(); - let decoded: NoteScript = encoded.try_into().unwrap(); + let block_ref = tx_context.tx_inputs().block_header().block_num(); + let note_ids = tx_context + .tx_inputs() + .input_notes() + .iter() + .map(|note| note.id()) + .collect::>(); + + let tx_script_target = build_default_auth_script(); + let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); + + // Execute the transaction and get the witness + let executed_transaction = executor + .execute_transaction(target_account.id(), block_ref, ¬e_ids, tx_args_target) + .unwrap(); + + // Account delta + target_account.apply_delta(executed_transaction.account_delta()).unwrap(); + assert!(!target_account.is_new()); + + assert!(prove_and_verify_transaction(executed_transaction).is_ok()); +} + +/// Consumes two existing notes (with an asset from a faucet for a combined total of 123 tokens) +/// with a basic account +#[test] +fn prove_consume_multiple_notes() { + let mut mock_chain = MockChain::new(); + let mut account = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![]); + + let fungible_asset_1: Asset = + FungibleAsset::new(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(), 100) + .unwrap() + .into(); + let fungible_asset_2: Asset = + FungibleAsset::new(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(), 23) + .unwrap() + .into(); + + let note_1 = mock_chain + .add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[fungible_asset_1], + NoteType::Private, + ) + .unwrap(); + let note_2 = mock_chain + .add_p2id_note( + ACCOUNT_ID_SENDER.try_into().unwrap(), + account.id(), + &[fungible_asset_2], + NoteType::Private, + ) + .unwrap(); + + let tx_script = + TransactionScript::compile(DEFAULT_AUTH_SCRIPT, vec![], TransactionKernel::assembler()) + .unwrap(); + let tx_context = mock_chain + .build_tx_context(account.id()) + .input_notes(vec![note_1, note_2]) + .tx_script(tx_script) + .build(); + + let executed_transaction = tx_context.execute().unwrap(); + + account.apply_delta(executed_transaction.account_delta()).unwrap(); + let resulting_asset = account.vault().assets().next().unwrap(); + if let Asset::Fungible(asset) = resulting_asset { + assert_eq!(asset.amount(), 123u64); + } else { + panic!("Resulting asset should be fungible"); + } + + assert!(prove_and_verify_transaction(executed_transaction).is_ok()); +} + +// HELPER FUNCTIONS +// =============================================================================================== + +fn create_new_account() -> (Account, Word, Rc>) { + let (pub_key, falcon_auth) = get_new_pk_and_authenticator(); + + let storage_item = SlotItem::new_value(0, 0, pub_key); + + let (account, seed) = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .add_storage_item(storage_item) + .account_type(AccountType::RegularAccountUpdatableCode) + .nonce(Felt::ZERO) + .build(TransactionKernel::assembler()) + .unwrap(); - assert_eq!(note_script, decoded); + (account, seed, falcon_auth) } diff --git a/miden-tx/tests/integration/scripts/p2idr.rs b/miden-tx/tests/integration/scripts/p2idr.rs index b89cd580d..3a1fb1fb9 100644 --- a/miden-tx/tests/integration/scripts/p2idr.rs +++ b/miden-tx/tests/integration/scripts/p2idr.rs @@ -8,17 +8,17 @@ use miden_objects::{ }, Account, AccountId, }, - assembly::ProgramAst, assets::{Asset, AssetVault, FungibleAsset}, crypto::rand::RpoRandomCoin, notes::NoteType, - testing::account_code::DEFAULT_AUTH_SCRIPT, transaction::TransactionArgs, Felt, }; use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; -use crate::{get_account_with_default_account_code, get_new_pk_and_authenticator}; +use crate::{ + build_default_auth_script, get_account_with_default_account_code, get_new_pk_and_authenticator, +}; // P2IDR TESTS // =============================================================================================== @@ -84,27 +84,26 @@ fn p2idr_script() { .unwrap(); // -------------------------------------------------------------------------------------------- - // We have two cases: - // Case "in time": block height is 4, reclaim block height is 5. Only the target account can consume the note. - // Case "reclaimable": block height is 4, reclaim block height is 3. Target and sender account can consume the note. - // The malicious account should never be able to consume the note. + // We have two cases. + // + // Case "in time": block height is 4, reclaim block height is 5. Only the target account can + // consume the note. + // + // Case "reclaimable": block height is 4, reclaim block height is 3. Target and sender account + // can consume the note. The malicious account should never be able to consume the note. // -------------------------------------------------------------------------------------------- // CONSTRUCT AND EXECUTE TX (Case "in time" - Target Account Execution Success) // -------------------------------------------------------------------------------------------- let tx_context_1 = TransactionContextBuilder::new(target_account.clone()) .input_notes(vec![note_in_time.clone()]) .build(); - let mut executor_1 = + let executor_1 = TransactionExecutor::new(tx_context_1.clone(), Some(target_falcon_auth.clone())); - executor_1.load_account(target_account_id).unwrap(); - let block_ref_1 = tx_context_1.tx_inputs().block_header().block_num(); let note_ids = tx_context_1.input_notes().iter().map(|note| note.id()).collect::>(); - let tx_script_code = ProgramAst::parse(DEFAULT_AUTH_SCRIPT).unwrap(); - let tx_script_target = - executor_1.compile_tx_script(tx_script_code.clone(), vec![], vec![]).unwrap(); + let tx_script_target = build_default_auth_script(); let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); // Execute the transaction and get the witness @@ -127,11 +126,9 @@ fn p2idr_script() { let tx_context_2 = TransactionContextBuilder::new(sender_account.clone()) .input_notes(vec![note_in_time.clone()]) .build(); - let mut executor_2 = + let executor_2 = TransactionExecutor::new(tx_context_2.clone(), Some(sender_falcon_auth.clone())); - executor_2.load_account(sender_account_id).unwrap(); - let tx_script_sender = - executor_2.compile_tx_script(tx_script_code.clone(), vec![], vec![]).unwrap(); + let tx_script_sender = build_default_auth_script(); let tx_args_sender = TransactionArgs::with_tx_script(tx_script_sender); let block_ref_2 = tx_context_2.tx_inputs().block_header().block_num(); @@ -154,10 +151,10 @@ fn p2idr_script() { let tx_context_3 = TransactionContextBuilder::new(malicious_account.clone()) .input_notes(vec![note_in_time.clone()]) .build(); - let mut executor_3 = + let executor_3 = TransactionExecutor::new(tx_context_3.clone(), Some(malicious_falcon_auth.clone())); - executor_3.load_account(malicious_account_id).unwrap(); - let tx_script_malicious = executor_3.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); + + let tx_script_malicious = build_default_auth_script(); let tx_args_malicious = TransactionArgs::with_tx_script(tx_script_malicious); let block_ref_3 = tx_context_3.tx_inputs().block_header().block_num(); @@ -180,8 +177,7 @@ fn p2idr_script() { let tx_context_4 = TransactionContextBuilder::new(target_account.clone()) .input_notes(vec![note_reclaimable.clone()]) .build(); - let mut executor_4 = TransactionExecutor::new(tx_context_4.clone(), Some(target_falcon_auth)); - executor_4.load_account(target_account_id).unwrap(); + let executor_4 = TransactionExecutor::new(tx_context_4.clone(), Some(target_falcon_auth)); let block_ref_4 = tx_context_4.tx_inputs().block_header().block_num(); let note_ids_4 = tx_context_4.input_notes().iter().map(|note| note.id()).collect::>(); @@ -211,9 +207,7 @@ fn p2idr_script() { let tx_context_5 = TransactionContextBuilder::new(sender_account.clone()) .input_notes(vec![note_reclaimable.clone()]) .build(); - let mut executor_5 = TransactionExecutor::new(tx_context_5.clone(), Some(sender_falcon_auth)); - - executor_5.load_account(sender_account_id).unwrap(); + let executor_5 = TransactionExecutor::new(tx_context_5.clone(), Some(sender_falcon_auth)); let block_ref_5 = tx_context_5.tx_inputs().block_header().block_num(); let note_ids_5 = tx_context_5.input_notes().iter().map(|note| note.id()).collect::>(); @@ -244,10 +238,7 @@ fn p2idr_script() { .input_notes(vec![note_reclaimable.clone()]) .build(); - let mut executor_6 = - TransactionExecutor::new(tx_context_6.clone(), Some(malicious_falcon_auth)); - - executor_6.load_account(malicious_account_id).unwrap(); + let executor_6 = TransactionExecutor::new(tx_context_6.clone(), Some(malicious_falcon_auth)); let block_ref_6 = tx_context_6.tx_inputs().block_header().block_num(); let note_ids_6 = tx_context_6.input_notes().iter().map(|note| note.id()).collect::>(); diff --git a/miden-tx/tests/integration/scripts/swap.rs b/miden-tx/tests/integration/scripts/swap.rs index 1f569f35d..d8c8319f5 100644 --- a/miden-tx/tests/integration/scripts/swap.rs +++ b/miden-tx/tests/integration/scripts/swap.rs @@ -1,32 +1,26 @@ -use miden_lib::notes::create_swap_note; +use miden_lib::{notes::create_swap_note, transaction::TransactionKernel}; use miden_objects::{ - accounts::{ - account_id::testing::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, ACCOUNT_ID_SENDER, - }, - Account, AccountId, - }, - assembly::ProgramAst, - assets::{Asset, AssetVault, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}, + accounts::{account_id::testing::ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, Account, AccountId}, + assets::{Asset, AssetVault, NonFungibleAsset, NonFungibleAssetDetails}, crypto::rand::RpoRandomCoin, - notes::{NoteAssets, NoteExecutionHint, NoteHeader, NoteId, NoteMetadata, NoteTag, NoteType}, + notes::{ + NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteHeader, NoteId, NoteMetadata, + NoteTag, NoteType, + }, testing::account_code::DEFAULT_AUTH_SCRIPT, - transaction::TransactionArgs, + transaction::TransactionScript, Felt, ZERO, }; -use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; +use miden_tx::testing::mock_chain::{Auth, MockChain}; -use crate::{ - get_account_with_default_account_code, get_new_pk_and_authenticator, - prove_and_verify_transaction, -}; +use crate::prove_and_verify_transaction; #[test] fn prove_swap_script() { // Create assets - let faucet_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let offered_asset: Asset = FungibleAsset::new(faucet_id, 100).unwrap().into(); + let mut chain = MockChain::new(); + let faucet = chain.add_existing_faucet(Auth::NoAuth, "POL", 100000u64); + let offered_asset = faucet.mint(100); let faucet_id_2 = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); let requested_asset: Asset = NonFungibleAsset::new( @@ -36,20 +30,12 @@ fn prove_swap_script() { .into(); // Create sender and target account - let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - - let target_account_id = - AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(); - let (target_pub_key, target_falcon_auth) = get_new_pk_and_authenticator(); - let target_account = get_account_with_default_account_code( - target_account_id, - target_pub_key, - Some(requested_asset), - ); + let sender_account = chain.add_new_wallet(Auth::BasicAuth, vec![offered_asset]); + let target_account = chain.add_existing_wallet(Auth::BasicAuth, vec![requested_asset]); // Create the note containing the SWAP script let (note, payback_note) = create_swap_note( - sender_account_id, + sender_account.id(), offered_asset, requested_asset, NoteType::Public, @@ -58,32 +44,21 @@ fn prove_swap_script() { ) .unwrap(); + chain.add_note(note.clone()); + chain.seal_block(None); + // CONSTRUCT AND EXECUTE TX (Success) // -------------------------------------------------------------------------------------------- - let tx_context = TransactionContextBuilder::new(target_account.clone()) - .input_notes(vec![note.clone()]) - .build(); + let transaction_script = + TransactionScript::compile(DEFAULT_AUTH_SCRIPT, vec![], TransactionKernel::assembler()) + .unwrap(); - let mut executor = - TransactionExecutor::new(tx_context.clone(), Some(target_falcon_auth.clone())); - executor.load_account(target_account_id).unwrap(); - - let block_ref = tx_context.tx_inputs().block_header().block_num(); - let note_ids = tx_context - .tx_inputs() - .input_notes() - .iter() - .map(|note| note.id()) - .collect::>(); - - let tx_script_code = ProgramAst::parse(DEFAULT_AUTH_SCRIPT).unwrap(); - let tx_script_target = - executor.compile_tx_script(tx_script_code.clone(), vec![], vec![]).unwrap(); - let tx_args_target = TransactionArgs::with_tx_script(tx_script_target); - - let executed_transaction = executor - .execute_transaction(target_account_id, block_ref, ¬e_ids, tx_args_target) - .expect("Transaction consuming swap note failed"); + let executed_transaction = chain + .build_tx_context(target_account.id()) + .tx_script(transaction_script) + .build() + .execute() + .unwrap(); // target account vault delta let target_account_after: Account = Account::from_parts( @@ -100,16 +75,22 @@ fn prove_swap_script() { // Check if only one `Note` has been created assert_eq!(executed_transaction.output_notes().num_notes(), 1); - // Check if the created `Note` is what we expect + // Check if the output `Note` is what we expect let recipient = payback_note.recipient().clone(); - let tag = NoteTag::from_account_id(sender_account_id, NoteExecutionHint::Local).unwrap(); - let note_metadata = - NoteMetadata::new(target_account_id, NoteType::OffChain, tag, ZERO).unwrap(); + let tag = NoteTag::from_account_id(sender_account.id(), NoteExecutionMode::Local).unwrap(); + let note_metadata = NoteMetadata::new( + target_account.id(), + NoteType::Private, + tag, + NoteExecutionHint::Always, + ZERO, + ) + .unwrap(); let assets = NoteAssets::new(vec![requested_asset]).unwrap(); let note_id = NoteId::new(recipient.digest(), assets.commitment()); - let created_note = executed_transaction.output_notes().get_note(0); - assert_eq!(NoteHeader::from(created_note), NoteHeader::new(note_id, note_metadata)); + let output_note = executed_transaction.output_notes().get_note(0); + assert_eq!(NoteHeader::from(output_note), NoteHeader::new(note_id, note_metadata)); // Prove, serialize/deserialize and verify the transaction assert!(prove_and_verify_transaction(executed_transaction.clone()).is_ok()); diff --git a/miden-tx/tests/integration/wallet/mod.rs b/miden-tx/tests/integration/wallet/mod.rs index 7e4be695a..87be64082 100644 --- a/miden-tx/tests/integration/wallet/mod.rs +++ b/miden-tx/tests/integration/wallet/mod.rs @@ -9,11 +9,10 @@ use miden_objects::{ }, Account, AccountId, AccountStorage, SlotItem, }, - assembly::ProgramAst, assets::{Asset, AssetVault, FungibleAsset}, crypto::dsa::rpo_falcon512::SecretKey, - notes::{NoteTag, NoteType}, - testing::{account_code::DEFAULT_AUTH_SCRIPT, prepare_word}, + notes::{NoteExecutionHint, NoteTag, NoteType}, + testing::prepare_word, transaction::TransactionArgs, Felt, Word, ONE, ZERO, }; @@ -21,8 +20,9 @@ use miden_tx::{testing::TransactionContextBuilder, TransactionExecutor}; use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use crate::{ - get_account_with_default_account_code, get_new_pk_and_authenticator, - get_note_with_fungible_asset_and_script, prove_and_verify_transaction, + build_default_auth_script, build_tx_args_from_script, get_account_with_default_account_code, + get_new_pk_and_authenticator, get_note_with_fungible_asset_and_script, + prove_and_verify_transaction, }; #[test] @@ -39,26 +39,18 @@ fn prove_receive_asset_via_wallet() { get_account_with_default_account_code(target_account_id, target_pub_key, None); // Create the note - let note_script_ast = ProgramAst::parse( - " - use.miden::note - use.miden::contracts::wallets::basic->wallet - + let note_script_src = " # add the asset begin dropw - exec.note::get_assets drop + exec.::miden::note::get_assets drop mem_loadw - call.wallet::receive_asset + call.::miden::contracts::wallets::basic::receive_asset dropw end - " - .to_string() - .as_str(), - ) - .unwrap(); + "; - let note = get_note_with_fungible_asset_and_script(fungible_asset_1, note_script_ast); + let note = get_note_with_fungible_asset_and_script(fungible_asset_1, note_script_src); // CONSTRUCT AND EXECUTE TX (Success) // -------------------------------------------------------------------------------------------- @@ -66,9 +58,7 @@ fn prove_receive_asset_via_wallet() { .input_notes(vec![note]) .build(); - let mut executor = - TransactionExecutor::new(tx_context.clone(), Some(target_falcon_auth.clone())); - executor.load_account(target_account.id()).unwrap(); + let executor = TransactionExecutor::new(tx_context.clone(), Some(target_falcon_auth.clone())); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -78,9 +68,8 @@ fn prove_receive_asset_via_wallet() { .map(|note| note.id()) .collect::>(); - let tx_script_code = ProgramAst::parse(DEFAULT_AUTH_SCRIPT).unwrap(); - let tx_script = executor.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); - let tx_args: TransactionArgs = TransactionArgs::with_tx_script(tx_script); + let tx_script = build_default_auth_script(); + let tx_args = TransactionArgs::with_tx_script(tx_script); // Execute the transaction and get the witness let executed_transaction = executor @@ -127,9 +116,7 @@ fn prove_send_asset_via_wallet() { // -------------------------------------------------------------------------------------------- let tx_context = TransactionContextBuilder::new(sender_account.clone()).build(); - let mut executor = - TransactionExecutor::new(tx_context.clone(), Some(sender_falcon_auth.clone())); - executor.load_account(sender_account.id()).unwrap(); + let executor = TransactionExecutor::new(tx_context.clone(), Some(sender_falcon_auth.clone())); let block_ref = tx_context.tx_inputs().block_header().block_num(); let note_ids = tx_context @@ -142,34 +129,31 @@ fn prove_send_asset_via_wallet() { let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; let aux = Felt::new(27); let tag = NoteTag::for_local_use_case(0, 0).unwrap(); - let note_type = NoteType::OffChain; + let note_type = NoteType::Private; assert_eq!(tag.validate(note_type), Ok(tag)); - let var_name = &format!( + let tx_script_src = &format!( " - use.miden::contracts::auth::basic->auth_tx - use.miden::contracts::wallets::basic->wallet - begin push.{recipient} + push.{note_execution_hint} push.{note_type} push.{aux} push.{tag} push.{asset} - call.wallet::send_asset + call.::miden::contracts::wallets::basic::send_asset dropw dropw dropw dropw - call.auth_tx::auth_tx_rpo_falcon512 + call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 end ", recipient = prepare_word(&recipient), note_type = note_type as u8, tag = tag, - asset = prepare_word(&fungible_asset_1.into()) + asset = prepare_word(&fungible_asset_1.into()), + note_execution_hint = Felt::from(NoteExecutionHint::always()) ); - let tx_script_code = ProgramAst::parse(var_name.as_str()).unwrap(); - let tx_script = executor.compile_tx_script(tx_script_code, vec![], vec![]).unwrap(); - let tx_args: TransactionArgs = TransactionArgs::with_tx_script(tx_script); + let tx_args = build_tx_args_from_script(tx_script_src); let executed_transaction = executor .execute_transaction(sender_account.id(), block_ref, ¬e_ids, tx_args) @@ -223,13 +207,13 @@ fn wallet_creation() { // sender_account_id not relevant here, just to create a default account code let sender_account_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - let expected_code_root = + let expected_code_commitment = get_account_with_default_account_code(sender_account_id, pub_key.into(), None) .code() - .root(); + .commitment(); assert!(wallet.is_regular_account()); - assert_eq!(wallet.code().root(), expected_code_root); + assert_eq!(wallet.code().commitment(), expected_code_commitment); let pub_key_word: Word = pub_key.into(); assert_eq!(wallet.storage().get_item(0).as_elements(), pub_key_word); } diff --git a/objects/Cargo.toml b/objects/Cargo.toml index 7e740003e..4965ea737 100644 --- a/objects/Cargo.toml +++ b/objects/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-objects" -version = "0.4.0" +version = "0.5.0" description = "Core components of the Miden rollup" readme = "README.md" categories = ["no-std"] @@ -35,9 +35,10 @@ rand = { workspace = true, optional = true } serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] } vm-core = { workspace = true } vm-processor = { workspace = true } -winter-rand-utils = { version = "0.8", optional = true } +winter-rand-utils = { version = "0.9", optional = true } [dev-dependencies] criterion = { version = "0.5", default-features = false, features = ["html_reports"] } -miden-objects = { path = ".", features = ["testing"] } -tempfile = { version = "3.0" } +miden-objects = { workspace = true, features = ["testing"] } +rstest = { version = "0.22" } +tempfile = { version = "3.12" } diff --git a/objects/README.md b/objects/README.md index 3959e5096..e1ac52e0c 100644 --- a/objects/README.md +++ b/objects/README.md @@ -22,7 +22,7 @@ Structures used to define fungible and non-fungible assets. Accounts own assets ### Block -Structures used to define a block. These objects contains authentication structures, merkle trees, used to represent the state of the rollup at a given point in time. +Structures used to define a block. These objects contain authentication structures, merkle trees, used to represent the state of the rollup at a given point in time. ### Notes diff --git a/objects/src/accounts/account_id.rs b/objects/src/accounts/account_id.rs index ed9a93cc0..1ec7be108 100644 --- a/objects/src/accounts/account_id.rs +++ b/objects/src/accounts/account_id.rs @@ -105,7 +105,8 @@ pub enum AccountStorageType { pub struct AccountId(Felt); impl AccountId { - /// Specifies a minimum number of trailing zeros required in the last element of the seed digest. + /// Specifies a minimum number of trailing zeros required in the last element of the seed + /// digest. /// /// Note: The account id includes 4 bits of metadata, these bits determine the account type /// (normal account, fungible token, non-fungible token), the storage type (on/off chain), and @@ -126,25 +127,28 @@ impl AccountId { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new account ID derived from the specified seed, code root and storage root. + /// Returns a new account ID derived from the specified seed, code commitment and storage root. /// - /// The account ID is computed by hashing the seed, code root and storage root and using 1 + /// The account ID is computed by hashing the seed, code commitment and storage root and using 1 /// element of the resulting digest to form the ID. Specifically we take element 0. We also /// require that the last element of the seed digest has at least `23` trailing zeros if it /// is a regular account, or `31` trailing zeros if it is a faucet account. /// /// The seed digest is computed using a sequential hash over - /// hash(SEED, CODE_ROOT, STORAGE_ROOT, ZERO). This takes two permutations. + /// hash(SEED, CODE_COMMITMENT, STORAGE_ROOT, ZERO). This takes two permutations. /// /// # Errors /// Returns an error if the resulting account ID does not comply with account ID rules: /// - the metadata embedded in the ID (i.e., the first 4 bits) is valid. /// - the ID has at least `5` ones. - /// - the last element of the seed digest has at least `23` trailing zeros for regular - /// accounts. + /// - the last element of the seed digest has at least `23` trailing zeros for regular accounts. /// - the last element of the seed digest has at least `31` trailing zeros for faucet accounts. - pub fn new(seed: Word, code_root: Digest, storage_root: Digest) -> Result { - let seed_digest = compute_digest(seed, code_root, storage_root); + pub fn new( + seed: Word, + code_commitment: Digest, + storage_root: Digest, + ) -> Result { + let seed_digest = compute_digest(seed, code_commitment, storage_root); Self::validate_seed_digest(&seed_digest)?; seed_digest[0].try_into() @@ -161,19 +165,19 @@ impl AccountId { /// Creates a new dummy [AccountId] for testing purposes. #[cfg(any(feature = "testing", test))] pub fn new_dummy(init_seed: [u8; 32], account_type: AccountType) -> Self { - let code_root = Digest::default(); + let code_commitment = Digest::default(); let storage_root = Digest::default(); let seed = get_account_seed( init_seed, account_type, AccountStorageType::OnChain, - code_root, + code_commitment, storage_root, ) .unwrap(); - Self::new(seed, code_root, storage_root).unwrap() + Self::new(seed, code_commitment, storage_root).unwrap() } // PUBLIC ACCESSORS @@ -218,10 +222,10 @@ impl AccountId { init_seed: [u8; 32], account_type: AccountType, storage_type: AccountStorageType, - code_root: Digest, + code_commitment: Digest, storage_root: Digest, ) -> Result { - get_account_seed(init_seed, account_type, storage_type, code_root, storage_root) + get_account_seed(init_seed, account_type, storage_type, code_commitment, storage_root) } /// Creates an Account Id from a hex string. Assumes the string starts with "0x" and @@ -230,8 +234,8 @@ impl AccountId { hex_to_bytes(hex_value) .map_err(|err| AccountError::HexParseError(err.to_string())) .and_then(|mut bytes: [u8; 8]| { - // `bytes` ends up being parsed as felt, and the input to that is assumed to be little-endian - // so we need to reverse the order + // `bytes` ends up being parsed as felt, and the input to that is assumed to be + // little-endian so we need to reverse the order bytes.reverse(); bytes.try_into() }) @@ -399,12 +403,12 @@ fn parse_felt(bytes: &[u8]) -> Result { Felt::try_from(bytes).map_err(|err| AccountError::AccountIdInvalidFieldElement(err.to_string())) } -/// Returns the digest of two hashing permutations over the seed, code root, storage root and +/// Returns the digest of two hashing permutations over the seed, code commitment, storage root and /// padding. -pub(super) fn compute_digest(seed: Word, code_root: Digest, storage_root: Digest) -> Digest { +pub(super) fn compute_digest(seed: Word, code_commitment: Digest, storage_root: Digest) -> Digest { let mut elements = Vec::with_capacity(16); elements.extend(seed); - elements.extend(*code_root); + elements.extend(*code_commitment); elements.extend(*storage_root); elements.resize(16, ZERO); Hasher::hash_elements(&elements) diff --git a/objects/src/accounts/code.rs b/objects/src/accounts/code.rs deleted file mode 100644 index 0f43ee76e..000000000 --- a/objects/src/accounts/code.rs +++ /dev/null @@ -1,215 +0,0 @@ -use alloc::vec::Vec; - -use assembly::ast::AstSerdeOptions; - -use super::{ - AccountError, Assembler, AssemblyContext, ByteReader, ByteWriter, Deserializable, - DeserializationError, Digest, ModuleAst, Serializable, -}; -use crate::crypto::merkle::SimpleSmt; - -// CONSTANTS -// ================================================================================================ - -/// Default serialization options for account code AST. -const MODULE_SERDE_OPTIONS: AstSerdeOptions = AstSerdeOptions::new(false); - -/// The depth of the Merkle tree that is used to commit to the account's public interface. -pub const PROCEDURE_TREE_DEPTH: u8 = 8; - -// ACCOUNT CODE -// ================================================================================================ - -/// A public interface of an account. -/// -/// Account's public interface consists of a set of account procedures, each procedure being a Miden -/// VM program. Thus, MAST root of each procedure commits to the underlying program. We commit to -/// the entire account interface by building a simple Merkle tree out of all procedure MAST roots. -#[derive(Debug, Clone)] -pub struct AccountCode { - module: ModuleAst, - procedures: Vec, - procedure_tree: SimpleSmt, -} - -impl AccountCode { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - - /// The depth of the Merkle tree that is used to commit to the account's public interface. - pub const PROCEDURE_TREE_DEPTH: u8 = PROCEDURE_TREE_DEPTH; - - /// The maximum number of account interface procedures. - pub const MAX_NUM_PROCEDURES: usize = 2_usize.pow(Self::PROCEDURE_TREE_DEPTH as u32); - - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new definition of an account's interface compiled from the specified source code. - /// - /// # Errors - /// Returns an error if: - /// - Compilation of the provided module fails. - /// - The number of procedures exported from the provided module is smaller than 1 or greater - /// than 256. - pub fn new(module: ModuleAst, assembler: &Assembler) -> Result { - // compile the module and make sure the number of exported procedures is within the limit - let procedures = assembler - .compile_module(&module, None, &mut AssemblyContext::for_module(false)) - .map_err(AccountError::AccountCodeAssemblerError)?; - - // make sure the number of procedures is between 1 and 256 (both inclusive) - if procedures.is_empty() { - return Err(AccountError::AccountCodeNoProcedures); - } else if procedures.len() > Self::MAX_NUM_PROCEDURES { - return Err(AccountError::AccountCodeTooManyProcedures { - max: Self::MAX_NUM_PROCEDURES, - actual: procedures.len(), - }); - } - - Ok(Self { - procedure_tree: build_procedure_tree(&procedures), - module, - procedures, - }) - } - - /// Returns a new definition of an account's interface instantiated from the provided - /// module and list of procedure digests. - /// - /// **Note**: this function assumes that the list of provided procedure digests results from - /// the compilation of the provided module, but this is not checked. - /// - /// # Panics - /// Panics if the number of procedures is smaller than 1 or greater than 256. - pub fn from_parts(module: ModuleAst, procedures: Vec) -> Self { - assert!(!procedures.is_empty(), "no account procedures"); - assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures"); - Self { - procedure_tree: build_procedure_tree(&procedures), - module, - procedures, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a commitment to an account's public interface. - pub fn root(&self) -> Digest { - self.procedure_tree().root() - } - - /// Returns a reference to the [ModuleAst] backing the [AccountCode]. - pub fn module(&self) -> &ModuleAst { - &self.module - } - - /// Returns a reference to the account procedure digests. - pub fn procedures(&self) -> &[Digest] { - &self.procedures - } - - /// Returns a reference to the procedure tree. - pub fn procedure_tree(&self) -> &SimpleSmt { - &self.procedure_tree - } - - /// Returns the number of public interface procedures defined for this account. - pub fn num_procedures(&self) -> usize { - self.procedures.len() - } - - /// Returns true if a procedure with the specified root is defined for this account. - pub fn has_procedure(&self, root: Digest) -> bool { - self.procedures.contains(&root) - } - - /// Returns a procedure digest for the procedure with the specified index. - /// - /// # Panics - /// Panics if the provided index is out of bounds. - pub fn get_procedure_by_index(&self, index: usize) -> Digest { - self.procedures[index] - } - - /// Returns the procedure index for the procedure with the specified root or None if such - /// procedure is not defined for this account. - pub fn get_procedure_index_by_root(&self, root: Digest) -> Option { - self.procedures.iter().position(|r| r == &root) - } -} - -// EQUALITY -// ================================================================================================ - -impl PartialEq for AccountCode { - fn eq(&self, other: &Self) -> bool { - // TODO: consider checking equality based only on the set of procedures - self.module == other.module && self.procedures == other.procedures - } -} - -impl Eq for AccountCode {} - -// SERIALIZATION -// ================================================================================================ - -impl Serializable for AccountCode { - fn write_into(&self, target: &mut W) { - // debug info (this includes module imports and source locations) is not serialized with account code - self.module.write_into(target, MODULE_SERDE_OPTIONS); - // since the number of procedures is guaranteed to be between 1 and 256, we can store the - // number as a single byte - but we do have to subtract 1 to store 256 as 255. - target.write_u8((self.procedures.len() - 1) as u8); - target.write_many(self.procedures()); - } -} - -impl Deserializable for AccountCode { - fn read_from(source: &mut R) -> Result { - // debug info (this includes module imports and source locations) is not serialized with account code - let module = ModuleAst::read_from(source, MODULE_SERDE_OPTIONS)?; - let num_procedures = (source.read_u8()? as usize) + 1; - let procedures = source.read_many::(num_procedures)?; - - Ok(Self::from_parts(module, procedures)) - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -fn build_procedure_tree(procedures: &[Digest]) -> SimpleSmt { - // order the procedure digests to achieve a reproducible tree - let procedures = { - let mut procedures = procedures.to_vec(); - procedures.sort_by_key(|a| a.as_bytes()); - procedures - }; - - SimpleSmt::::with_leaves( - procedures - .iter() - .enumerate() - .map(|(idx, p)| (idx as u64, p.into())) - .collect::>(), - ) - .expect("failed to build procedure tree") -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - use super::{AccountCode, Deserializable, Serializable}; - - #[test] - fn test_serde() { - let code = AccountCode::mock(); - let serialized = code.to_bytes(); - let deserialized = AccountCode::read_from_bytes(&serialized).unwrap(); - assert_eq!(deserialized, code) - } -} diff --git a/objects/src/accounts/code/mod.rs b/objects/src/accounts/code/mod.rs new file mode 100644 index 000000000..8f649dacc --- /dev/null +++ b/objects/src/accounts/code/mod.rs @@ -0,0 +1,270 @@ +use alloc::{string::ToString, sync::Arc, vec::Vec}; + +use assembly::{Assembler, Compile, Library}; +use vm_core::mast::MastForest; + +use super::{ + AccountError, ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, Felt, + Hasher, Serializable, +}; + +pub mod procedure; +use procedure::AccountProcedureInfo; + +// ACCOUNT CODE +// ================================================================================================ + +/// A public interface of an account. +/// +/// Account's public interface consists of a set of account procedures, each procedure being a +/// Miden VM program. Thus, MAST root of each procedure commits to the underlying program. +/// +/// Each exported procedure is associated with a storage offset. This offset is applied to any +/// accesses made from within the procedure to the associated account's storage. For example, if +/// storage offset for a procedure is set ot 1, a call to the account::get_item(storage_slot=4) +/// made from this procedure would actually access storage slot with index 5. +/// +/// We commit to the entire account interface by building a sequential hash of all procedure MAST +/// roots and associated storage_offset's. Specifically, each procedure contributes exactly 8 field +/// elements to the sequence of elements to be hashed. These elements are defined as follows: +/// +/// ```text +/// [PROCEDURE_MAST_ROOT, storage_offset, 0, 0, 0] +/// ``` +#[derive(Debug, Clone)] +pub struct AccountCode { + mast: Arc, + procedures: Vec, + commitment: Digest, +} + +impl AccountCode { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The maximum number of account interface procedures. + pub const MAX_NUM_PROCEDURES: usize = 256; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Returns a new [AccountCode] instantiated from the provided [Library]. + /// + /// All procedures exported from the provided library will become members of the account's + /// public interface. + /// + /// # Errors + /// Returns an error if the number of procedures exported from the provided library is smaller + /// than 1 or greater than 256. + pub fn new(library: Library) -> Result { + // extract procedure information from the library exports + // TODO: currently, offsets for all procedures are set to 0; instead they should be read + // from the Library metadata + let mut procedures: Vec = Vec::new(); + for module in library.module_infos() { + for proc_mast_root in module.procedure_digests() { + procedures.push(AccountProcedureInfo::new(proc_mast_root, 0)); + } + } + + // make sure the number of procedures is between 1 and 256 (both inclusive) + if procedures.is_empty() { + return Err(AccountError::AccountCodeNoProcedures); + } else if procedures.len() > Self::MAX_NUM_PROCEDURES { + return Err(AccountError::AccountCodeTooManyProcedures { + max: Self::MAX_NUM_PROCEDURES, + actual: procedures.len(), + }); + } + + Ok(Self { + commitment: build_procedure_commitment(&procedures), + procedures, + mast: Arc::new(library.into()), + }) + } + + /// Returns a new [AccountCode] compiled from the provided source code using the specified + /// assembler. + /// + /// All procedures exported from the provided code will become members of the account's + /// public interface. + /// + /// # Errors + /// Returns an error if: + /// - Compilation of the provided source code fails. + /// - The number of procedures exported from the provided library is smaller than 1 or greater + /// than 256. + pub fn compile(source_code: impl Compile, assembler: Assembler) -> Result { + let library = assembler + .assemble_library([source_code]) + .map_err(|report| AccountError::AccountCodeAssemblyError(report.to_string()))?; + + Self::new(library) + } + + /// Returns a new [AccountCode] deserialized from the provided bytes. + /// + /// # Errors + /// Returns an error if account code deserialization fails. + pub fn from_bytes(bytes: &[u8]) -> Result { + Self::read_from_bytes(bytes).map_err(AccountError::AccountCodeDeserializationError) + } + + /// Returns a new definition of an account's interface instantiated from the provided + /// [MastForest] and a list of [AccountProcedureInfo]s. + /// + /// # Panics + /// Panics if: + /// - The number of procedures is smaller than 1 or greater than 256. + /// - If some any of the provided procedures does not have a corresponding root in the provided + /// MAST forest. + pub fn from_parts(mast: Arc, procedures: Vec) -> Self { + assert!(!procedures.is_empty(), "no account procedures"); + assert!(procedures.len() <= Self::MAX_NUM_PROCEDURES, "too many account procedures"); + + Self { + commitment: build_procedure_commitment(&procedures), + procedures, + mast, + } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a commitment to an account's public interface. + pub fn commitment(&self) -> Digest { + self.commitment + } + + /// Returns a reference to the [MastForest] backing this account code. + pub fn mast(&self) -> Arc { + self.mast.clone() + } + + /// Returns a reference to the account procedures. + pub fn procedures(&self) -> &[AccountProcedureInfo] { + &self.procedures + } + + /// Returns an iterator over the procedure MAST roots of this account code. + pub fn procedure_roots(&self) -> impl Iterator + '_ { + self.procedures().iter().map(|procedure| *procedure.mast_root()) + } + + /// Returns the number of public interface procedures defined in this account code. + pub fn num_procedures(&self) -> usize { + self.procedures.len() + } + + /// Returns true if a procedure with the specified MAST root is defined in this account code. + pub fn has_procedure(&self, mast_root: Digest) -> bool { + self.procedures.iter().any(|procedure| procedure.mast_root() == &mast_root) + } + + /// Returns information about the procedure at the specified index. + /// + /// # Panics + /// Panics if the provided index is out of bounds. + pub fn get_procedure_by_index(&self, index: usize) -> &AccountProcedureInfo { + &self.procedures[index] + } + + /// Returns the procedure index for the procedure with the specified MAST root or None if such + /// procedure is not defined in this [AccountCode]. + pub fn get_procedure_index_by_root(&self, root: Digest) -> Option { + self.procedures + .iter() + .map(|procedure| procedure.mast_root()) + .position(|r| r == &root) + } + + /// Converts procedure information in this [AccountCode] into a vector of field elements. + /// + /// This is done by first converting each procedure into exactly 8 elements as follows: + /// ```text + /// [PROCEDURE_MAST_ROOT, storage_offset, 0, 0, 0] + /// ``` + /// And then concatenating the resulting elements into a single vector. + pub fn as_elements(&self) -> Vec { + procedures_as_elements(self.procedures()) + } +} + +// EQUALITY +// ================================================================================================ + +impl PartialEq for AccountCode { + fn eq(&self, other: &Self) -> bool { + // TODO: consider checking equality based only on the set of procedures + self.mast == other.mast && self.procedures == other.procedures + } +} + +impl Eq for AccountCode {} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for AccountCode { + fn write_into(&self, target: &mut W) { + self.mast.write_into(target); + // since the number of procedures is guaranteed to be between 1 and 256, we can store the + // number as a single byte - but we do have to subtract 1 to store 256 as 255. + target.write_u8((self.procedures.len() - 1) as u8); + target.write_many(self.procedures()); + } +} + +impl Deserializable for AccountCode { + fn read_from(source: &mut R) -> Result { + let module = Arc::new(MastForest::read_from(source)?); + let num_procedures = (source.read_u8()? as usize) + 1; + let procedures = source.read_many::(num_procedures)?; + + Ok(Self::from_parts(module, procedures)) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Converts given procedures into field elements +fn procedures_as_elements(procedures: &[AccountProcedureInfo]) -> Vec { + procedures + .iter() + .flat_map(|procedure| <[Felt; 8]>::from(procedure.clone())) + .collect() +} + +/// Computes the commitment to the given procedures +fn build_procedure_commitment(procedures: &[AccountProcedureInfo]) -> Digest { + let elements = procedures_as_elements(procedures); + Hasher::hash_elements(&elements) +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + + use super::{AccountCode, Deserializable, Serializable}; + use crate::accounts::code::build_procedure_commitment; + + #[test] + fn test_serde() { + let code = AccountCode::mock(); + let serialized = code.to_bytes(); + let deserialized = AccountCode::read_from_bytes(&serialized).unwrap(); + assert_eq!(deserialized, code) + } + + #[test] + fn test_account_code_procedure_commitment() { + let code = AccountCode::mock(); + let procedure_commitment = build_procedure_commitment(code.procedures()); + assert_eq!(procedure_commitment, code.commitment()) + } +} diff --git a/objects/src/accounts/code/procedure.rs b/objects/src/accounts/code/procedure.rs new file mode 100644 index 000000000..bbe30347f --- /dev/null +++ b/objects/src/accounts/code/procedure.rs @@ -0,0 +1,114 @@ +use vm_core::{ + utils::{ByteReader, ByteWriter, Deserializable, Serializable}, + FieldElement, +}; +use vm_processor::DeserializationError; + +use super::{Digest, Felt}; +use crate::AccountError; + +// ACCOUNT PROCEDURE INFO +// ================================================================================================ + +/// Information about a procedure exposed in a public account interface. +/// +/// The info included the MAST root of the procedure and the storage offset applied to all account +/// storage-related accesses made by this procedure. For example, if storage offset is set ot 1, a +/// call to the account::get_item(storage_slot=4) made from this procedure would actually access +/// storage slot with index 5. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct AccountProcedureInfo { + mast_root: Digest, + storage_offset: u16, +} + +impl AccountProcedureInfo { + /// The number of field elements needed to represent an [AccountProcedureInfo] in kernel memory. + pub const NUM_ELEMENTS_PER_PROC: usize = 8; + + /// Returns a new instance of an [AccountProcedureInfo]. + pub fn new(mast_root: Digest, storage_offset: u16) -> Self { + Self { mast_root, storage_offset } + } + + /// Returns a reference to the procedure's mast_root. + pub fn mast_root(&self) -> &Digest { + &self.mast_root + } + + /// Returns a reference to the procedure's storage_offset. + pub fn storage_offset(&self) -> u16 { + self.storage_offset + } +} + +impl From for [Felt; 8] { + fn from(value: AccountProcedureInfo) -> Self { + let mut result = [Felt::ZERO; 8]; + + // copy mast_root into first 4 elements + result[0..4].copy_from_slice(value.mast_root().as_elements()); + + // copy the storage offset into value[4] + result[4] = Felt::from(value.storage_offset()); + + result + } +} + +impl TryFrom<[Felt; 8]> for AccountProcedureInfo { + type Error = AccountError; + + fn try_from(value: [Felt; 8]) -> Result { + // get mast_root from first 4 elements + let mast_root = Digest::from(<[Felt; 4]>::try_from(&value[0..4]).unwrap()); + + // get storage_offset form value[4] + let storage_offset: u16 = value[4] + .try_into() + .map_err(|_| AccountError::AccountCodeProcedureInvalidStorageOffset)?; + + // Check if the last three elements are zero + if value[5..].iter().any(|&x| x != Felt::ZERO) { + return Err(AccountError::AccountCodeProcedureInvalidPadding); + } + + Ok(Self { mast_root, storage_offset }) + } +} + +impl Serializable for AccountProcedureInfo { + fn write_into(&self, target: &mut W) { + target.write(self.mast_root()); + target.write_u16(self.storage_offset()); + } +} + +impl Deserializable for AccountProcedureInfo { + fn read_from(source: &mut R) -> Result { + let mast_root: Digest = source.read()?; + let storage_offset = source.read_u16()?; + + Ok(Self::new(mast_root, storage_offset)) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_crypto::utils::{Deserializable, Serializable}; + + use crate::accounts::{AccountCode, AccountProcedureInfo}; + + #[test] + fn test_serde_account_procedure() { + let account_code = AccountCode::mock(); + + let serialized = account_code.procedures()[0].to_bytes(); + let deserialized = AccountProcedureInfo::read_from_bytes(&serialized).unwrap(); + + assert_eq!(deserialized, account_code.procedures()[0]); + } +} diff --git a/objects/src/accounts/delta/builder.rs b/objects/src/accounts/delta/builder.rs deleted file mode 100644 index 52b340a58..000000000 --- a/objects/src/accounts/delta/builder.rs +++ /dev/null @@ -1,57 +0,0 @@ -use alloc::vec::Vec; - -use super::{AccountStorageDelta, StorageMapDelta, Word}; -use crate::AccountDeltaError; - -#[derive(Clone, Debug, Default)] -pub struct AccountStorageDeltaBuilder { - pub cleared_items: Vec, - pub updated_items: Vec<(u8, Word)>, - pub updated_maps: Vec<(u8, StorageMapDelta)>, -} - -impl AccountStorageDeltaBuilder { - // CONSTRUCTORS - // ------------------------------------------------------------------------------------------- - pub fn new() -> Self { - Self::default() - } - - // MODIFIERS - // ------------------------------------------------------------------------------------------- - pub fn add_cleared_items(mut self, items: I) -> Self - where - I: IntoIterator, - { - self.cleared_items.extend(items); - self - } - - pub fn add_updated_items(mut self, items: I) -> Self - where - I: IntoIterator, - { - self.updated_items.extend(items); - self - } - - pub fn add_updated_maps(mut self, items: I) -> Self - where - I: IntoIterator, - { - self.updated_maps.extend(items); - self - } - - // BUILDERS - // ------------------------------------------------------------------------------------------- - pub fn build(self) -> Result { - let delta = AccountStorageDelta { - cleared_items: self.cleared_items, - updated_items: self.updated_items, - updated_maps: self.updated_maps, - }; - delta.validate()?; - Ok(delta) - } -} diff --git a/objects/src/accounts/delta/mod.rs b/objects/src/accounts/delta/mod.rs index c3b9e9739..8702fca9b 100644 --- a/objects/src/accounts/delta/mod.rs +++ b/objects/src/accounts/delta/mod.rs @@ -4,16 +4,15 @@ use super::{ Account, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, Serializable, Word, ZERO, }; -use crate::{assets::Asset, AccountDeltaError}; - -mod builder; -pub use builder::AccountStorageDeltaBuilder; +use crate::AccountDeltaError; mod storage; pub use storage::{AccountStorageDelta, StorageMapDelta}; mod vault; -pub use vault::AccountVaultDelta; +pub use vault::{ + AccountVaultDelta, FungibleAssetDelta, NonFungibleAssetDelta, NonFungibleDeltaAction, +}; // ACCOUNT DELTA // ================================================================================================ @@ -39,25 +38,34 @@ impl AccountDelta { /// Returns new [AccountDelta] instantiated from the provided components. /// /// # Errors - /// Returns an error if: - /// - Storage or vault deltas are invalid. - /// - Storage and vault deltas are empty, and the nonce was updated. - /// - Storage or vault deltas are not empty, but nonce was not updated. + /// Returns an error if storage or vault were updated, but the nonce was either not updated + /// or set to 0. pub fn new( storage: AccountStorageDelta, vault: AccountVaultDelta, nonce: Option, ) -> Result { - // make sure storage and vault deltas are valid - storage.validate()?; - vault.validate()?; - - // nonce must be updated if and only if either account storage or vault were updated + // nonce must be updated if either account storage or vault were updated validate_nonce(nonce, &storage, &vault)?; Ok(Self { storage, vault, nonce }) } + /// Merge another [AccountDelta] into this one. + pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> { + match (&mut self.nonce, other.nonce) { + (Some(old), Some(new)) if new.as_int() <= old.as_int() => { + return Err(AccountDeltaError::InconsistentNonceUpdate(format!( + "New nonce {new} is not larger than the old nonce {old}" + ))) + }, + // Incoming nonce takes precedence. + (old, new) => *old = new.or(*old), + }; + self.storage.merge(other.storage)?; + self.vault.merge(other.vault) + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -104,6 +112,36 @@ impl AccountUpdateDetails { pub fn is_private(&self) -> bool { matches!(self, Self::Private) } + + /// Merges the `other` update into this one. + /// + /// This account update is assumed to come before the other. + pub fn merge(self, other: AccountUpdateDetails) -> Result { + let merged_update = match (self, other) { + (AccountUpdateDetails::Private, AccountUpdateDetails::Private) => { + AccountUpdateDetails::Private + }, + (AccountUpdateDetails::New(mut account), AccountUpdateDetails::Delta(delta)) => { + account.apply_delta(&delta).map_err(|_| { + AccountDeltaError::IncompatibleAccountUpdates( + AccountUpdateDetails::New(account.clone()), + AccountUpdateDetails::Delta(delta), + ) + })?; + + AccountUpdateDetails::New(account) + }, + (AccountUpdateDetails::Delta(mut delta), AccountUpdateDetails::Delta(new_delta)) => { + delta.merge(new_delta)?; + AccountUpdateDetails::Delta(delta) + }, + (left, right) => { + return Err(AccountDeltaError::IncompatibleAccountUpdates(left, right)) + }, + }; + + Ok(merged_update) + } } // SERIALIZATION @@ -167,9 +205,8 @@ impl Deserializable for AccountUpdateDetails { /// Checks if the nonce was updated correctly given the provided storage and vault deltas. /// /// # Errors -/// Returns an error if: -/// - Storage or vault were updated, but the nonce was either not updated or set to 0. -/// - Storage and vault were not updated, but the nonce was updated. +/// Returns an error if storage or vault were updated, but the nonce was either not updated +/// or set to 0. fn validate_nonce( nonce: Option, storage: &AccountStorageDelta, @@ -190,10 +227,6 @@ fn validate_nonce( )) }, } - } else if nonce.is_some() { - return Err(AccountDeltaError::InconsistentNonceUpdate( - "nonce updated for empty delta".to_string(), - )); } Ok(()) @@ -210,26 +243,14 @@ mod tests { #[test] fn account_delta_nonce_validation() { // empty delta - let storage_delta = AccountStorageDelta { - cleared_items: vec![], - updated_items: vec![], - updated_maps: vec![], - }; - - let vault_delta = AccountVaultDelta { - added_assets: vec![], - removed_assets: vec![], - }; + let storage_delta = AccountStorageDelta::default(); + let vault_delta = AccountVaultDelta::default(); assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_ok()); - assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_err()); + assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ONE)).is_ok()); // non-empty delta - let storage_delta = AccountStorageDelta { - cleared_items: vec![1], - updated_items: vec![], - updated_maps: vec![], - }; + let storage_delta = AccountStorageDelta::from_iters([1], [], []); assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), None).is_err()); assert!(AccountDelta::new(storage_delta.clone(), vault_delta.clone(), Some(ZERO)).is_err()); diff --git a/objects/src/accounts/delta/storage.rs b/objects/src/accounts/delta/storage.rs index 4e43454aa..f9634031f 100644 --- a/objects/src/accounts/delta/storage.rs +++ b/objects/src/accounts/delta/storage.rs @@ -1,202 +1,174 @@ -use alloc::{string::ToString, vec::Vec}; +use alloc::{ + collections::{btree_map::Entry, BTreeMap}, + string::ToString, + vec::Vec, +}; + +use miden_crypto::EMPTY_WORD; use super::{ - AccountDeltaError, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, - Serializable, Word, + AccountDeltaError, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, + Word, }; +use crate::Digest; // CONSTANTS // ================================================================================================ -const MAX_MUTABLE_STORAGE_SLOT_IDX: u8 = 254; +const IMMUTABLE_STORAGE_SLOT: u8 = u8::MAX; // ACCOUNT STORAGE DELTA // ================================================================================================ /// [AccountStorageDelta] stores the differences between two states of account storage. /// -/// The differences are represented as follows: -/// - item updates: represented by `cleared_items` and `updated_items` field. +/// The delta consists of two maps: +/// - A map containing the updates to simple storage slots. The keys in this map are indexes of the +/// updated storage slots and the values are the new values for these slots. +/// - A map containing updates to storage maps. The keys in this map are also indexes of the updated +/// storage slots and the values are corresponding storage map delta objects. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct AccountStorageDelta { - pub cleared_items: Vec, - pub updated_items: Vec<(u8, Word)>, - pub updated_maps: Vec<(u8, StorageMapDelta)>, + slots: BTreeMap, + maps: BTreeMap, } impl AccountStorageDelta { - /// Creates an [AccountStorageDelta] from the given iterators. - #[cfg(test)] - fn from_iters(cleared_items: A, updated_items: B, updated_maps: C) -> Self - where - A: IntoIterator, - B: IntoIterator, - C: IntoIterator, - { - Self { - cleared_items: Vec::from_iter(cleared_items), - updated_items: Vec::from_iter(updated_items), - updated_maps: Vec::from_iter(updated_maps), - } + /// Creates a new storage delta from the provided fields. + pub fn new( + slots: BTreeMap, + maps: BTreeMap, + ) -> Result { + let result = Self { slots, maps }; + result.validate()?; + + Ok(result) } - /// Checks whether this storage delta is valid. - /// - /// # Errors - /// Returns an error if: - /// - The number of cleared or updated items is greater than 255. - /// - Any of cleared or updated items are at slot 255 (i.e., immutable slot). - /// - Any of the cleared or updated items is referenced more than once (e.g., updated twice). - /// - There is a storage map delta without a corresponding storage item update. - pub fn validate(&self) -> Result<(), AccountDeltaError> { - let num_cleared_items = self.cleared_items.len(); - let num_updated_items = self.updated_items.len(); - - if num_cleared_items > u8::MAX as usize { - return Err(AccountDeltaError::TooManyClearedStorageItems { - actual: num_cleared_items, - max: u8::MAX as usize, - }); - } else if num_updated_items > u8::MAX as usize { - return Err(AccountDeltaError::TooManyRemovedAssets { - actual: num_updated_items, - max: u8::MAX as usize, - }); - } + /// Returns a reference to the updated slots in this storage delta. + pub fn slots(&self) -> &BTreeMap { + &self.slots + } - // make sure cleared items vector does not contain errors - for (pos, &idx) in self.cleared_items.iter().enumerate() { - if idx > MAX_MUTABLE_STORAGE_SLOT_IDX { - return Err(AccountDeltaError::ImmutableStorageSlot(idx as usize)); - } + /// Returns a reference to the updated maps in this storage delta. + pub fn maps(&self) -> &BTreeMap { + &self.maps + } - if self.cleared_items[..pos].contains(&idx) { - return Err(AccountDeltaError::DuplicateStorageItemUpdate(idx as usize)); - } - } + /// Returns true if storage delta contains no updates. + pub fn is_empty(&self) -> bool { + self.slots.is_empty() && self.maps.is_empty() + } - // make sure updates items vector does not contain errors - for (pos, (idx, _)) in self.updated_items.iter().enumerate() { - if *idx > MAX_MUTABLE_STORAGE_SLOT_IDX { - return Err(AccountDeltaError::ImmutableStorageSlot(*idx as usize)); - } + /// Tracks a slot change + pub fn set_item(&mut self, slot_index: u8, new_slot_value: Word) { + self.slots.insert(slot_index, new_slot_value); + } - if self.cleared_items.contains(idx) { - return Err(AccountDeltaError::DuplicateStorageItemUpdate(*idx as usize)); - } + /// Tracks a map item change + pub fn set_map_item(&mut self, slot_index: u8, key: Digest, new_value: Word) { + self.maps.entry(slot_index).or_default().insert(key, new_value); + } + + /// Merges another delta into this one, overwriting any existing values. + pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> { + self.slots.extend(other.slots); - if self.updated_items[..pos].iter().any(|x| x.0 == *idx) { - return Err(AccountDeltaError::DuplicateStorageItemUpdate(*idx as usize)); + // merge maps + for (slot, update) in other.maps.into_iter() { + match self.maps.entry(slot) { + Entry::Vacant(entry) => { + entry.insert(update); + }, + Entry::Occupied(mut entry) => entry.get_mut().merge(update), } } - // make sure storage map deltas are valid - for (index, storage_map_delta) in self.updated_maps.iter() { - if index > &MAX_MUTABLE_STORAGE_SLOT_IDX { - return Err(AccountDeltaError::ImmutableStorageSlot(*index as usize)); - } - if !storage_map_delta.is_empty() { - storage_map_delta.validate()?; + self.validate() + } + + /// Checks whether this storage delta is valid. + /// + /// # Errors + /// Returns an error if: + /// - Any of updated items are at slot 255 (i.e., immutable slot). + /// - Any of the updated slot is referenced from both maps (e.g., updated twice). + fn validate(&self) -> Result<(), AccountDeltaError> { + if self.slots.contains_key(&IMMUTABLE_STORAGE_SLOT) + || self.maps.contains_key(&IMMUTABLE_STORAGE_SLOT) + { + return Err(AccountDeltaError::ImmutableStorageSlot(IMMUTABLE_STORAGE_SLOT as usize)); + } + + for slot in self.maps.keys() { + if self.slots.contains_key(slot) { + return Err(AccountDeltaError::DuplicateStorageItemUpdate(*slot as usize)); } } Ok(()) } +} - /// Returns true if storage delta contains no updates. - pub fn is_empty(&self) -> bool { - self.cleared_items.is_empty() - && self.updated_items.is_empty() - && self.updated_maps.is_empty() +#[cfg(any(feature = "testing", test))] +impl AccountStorageDelta { + /// Creates an [AccountStorageDelta] from the given iterators. + pub fn from_iters( + cleared_items: impl IntoIterator, + updated_items: impl IntoIterator, + updated_maps: impl IntoIterator, + ) -> Self { + Self { + slots: BTreeMap::from_iter( + cleared_items.into_iter().map(|key| (key, EMPTY_WORD)).chain(updated_items), + ), + maps: BTreeMap::from_iter(updated_maps), + } } } impl Serializable for AccountStorageDelta { fn write_into(&self, target: &mut W) { - assert!(self.cleared_items.len() <= u8::MAX as usize, "too many cleared storage items"); - target.write_u8(self.cleared_items.len() as u8); - target.write_many(self.cleared_items.iter()); + let cleared: Vec = self + .slots + .iter() + .filter(|&(_, value)| (value == &EMPTY_WORD)) + .map(|(slot, _)| *slot) + .collect(); + let updated: Vec<_> = + self.slots.iter().filter(|&(_, value)| value != &EMPTY_WORD).collect(); - assert!(self.updated_items.len() <= u8::MAX as usize, "too many updated storage items"); - target.write_u8(self.updated_items.len() as u8); - target.write_many(self.updated_items.iter()); + target.write_u8(cleared.len() as u8); + target.write_many(cleared.iter()); - assert!(self.updated_maps.len() <= u8::MAX as usize, "too many updated storage maps"); - target.write_u8(self.updated_maps.len() as u8); - target.write_many(self.updated_maps.iter()); + target.write_u8(updated.len() as u8); + target.write_many(updated.iter()); + + target.write_u8(self.maps.len() as u8); + target.write_many(self.maps.iter()); } } impl Deserializable for AccountStorageDelta { fn read_from(source: &mut R) -> Result { - // deserialize and validate cleared items + let mut slots = BTreeMap::new(); + let num_cleared_items = source.read_u8()? as usize; - let mut cleared_items = Vec::with_capacity(num_cleared_items); for _ in 0..num_cleared_items { - let idx = source.read_u8()?; - - // make sure index is valid - if idx > MAX_MUTABLE_STORAGE_SLOT_IDX { - return Err(DeserializationError::InvalidValue( - "immutable storage item cleared".to_string(), - )); - } - - // make sure the same item hasn't been cleared before - if cleared_items.contains(&idx) { - return Err(DeserializationError::InvalidValue( - "storage item cleared more than once".to_string(), - )); - } - - cleared_items.push(idx); + let cleared_slot = source.read_u8()?; + slots.insert(cleared_slot, EMPTY_WORD); } - // deserialize and validate updated items let num_updated_items = source.read_u8()? as usize; - let mut updated_items: Vec<(u8, Word)> = Vec::with_capacity(num_updated_items); for _ in 0..num_updated_items { - let idx = source.read_u8()?; - let value = Word::read_from(source)?; - - // make sure index is valid - if idx > MAX_MUTABLE_STORAGE_SLOT_IDX { - return Err(DeserializationError::InvalidValue( - "immutable storage item updated".to_string(), - )); - } - - // make sure the same item hasn't been updated before - if updated_items.iter().any(|x| x.0 == idx) { - return Err(DeserializationError::InvalidValue( - "storage item updated more than once".to_string(), - )); - } - - // make sure the storage item hasn't been cleared in the same delta - if cleared_items.contains(&idx) { - return Err(DeserializationError::InvalidValue( - "storage item both cleared and updated".to_string(), - )); - } - - updated_items.push((idx, value)); + let (updated_slot, updated_value) = source.read()?; + slots.insert(updated_slot, updated_value); } - // deserialize and validate storage map deltas - let num_updated_maps = source.read_u8()? as usize; - let mut updated_maps = Vec::with_capacity(num_updated_maps); - for _ in 0..num_updated_maps { - let idx = source.read_u8()?; - let value = StorageMapDelta::read_from(source)?; - updated_maps.push((idx, value)); - } + let num_maps = source.read_u8()? as usize; + let maps = source.read_many::<(u8, StorageMapDelta)>(num_maps)?.into_iter().collect(); - Ok(Self { - cleared_items, - updated_items, - updated_maps, - }) + Self::new(slots, maps).map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -205,82 +177,91 @@ impl Deserializable for AccountStorageDelta { /// [StorageMapDelta] stores the differences between two states of account storage maps. /// -/// The differences are represented as follows: -/// - leave updates: represented by `cleared_leaves` and `updated_leaves` field. +/// The differences are represented as leaf updates: a map of updated item key ([Digest]) to +/// value ([Word]). For cleared items the value is [EMPTY_WORD]. #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct StorageMapDelta { - pub cleared_leaves: Vec, - pub updated_leaves: Vec<(Word, Word)>, -} +pub struct StorageMapDelta(BTreeMap); impl StorageMapDelta { - /// Creates a new [StorageMapDelta] from the provided iteartors. - fn from_iters(cleared_leaves: A, updated_leaves: B) -> Self - where - A: IntoIterator, - B: IntoIterator, - { - Self { - cleared_leaves: Vec::from_iter(cleared_leaves), - updated_leaves: Vec::from_iter(updated_leaves), - } + /// Creates a new storage map delta from the provided leaves. + pub fn new(map: BTreeMap) -> Self { + Self(map) } - /// Creates a new storage map delta from the provided cleared and updated leaves. - pub fn from(cleared_leaves: Vec, updated_leaves: Vec<(Word, Word)>) -> Self { - Self::from_iters(cleared_leaves, updated_leaves) + /// Returns a reference to the updated leaves in this storage map delta. + pub fn leaves(&self) -> &BTreeMap { + &self.0 } - /// Checks whether this storage map delta is valid. - /// - /// # Errors - /// Returns an error if: - /// - Any of the cleared or updated leaves is referenced more than once (e.g., updated twice). - pub fn validate(&self) -> Result<(), AccountDeltaError> { - // we add all keys to a single vector and sort them to check for duplicates - // we don't use a hash set because we want to use no-std compatible code - let mut all_keys: Vec> = self - .cleared_leaves - .iter() - .chain(self.updated_leaves.iter().map(|(key, _)| key)) - .map(|x| x.iter().map(|x| x.as_int()).collect::>()) - .collect(); - - all_keys.sort_unstable(); - - if let Some(key) = all_keys.windows(2).find(|els| els[0] == els[1]) { - let mut iter = key[0].iter().map(|&x| Felt::new(x)); - // we know that the key is 4 elements long - let digest = Word::from([ - iter.next().unwrap(), - iter.next().unwrap(), - iter.next().unwrap(), - iter.next().unwrap(), - ]); - return Err(AccountDeltaError::DuplicateStorageMapLeaf { key: digest.into() }); - } - - Ok(()) + /// Inserts an item into the storage map delta. + pub fn insert(&mut self, key: Digest, value: Word) { + self.0.insert(key, value); } /// Returns true if storage map delta contains no updates. pub fn is_empty(&self) -> bool { - self.cleared_leaves.is_empty() && self.updated_leaves.is_empty() + self.0.is_empty() + } + + /// Merge `other` into this delta, giving precedence to `other`. + pub fn merge(&mut self, other: Self) { + // Aggregate the changes into a map such that `other` overwrites self. + self.0.extend(other.0); + } +} + +#[cfg(any(feature = "testing", test))] +impl StorageMapDelta { + /// Creates a new [StorageMapDelta] from the provided iterators. + pub fn from_iters( + cleared_leaves: impl IntoIterator, + updated_leaves: impl IntoIterator, + ) -> Self { + Self(BTreeMap::from_iter( + cleared_leaves + .into_iter() + .map(|key| (key.into(), EMPTY_WORD)) + .chain(updated_leaves.into_iter().map(|(key, value)| (key.into(), value))), + )) } } impl Serializable for StorageMapDelta { fn write_into(&self, target: &mut W) { - self.cleared_leaves.write_into(target); - self.updated_leaves.write_into(target); + let cleared: Vec<&Digest> = self + .0 + .iter() + .filter(|&(_, value)| value == &EMPTY_WORD) + .map(|(key, _)| key) + .collect(); + + let updated: Vec<_> = self.0.iter().filter(|&(_, value)| value != &EMPTY_WORD).collect(); + + target.write_usize(cleared.len()); + target.write_many(cleared.iter()); + + target.write_usize(updated.len()); + target.write_many(updated.iter()); } } impl Deserializable for StorageMapDelta { fn read_from(source: &mut R) -> Result { - let cleared_leaves = Vec::<_>::read_from(source)?; - let updated_leaves = Vec::<_>::read_from(source)?; - Ok(Self { cleared_leaves, updated_leaves }) + let mut map = BTreeMap::new(); + + let cleared_count = source.read_usize()?; + for _ in 0..cleared_count { + let cleared_key = source.read()?; + map.insert(cleared_key, EMPTY_WORD); + } + + let updated_count = source.read_usize()?; + for _ in 0..updated_count { + let (updated_key, updated_value) = source.read()?; + map.insert(updated_key, updated_value); + } + + Ok(Self::new(map)) } } @@ -290,7 +271,9 @@ impl Deserializable for StorageMapDelta { #[cfg(test)] mod tests { use super::{AccountStorageDelta, Deserializable, Serializable}; - use crate::{accounts::StorageMapDelta, ONE, ZERO}; + use crate::{ + accounts::StorageMapDelta, testing::storage::AccountStorageDeltaBuilder, ONE, ZERO, + }; #[test] fn account_storage_delta_validation() { @@ -311,10 +294,6 @@ mod tests { let bytes = delta.to_bytes(); assert!(AccountStorageDelta::read_from_bytes(&bytes).is_err()); - // duplicate in cleared items - let delta = AccountStorageDelta::from_iters([1, 2, 1], [], []); - assert!(delta.validate().is_err()); - let bytes = delta.to_bytes(); assert!(AccountStorageDelta::read_from_bytes(&bytes).is_err()); @@ -329,26 +308,25 @@ mod tests { let bytes = delta.to_bytes(); assert!(AccountStorageDelta::read_from_bytes(&bytes).is_err()); - // duplicate in updated items + let bytes = delta.to_bytes(); + assert!(AccountStorageDelta::read_from_bytes(&bytes).is_err()); + + // duplicate across cleared items and maps let delta = AccountStorageDelta::from_iters( - [], - [ - (4, [ONE, ONE, ONE, ONE]), - (5, [ONE, ONE, ONE, ZERO]), - (4, [ONE, ONE, ZERO, ZERO]), - ], - [], + [1, 2, 3], + [(2, [ONE, ONE, ONE, ONE]), (5, [ONE, ONE, ONE, ZERO])], + [(1, StorageMapDelta::default())], ); assert!(delta.validate().is_err()); let bytes = delta.to_bytes(); assert!(AccountStorageDelta::read_from_bytes(&bytes).is_err()); - // duplicate across cleared and updated items + // duplicate across updated items and maps let delta = AccountStorageDelta::from_iters( - [1, 2, 3], + [1, 3], [(2, [ONE, ONE, ONE, ONE]), (5, [ONE, ONE, ONE, ZERO])], - [], + [(2, StorageMapDelta::default())], ); assert!(delta.validate().is_err()); @@ -367,17 +345,8 @@ mod tests { let storage_delta = AccountStorageDelta::from_iters([], [(2, [ONE, ONE, ONE, ONE])], []); assert!(!storage_delta.is_empty()); - let storage_delta = AccountStorageDelta::from_iters( - [], - [], - [( - 3, - StorageMapDelta { - cleared_leaves: vec![], - updated_leaves: vec![], - }, - )], - ); + let storage_delta = + AccountStorageDelta::from_iters([], [], [(3, StorageMapDelta::default())]); assert!(!storage_delta.is_empty()); } @@ -398,17 +367,8 @@ mod tests { let deserialized = AccountStorageDelta::read_from_bytes(&serialized).unwrap(); assert_eq!(deserialized, storage_delta); - let storage_delta = AccountStorageDelta::from_iters( - [], - [], - [( - 3, - StorageMapDelta { - cleared_leaves: vec![], - updated_leaves: vec![], - }, - )], - ); + let storage_delta = + AccountStorageDelta::from_iters([], [], [(3, StorageMapDelta::default())]); let serialized = storage_delta.to_bytes(); let deserialized = AccountStorageDelta::read_from_bytes(&serialized).unwrap(); assert_eq!(deserialized, storage_delta); @@ -432,4 +392,57 @@ mod tests { let deserialized = StorageMapDelta::read_from_bytes(&serialized).unwrap(); assert_eq!(deserialized, storage_map_delta); } + + #[rstest::rstest] + #[case::some_some(Some(1), Some(2), Some(2))] + #[case::none_some(None, Some(2), Some(2))] + #[case::some_none(Some(1), None, None)] + #[test] + fn merge_items(#[case] x: Option, #[case] y: Option, #[case] expected: Option) { + /// Creates a delta containing the item as an update if Some, else with the item cleared. + fn create_delta(item: Option) -> AccountStorageDelta { + const SLOT: u8 = 123; + let item = item.map(|x| (SLOT, [vm_core::Felt::new(x), ZERO, ZERO, ZERO])); + + AccountStorageDeltaBuilder::default() + .add_cleared_items(item.is_none().then_some(SLOT)) + .add_updated_items(item) + .build() + .unwrap() + } + + let mut delta_x = create_delta(x); + let delta_y = create_delta(y); + let expected = create_delta(expected); + + delta_x.merge(delta_y).unwrap(); + + assert_eq!(delta_x, expected); + } + + #[rstest::rstest] + #[case::some_some(Some(1), Some(2), Some(2))] + #[case::none_some(None, Some(2), Some(2))] + #[case::some_none(Some(1), None, None)] + #[test] + fn merge_maps(#[case] x: Option, #[case] y: Option, #[case] expected: Option) { + fn create_delta(value: Option) -> StorageMapDelta { + let key = [vm_core::Felt::new(10), ZERO, ZERO, ZERO]; + match value { + Some(value) => StorageMapDelta::from_iters( + [], + [(key, [vm_core::Felt::new(value), ZERO, ZERO, ZERO])], + ), + None => StorageMapDelta::from_iters([key], []), + } + } + + let mut delta_x = create_delta(x); + let delta_y = create_delta(y); + let expected = create_delta(expected); + + delta_x.merge(delta_y); + + assert_eq!(delta_x, expected); + } } diff --git a/objects/src/accounts/delta/vault.rs b/objects/src/accounts/delta/vault.rs index 6b50561d5..74ca0d267 100644 --- a/objects/src/accounts/delta/vault.rs +++ b/objects/src/accounts/delta/vault.rs @@ -1,8 +1,15 @@ -use alloc::{string::ToString, vec::Vec}; +use alloc::{ + collections::{btree_map::Entry, BTreeMap}, + string::ToString, + vec::Vec, +}; use super::{ - AccountDeltaError, Asset, ByteReader, ByteWriter, Deserializable, DeserializationError, - Serializable, + AccountDeltaError, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, +}; +use crate::{ + accounts::{AccountId, AccountType}, + assets::{Asset, FungibleAsset, NonFungibleAsset}, }; // ACCOUNT VAULT DELTA @@ -11,221 +18,450 @@ use super::{ /// [AccountVaultDelta] stores the difference between the initial and final account vault states. /// /// The difference is represented as follows: -/// - added_assets: a vector of assets that were added to the account vault. -/// - removed_assets: a vector of assets that were removed from the account vault. +/// - fungible: a binary tree map of fungible asset balance changes in the account vault. +/// - non_fungible: a binary tree map of non-fungible assets that were added to or removed from the +/// account vault. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct AccountVaultDelta { - pub added_assets: Vec, - pub removed_assets: Vec, + fungible: FungibleAssetDelta, + non_fungible: NonFungibleAssetDelta, } impl AccountVaultDelta { - /// Creates an empty [AccountVaultDelta]. - pub fn empty() -> Self { - Self::default() + /// Validates and creates an [AccountVaultDelta] with the given fungible and non-fungible asset + /// deltas. + /// + /// # Errors + /// Returns an error if the delta does not pass the validation. + pub const fn new(fungible: FungibleAssetDelta, non_fungible: NonFungibleAssetDelta) -> Self { + Self { fungible, non_fungible } } - /// Creates an [AccountVaultDelta] from the given iterators. - pub fn from_iterators(added_assets: A, removed_assets: B) -> Self - where - A: IntoIterator, - B: IntoIterator, - { - Self { - added_assets: Vec::from_iter(added_assets), - removed_assets: Vec::from_iter(removed_assets), + /// Returns a reference to the fungible asset delta. + pub fn fungible(&self) -> &FungibleAssetDelta { + &self.fungible + } + + /// Returns a reference to the non-fungible asset delta. + pub fn non_fungible(&self) -> &NonFungibleAssetDelta { + &self.non_fungible + } + + /// Returns true if this vault delta contains no updates. + pub fn is_empty(&self) -> bool { + self.fungible.is_empty() && self.non_fungible.is_empty() + } + + /// Tracks asset addition. + pub fn add_asset(&mut self, asset: Asset) -> Result<(), AccountDeltaError> { + match asset { + Asset::Fungible(asset) => self.fungible.add(asset), + Asset::NonFungible(asset) => self.non_fungible.add(asset), } } - /// Checks whether this vault delta is valid. + /// Tracks asset removal. + pub fn remove_asset(&mut self, asset: Asset) -> Result<(), AccountDeltaError> { + match asset { + Asset::Fungible(asset) => self.fungible.remove(asset), + Asset::NonFungible(asset) => self.non_fungible.remove(asset), + } + } + + /// Merges another delta into this one, overwriting any existing values. + /// + /// The result is validated as part of the merge. /// /// # Errors - /// Returns an error if: - /// - The number added assets is greater than [u16::MAX]. - /// - The number of removed assets is greater than [u16::MAX]. - /// - The same asset was added more than once, removed more than once, or both added and - /// removed. - pub fn validate(&self) -> Result<(), AccountDeltaError> { - if self.added_assets.len() > u16::MAX as usize { - return Err(AccountDeltaError::TooManyAddedAsset { - actual: self.added_assets.len(), - max: u16::MAX as usize, - }); - } else if self.removed_assets.len() > u16::MAX as usize { - return Err(AccountDeltaError::TooManyRemovedAssets { - actual: self.removed_assets.len(), - max: u16::MAX as usize, - }); - } + /// Returns an error if the resulted delta does not pass the validation. + pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> { + self.non_fungible.merge(other.non_fungible)?; + self.fungible.merge(other.fungible) + } +} - // make sure all added assets are unique - for (pos, asset) in self.added_assets.iter().enumerate() { - if self.added_assets[..pos].iter().any(|a| a.is_same(asset)) { - return Err(AccountDeltaError::DuplicateVaultUpdate(*asset)); +#[cfg(any(feature = "testing", test))] +impl AccountVaultDelta { + /// Creates an [AccountVaultDelta] from the given iterators. + pub fn from_iters( + added_assets: impl IntoIterator, + removed_assets: impl IntoIterator, + ) -> Self { + use crate::assets::Asset; + + let mut fungible = FungibleAssetDelta::default(); + let mut non_fungible = NonFungibleAssetDelta::default(); + + for asset in added_assets { + match asset { + Asset::Fungible(asset) => { + fungible.add(asset).unwrap(); + }, + Asset::NonFungible(asset) => { + non_fungible.add(asset).unwrap(); + }, } } - // make sure all removed assets are the same - for (pos, asset) in self.removed_assets.iter().enumerate() { - if self.removed_assets[..pos].iter().any(|a| a.is_same(asset)) { - return Err(AccountDeltaError::DuplicateVaultUpdate(*asset)); + for asset in removed_assets { + match asset { + Asset::Fungible(asset) => { + fungible.remove(asset).unwrap(); + }, + Asset::NonFungible(asset) => { + non_fungible.remove(asset).unwrap(); + }, } + } + + Self { fungible, non_fungible } + } + + /// Returns an iterator over the added assets in this delta. + pub fn added_assets(&self) -> impl Iterator + '_ { + use crate::assets::{Asset, FungibleAsset, NonFungibleAsset}; + self.fungible + .0 + .iter() + .filter(|&(_, &value)| value >= 0) + .map(|(&faucet_id, &diff)| { + Asset::Fungible(FungibleAsset::new(faucet_id, diff.unsigned_abs()).unwrap()) + }) + .chain(self.non_fungible.filter_by_action(NonFungibleDeltaAction::Add).map(|key| { + Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.into()) }) + })) + } + + /// Returns an iterator over the removed assets in this delta. + pub fn removed_assets(&self) -> impl Iterator + '_ { + use crate::assets::{Asset, FungibleAsset, NonFungibleAsset}; + self.fungible + .0 + .iter() + .filter(|&(_, &value)| value < 0) + .map(|(&faucet_id, &diff)| { + Asset::Fungible(FungibleAsset::new(faucet_id, diff.unsigned_abs()).unwrap()) + }) + .chain(self.non_fungible.filter_by_action(NonFungibleDeltaAction::Remove).map(|key| { + Asset::NonFungible(unsafe { NonFungibleAsset::new_unchecked(key.into()) }) + })) + } +} + +impl Serializable for AccountVaultDelta { + fn write_into(&self, target: &mut W) { + target.write(&self.fungible); + target.write(&self.non_fungible); + } +} + +impl Deserializable for AccountVaultDelta { + fn read_from(source: &mut R) -> Result { + let fungible = source.read()?; + let non_fungible = source.read()?; + + Ok(Self::new(fungible, non_fungible)) + } +} + +// FUNGIBLE ASSET DELTA +// ================================================================================================ + +/// A binary tree map of fungible asset balance changes in the account vault. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct FungibleAssetDelta(BTreeMap); + +impl FungibleAssetDelta { + /// Validates and creates a new fungible asset delta. + /// + /// # Errors + /// Returns an error if the delta does not pass the validation. + pub fn new(map: BTreeMap) -> Result { + let delta = Self(map); + delta.validate()?; + + Ok(delta) + } + + /// Adds a new fungible asset to the delta. + /// + /// # Errors + /// Returns an error if the delta would overflow. + pub fn add(&mut self, asset: FungibleAsset) -> Result<(), AccountDeltaError> { + let amount: i64 = asset.amount().try_into().expect("Amount it too high"); + self.add_delta(asset.faucet_id(), amount) + } + + /// Removes a fungible asset from the delta. + /// + /// # Errors + /// Returns an error if the delta would overflow. + pub fn remove(&mut self, asset: FungibleAsset) -> Result<(), AccountDeltaError> { + let amount: i64 = asset.amount().try_into().expect("Amount it too high"); + self.add_delta(asset.faucet_id(), -amount) + } + + /// Returns true if this vault delta contains no updates. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns an iterator over the (key, value) pairs of the map. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + /// Merges another delta into this one, overwriting any existing values. + /// + /// The result is validated as part of the merge. + /// + /// # Errors + /// Returns an error if the result did not pass validation. + pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> { + // Merge fungible assets. + // + // Track fungible asset amounts - positive and negative. `i64` is not lossy while + // fungibles are restricted to 2^63-1. Overflow is still possible but we check for that. + + for (&faucet_id, &amount) in other.0.iter() { + self.add_delta(faucet_id, amount)?; + } + + Ok(()) + } + + // HELPER FUNCTIONS + // --------------------------------------------------------------------------------------------- + + /// Updates the provided map with the provided key and amount. If the final amount is 0, + /// the entry is removed. + /// + /// # Errors + /// Returns an error if the delta would overflow. + fn add_delta(&mut self, faucet_id: AccountId, delta: i64) -> Result<(), AccountDeltaError> { + match self.0.entry(faucet_id) { + Entry::Vacant(entry) => { + entry.insert(delta); + }, + Entry::Occupied(mut entry) => { + let old = *entry.get(); + let new = old.checked_add(delta).ok_or( + AccountDeltaError::FungibleAssetDeltaOverflow { + faucet_id, + this: old, + other: delta, + }, + )?; + + if new == 0 { + entry.remove(); + } else { + *entry.get_mut() = new; + } + }, + } + + Ok(()) + } - if self.added_assets.iter().any(|a| a.is_same(asset)) { - return Err(AccountDeltaError::DuplicateVaultUpdate(*asset)); + /// Checks whether this vault delta is valid. + /// + /// # Errors + /// Returns an error if one or more fungible assets' faucet IDs are invalid. + fn validate(&self) -> Result<(), AccountDeltaError> { + for faucet_id in self.0.keys() { + if !matches!(faucet_id.account_type(), AccountType::FungibleFaucet) { + return Err(AccountDeltaError::NotAFungibleFaucetId(*faucet_id)); } } Ok(()) } +} + +impl Serializable for FungibleAssetDelta { + fn write_into(&self, target: &mut W) { + target.write_usize(self.0.len()); + // TODO: We save `i64` as `u64` since winter utils only support unsigned integers for now. + // We should update this code (and deserialization as well) once it support signed + // integers. + target.write_many(self.0.iter().map(|(&faucet_id, &delta)| (faucet_id, delta as u64))); + } +} + +impl Deserializable for FungibleAssetDelta { + fn read_from(source: &mut R) -> Result { + let num_fungible_assets = source.read_usize()?; + // TODO: We save `i64` as `u64` since winter utils only support unsigned integers for now. + // We should update this code (and serialization as well) once it support signed integers. + let map = source + .read_many::<(AccountId, u64)>(num_fungible_assets)? + .into_iter() + .map(|(account_id, delta_as_u64)| (account_id, delta_as_u64 as i64)) + .collect(); + + Self::new(map).map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + +// NON-FUNGIBLE ASSET DELTA +// ================================================================================================ + +/// A binary tree map of non-fungible asset changes (addition and removal) in the account vault. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct NonFungibleAssetDelta(BTreeMap); + +impl NonFungibleAssetDelta { + /// Creates a new non-fungible asset delta. + pub const fn new(map: BTreeMap) -> Self { + Self(map) + } + + /// Adds a new non-fungible asset to the delta. + /// + /// # Errors + /// Returns an error if the delta already contains the asset addition. + pub fn add(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> { + self.apply_action(asset, NonFungibleDeltaAction::Add) + } + + /// Removes a non-fungible asset from the delta. + /// + /// # Errors + /// Returns an error if the delta already contains the asset removal. + pub fn remove(&mut self, asset: NonFungibleAsset) -> Result<(), AccountDeltaError> { + self.apply_action(asset, NonFungibleDeltaAction::Remove) + } /// Returns true if this vault delta contains no updates. pub fn is_empty(&self) -> bool { - self.added_assets.is_empty() && self.removed_assets.is_empty() + self.0.is_empty() + } + + /// Returns an iterator over the (key, value) pairs of the map. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + /// Merges another delta into this one, overwriting any existing values. + /// + /// The result is validated as part of the merge. + /// + /// # Errors + /// Returns an error if duplicate non-fungible assets are added or removed. + pub fn merge(&mut self, other: Self) -> Result<(), AccountDeltaError> { + // Merge non-fungible assets. Each non-fungible asset can cancel others out. + for (&key, &action) in other.0.iter() { + self.apply_action(key, action)?; + } + + Ok(()) + } + + // HELPER FUNCTIONS + // --------------------------------------------------------------------------------------------- + + /// Updates the provided map with the provided key and action. + /// If the action is the opposite to the previous one, the entry is removed. + /// + /// # Errors + /// Returns an error if the delta already contains the provided key and action. + fn apply_action( + &mut self, + asset: NonFungibleAsset, + action: NonFungibleDeltaAction, + ) -> Result<(), AccountDeltaError> { + match self.0.entry(asset) { + Entry::Vacant(entry) => { + entry.insert(action); + }, + Entry::Occupied(entry) => { + let previous = *entry.get(); + if previous == action { + // Asset cannot be added nor removed twice. + return Err(AccountDeltaError::DuplicateNonFungibleVaultUpdate(asset)); + } + // Otherwise they cancel out. + entry.remove(); + }, + } + + Ok(()) + } + + /// Returns an iterator over all keys that have the provided action. + fn filter_by_action( + &self, + action: NonFungibleDeltaAction, + ) -> impl Iterator + '_ { + self.0 + .iter() + .filter(move |&(_, cur_action)| cur_action == &action) + .map(|(key, _)| *key) } } -impl Serializable for AccountVaultDelta { +impl Serializable for NonFungibleAssetDelta { fn write_into(&self, target: &mut W) { - assert!(self.added_assets.len() <= u16::MAX as usize, "too many added assets"); - target.write_u16(self.added_assets.len() as u16); - target.write_many(self.added_assets.iter()); + let added: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Add).collect(); + let removed: Vec<_> = self.filter_by_action(NonFungibleDeltaAction::Remove).collect(); - assert!(self.removed_assets.len() <= u16::MAX as usize, "too many removed assets"); - target.write_u16(self.removed_assets.len() as u16); - target.write_many(self.removed_assets.iter()); + target.write_usize(added.len()); + target.write_many(added.iter()); + + target.write_usize(removed.len()); + target.write_many(removed.iter()); } } -impl Deserializable for AccountVaultDelta { +impl Deserializable for NonFungibleAssetDelta { fn read_from(source: &mut R) -> Result { - // deserialize and validate added assets - let num_added_assets = source.read_u16()? as usize; - let mut added_assets: Vec = Vec::with_capacity(num_added_assets); - for _ in 0..num_added_assets { - let asset = Asset::read_from(source)?; - if added_assets.iter().any(|a| a.is_same(&asset)) { - return Err(DeserializationError::InvalidValue( - "asset added more than once".to_string(), - )); - } + let mut map = BTreeMap::new(); - added_assets.push(asset); + let num_added = source.read_usize()?; + for _ in 0..num_added { + let added_asset = source.read()?; + map.insert(added_asset, NonFungibleDeltaAction::Add); } - // deserialize and validate removed assets - let num_removed_assets = source.read_u16()? as usize; - let mut removed_assets: Vec = Vec::with_capacity(num_removed_assets); - for _ in 0..num_removed_assets { - let asset = Asset::read_from(source)?; - - if removed_assets.iter().any(|a| a.is_same(&asset)) { - return Err(DeserializationError::InvalidValue( - "asset added more than once".to_string(), - )); - } - - if added_assets.iter().any(|a| a.is_same(&asset)) { - return Err(DeserializationError::InvalidValue( - "asset both added and removed".to_string(), - )); - } - removed_assets.push(asset); + let num_removed = source.read_usize()?; + for _ in 0..num_removed { + let removed_asset = source.read()?; + map.insert(removed_asset, NonFungibleDeltaAction::Remove); } - Ok(Self { added_assets, removed_assets }) + Ok(Self::new(map)) } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum NonFungibleDeltaAction { + Add, + Remove, +} + // TESTS // ================================================================================================ #[cfg(test)] mod tests { - use super::{AccountVaultDelta, Asset, Deserializable, Serializable}; + use super::{AccountVaultDelta, Deserializable, Serializable}; use crate::{ accounts::{ account_id::testing::{ ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, + ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, }, AccountId, }, - assets::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}, + assets::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}, testing::storage::build_assets, }; - #[test] - fn account_vault_delta_validation() { - let ffid1 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); - let ffid2 = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let nffid1 = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); - let nffid2 = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - - let asset1: Asset = FungibleAsset::new(ffid1, 10).unwrap().into(); - let asset2: Asset = FungibleAsset::new(ffid1, 30).unwrap().into(); - let asset3: Asset = FungibleAsset::new(ffid2, 20).unwrap().into(); - - let asset4: Asset = - NonFungibleAsset::new(&NonFungibleAssetDetails::new(nffid1, vec![1, 2, 3]).unwrap()) - .unwrap() - .into(); - let asset5: Asset = - NonFungibleAsset::new(&NonFungibleAssetDetails::new(nffid1, vec![4, 5, 6]).unwrap()) - .unwrap() - .into(); - let asset6: Asset = - NonFungibleAsset::new(&NonFungibleAssetDetails::new(nffid2, vec![7, 8, 9]).unwrap()) - .unwrap() - .into(); - - assert_eq!(asset5, Asset::read_from_bytes(&asset5.to_bytes()).unwrap()); - - // control case - let delta = AccountVaultDelta { - added_assets: vec![asset1, asset4, asset5], - removed_assets: vec![asset3, asset6], - }; - assert!(delta.validate().is_ok()); - - let bytes = delta.to_bytes(); - assert_eq!(AccountVaultDelta::read_from_bytes(&bytes), Ok(delta)); - - // duplicate asset in added assets - let delta = AccountVaultDelta { - added_assets: vec![asset1, asset4, asset5, asset2], - removed_assets: vec![], - }; - assert!(delta.validate().is_err()); - - let bytes = delta.to_bytes(); - assert!(AccountVaultDelta::read_from_bytes(&bytes).is_err()); - - // duplicate asset in removed assets - let delta = AccountVaultDelta { - added_assets: vec![], - removed_assets: vec![asset1, asset4, asset5, asset2], - }; - assert!(delta.validate().is_err()); - - let bytes = delta.to_bytes(); - assert!(AccountVaultDelta::read_from_bytes(&bytes).is_err()); - - // duplicate asset across added and removed assets - let delta = AccountVaultDelta { - added_assets: vec![asset1, asset3], - removed_assets: vec![asset4, asset5, asset2], - }; - assert!(delta.validate().is_err()); - - let bytes = delta.to_bytes(); - assert!(AccountVaultDelta::read_from_bytes(&bytes).is_err()); - } - #[test] fn test_serde_account_vault() { let (asset_0, asset_1) = build_assets(); - let delta = AccountVaultDelta::from_iterators([asset_0], [asset_1]); + let delta = AccountVaultDelta::from_iters([asset_0], [asset_1]); let serialized = delta.to_bytes(); let deserialized = AccountVaultDelta::read_from_bytes(&serialized).unwrap(); @@ -237,8 +473,93 @@ mod tests { let faucet = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); let asset: Asset = FungibleAsset::new(faucet, 123).unwrap().into(); - assert!(AccountVaultDelta::empty().is_empty()); - assert!(!AccountVaultDelta::from_iterators([asset], []).is_empty()); - assert!(!AccountVaultDelta::from_iterators([], [asset]).is_empty()); + assert!(AccountVaultDelta::default().is_empty()); + assert!(!AccountVaultDelta::from_iters([asset], []).is_empty()); + assert!(!AccountVaultDelta::from_iters([], [asset]).is_empty()); + } + + #[rstest::rstest] + #[case::pos_pos(50, 50, Some(100))] + #[case::neg_neg(-50, -50, Some(-100))] + #[case::empty_pos(0, 50, Some(50))] + #[case::empty_neg(0, -50, Some(-50))] + #[case::nullify_pos_neg(100, -100, Some(0))] + #[case::nullify_neg_pos(-100, 100, Some(0))] + #[case::overflow(FungibleAsset::MAX_AMOUNT as i64, FungibleAsset::MAX_AMOUNT as i64, None)] + #[case::underflow(-(FungibleAsset::MAX_AMOUNT as i64), -(FungibleAsset::MAX_AMOUNT as i64), None)] + #[test] + fn merge_fungible_aggregation(#[case] x: i64, #[case] y: i64, #[case] expected: Option) { + /// Creates an [AccountVaultDelta] with a single [FungibleAsset] delta. This delta will + /// be added if `amount > 0`, removed if `amount < 0` or entirely missing if `amount == 0`. + fn create_delta_with_fungible(account_id: AccountId, amount: i64) -> AccountVaultDelta { + let asset = FungibleAsset::new(account_id, amount.unsigned_abs()).unwrap().into(); + match amount { + 0 => AccountVaultDelta::default(), + x if x.is_positive() => AccountVaultDelta::from_iters([asset], []), + _ => AccountVaultDelta::from_iters([], [asset]), + } + } + + let account_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); + + let mut delta_x = create_delta_with_fungible(account_id, x); + let delta_y = create_delta_with_fungible(account_id, y); + + let result = delta_x.merge(delta_y); + + // None is used to indicate an error is expected. + if let Some(expected) = expected { + let expected = create_delta_with_fungible(account_id, expected); + assert_eq!(result.map(|_| delta_x).unwrap(), expected); + } else { + assert!(result.is_err()); + } + } + + #[rstest::rstest] + #[case::empty_removed(None, Some(false), Ok(Some(false)))] + #[case::empty_added(None, Some(true), Ok(Some(true)))] + #[case::add_remove(Some(true), Some(false), Ok(None))] + #[case::remove_add(Some(false), Some(true), Ok(None))] + #[case::double_add(Some(true), Some(true), Err(()))] + #[case::double_remove(Some(false), Some(false), Err(()))] + #[test] + fn merge_non_fungible_aggregation( + #[case] x: Option, + #[case] y: Option, + #[case] expected: Result, ()>, + ) { + /// Creates an [AccountVaultDelta] with an optional [NonFungibleAsset] delta. This delta + /// will be added if `Some(true)`, removed for `Some(false)` and missing for `None`. + fn create_delta_with_non_fungible( + account_id: AccountId, + added: Option, + ) -> AccountVaultDelta { + let asset: Asset = NonFungibleAsset::new( + &NonFungibleAssetDetails::new(account_id, vec![1, 2, 3]).unwrap(), + ) + .unwrap() + .into(); + + match added { + Some(true) => AccountVaultDelta::from_iters([asset], []), + Some(false) => AccountVaultDelta::from_iters([], [asset]), + None => AccountVaultDelta::default(), + } + } + + let account_id = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN).unwrap(); + + let mut delta_x = create_delta_with_non_fungible(account_id, x); + let delta_y = create_delta_with_non_fungible(account_id, y); + + let result = delta_x.merge(delta_y); + + if let Ok(expected) = expected { + let expected = create_delta_with_non_fungible(account_id, expected); + assert_eq!(result.map(|_| delta_x).unwrap(), expected); + } else { + assert!(result.is_err()); + } } } diff --git a/objects/src/accounts/mod.rs b/objects/src/accounts/mod.rs index 731bcbe53..2038a2617 100644 --- a/objects/src/accounts/mod.rs +++ b/objects/src/accounts/mod.rs @@ -1,5 +1,4 @@ use crate::{ - assembly::{Assembler, AssemblyContext, ModuleAst}, assets::AssetVault, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, AccountError, Digest, Felt, Hasher, Word, ZERO, @@ -15,10 +14,13 @@ pub mod auth; pub use auth::AuthSecretKey; pub mod code; -pub use code::AccountCode; +pub use code::{procedure::AccountProcedureInfo, AccountCode}; pub mod delta; -pub use delta::{AccountDelta, AccountStorageDelta, AccountVaultDelta, StorageMapDelta}; +pub use delta::{ + AccountDelta, AccountStorageDelta, AccountVaultDelta, FungibleAssetDelta, + NonFungibleAssetDelta, NonFungibleDeltaAction, StorageMapDelta, +}; mod seed; pub use seed::{get_account_seed, get_account_seed_single}; @@ -75,7 +77,7 @@ impl Account { code: AccountCode, storage: AccountStorage, ) -> Result { - let id = AccountId::new(seed, code.root(), storage.root())?; + let id = AccountId::new(seed, code.commitment(), storage.root())?; let vault = AssetVault::default(); let nonce = ZERO; Ok(Self { id, vault, storage, code, nonce }) @@ -97,15 +99,16 @@ impl Account { /// Returns hash of this account. /// - /// Hash of an account is computed as hash(id, nonce, vault_root, storage_root, code_root). - /// Computing the account hash requires 2 permutations of the hash function. + /// Hash of an account is computed as hash(id, nonce, vault_root, storage_root, + /// code_commitment). Computing the account hash requires 2 permutations of the hash + /// function. pub fn hash(&self) -> Digest { hash_account( self.id, self.nonce, self.vault.commitment(), self.storage.root(), - self.code.root(), + self.code.commitment(), ) } @@ -189,15 +192,11 @@ impl Account { /// - The nonce specified in the provided delta smaller than or equal to the current account /// nonce. pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), AccountError> { - // update vault; we don't check vault delta validity here because AccountDelta can contain + // update vault; we don't check vault delta validity here because `AccountDelta` can contain // only valid vault deltas - for &asset in delta.vault().added_assets.iter() { - self.vault.add_asset(asset).map_err(AccountError::AssetVaultUpdateError)?; - } - - for &asset in delta.vault().removed_assets.iter() { - self.vault.remove_asset(asset).map_err(AccountError::AssetVaultUpdateError)?; - } + self.vault + .apply_delta(delta.vault()) + .map_err(AccountError::AssetVaultUpdateError)?; // update storage self.storage.apply_delta(delta.storage())?; @@ -287,23 +286,24 @@ impl<'de> serde::Deserialize<'de> for Account { // HELPERS // ================================================================================================ -/// Returns hash of an account with the specified ID, nonce, vault root, storage root, and code root. +/// Returns hash of an account with the specified ID, nonce, vault root, storage root, and code +/// commitment. /// -/// Hash of an account is computed as hash(id, nonce, vault_root, storage_root, code_root). +/// Hash of an account is computed as hash(id, nonce, vault_root, storage_root, code_commitment). /// Computing the account hash requires 2 permutations of the hash function. pub fn hash_account( id: AccountId, nonce: Felt, vault_root: Digest, storage_root: Digest, - code_root: Digest, + code_commitment: Digest, ) -> Digest { let mut elements = [ZERO; 16]; elements[0] = id.into(); elements[3] = nonce; elements[4..8].copy_from_slice(&*vault_root); elements[8..12].copy_from_slice(&*storage_root); - elements[12..].copy_from_slice(&*code_root); + elements[12..].copy_from_slice(&*code_commitment); Hasher::hash_elements(&elements) } @@ -322,10 +322,10 @@ mod tests { use super::{AccountDelta, AccountStorageDelta, AccountVaultDelta}; use crate::{ - accounts::{ - delta::AccountStorageDeltaBuilder, Account, SlotItem, StorageMap, StorageMapDelta, + accounts::{Account, SlotItem, StorageMap, StorageMapDelta}, + testing::storage::{ + build_account, build_account_delta, build_assets, AccountStorageDeltaBuilder, }, - testing::storage::{build_account, build_account_delta, build_assets}, }; #[test] @@ -345,7 +345,7 @@ mod tests { fn test_serde_account_delta() { let final_nonce = Felt::new(2); let (asset_0, asset_1) = build_assets(); - let storage_delta = AccountStorageDeltaBuilder::new() + let storage_delta = AccountStorageDeltaBuilder::default() .add_cleared_items([0]) .add_updated_items([(1_u8, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])]) .build() @@ -396,13 +396,13 @@ mod tests { [Felt::new(9_u64), Felt::new(10_u64), Felt::new(11_u64), Felt::new(12_u64)], ); let updated_map = - StorageMapDelta::from(vec![], vec![(new_map_entry.0.into(), new_map_entry.1)]); + StorageMapDelta::from_iters([], [(new_map_entry.0.into(), new_map_entry.1)]); storage_map.insert(new_map_entry.0, new_map_entry.1); maps.insert(2u8, storage_map.clone()); // build account delta let final_nonce = Felt::new(2); - let storage_delta = AccountStorageDeltaBuilder::new() + let storage_delta = AccountStorageDeltaBuilder::default() .add_cleared_items([0]) .add_updated_items([(1_u8, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])]) .add_updated_maps([(2_u8, updated_map)]) @@ -443,7 +443,7 @@ mod tests { ); // build account delta - let storage_delta = AccountStorageDeltaBuilder::new() + let storage_delta = AccountStorageDeltaBuilder::default() .add_cleared_items([0]) .add_updated_items([(1_u8, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])]) .build() @@ -469,7 +469,7 @@ mod tests { // build account delta let final_nonce = Felt::new(1); - let storage_delta = AccountStorageDeltaBuilder::new() + let storage_delta = AccountStorageDeltaBuilder::default() .add_cleared_items([0]) .add_updated_items([(1_u8, [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)])]) .build() @@ -481,7 +481,6 @@ mod tests { } #[test] - #[should_panic] fn empty_account_delta_with_incremented_nonce() { // build account let init_nonce = Felt::new(1); diff --git a/objects/src/accounts/seed.rs b/objects/src/accounts/seed.rs index bdacc1634..eaa465617 100644 --- a/objects/src/accounts/seed.rs +++ b/objects/src/accounts/seed.rs @@ -23,7 +23,7 @@ pub fn get_account_seed( init_seed: [u8; 32], account_type: AccountType, storage_type: AccountStorageType, - code_root: Digest, + code_commitment: Digest, storage_root: Digest, ) -> Result { let thread_count = thread::available_parallelism().map_or(1, |v| v.get()); @@ -43,7 +43,7 @@ pub fn get_account_seed( init_seed, account_type, storage_type, - code_root, + code_commitment, storage_root, ) }); @@ -73,7 +73,7 @@ pub fn get_account_seed_inner( init_seed: [u8; 32], account_type: AccountType, storage_type: AccountStorageType, - code_root: Digest, + code_commitment: Digest, storage_root: Digest, ) { let init_seed: Vec<[u8; 8]> = @@ -84,7 +84,7 @@ pub fn get_account_seed_inner( Felt::new(u64::from_le_bytes(init_seed[2])), Felt::new(u64::from_le_bytes(init_seed[3])), ]; - let mut current_digest = compute_digest(current_seed, code_root, storage_root); + let mut current_digest = compute_digest(current_seed, code_commitment, storage_root); #[cfg(feature = "log")] let mut log = log::Log::start(current_digest, current_seed, account_type, storage_type); @@ -116,7 +116,7 @@ pub fn get_account_seed_inner( } } current_seed = current_digest.into(); - current_digest = compute_digest(current_seed, code_root, storage_root); + current_digest = compute_digest(current_seed, code_commitment, storage_root); } } @@ -125,10 +125,10 @@ pub fn get_account_seed( init_seed: [u8; 32], account_type: AccountType, storage_type: AccountStorageType, - code_root: Digest, + code_commitment: Digest, storage_root: Digest, ) -> Result { - get_account_seed_single(init_seed, account_type, storage_type, code_root, storage_root) + get_account_seed_single(init_seed, account_type, storage_type, code_commitment, storage_root) } /// Finds and returns a seed suitable for creating an account ID for the specified account type @@ -137,7 +137,7 @@ pub fn get_account_seed_single( init_seed: [u8; 32], account_type: AccountType, storage_type: AccountStorageType, - code_root: Digest, + code_commitment: Digest, storage_root: Digest, ) -> Result { let init_seed: Vec<[u8; 8]> = @@ -148,7 +148,7 @@ pub fn get_account_seed_single( Felt::new(u64::from_le_bytes(init_seed[2])), Felt::new(u64::from_le_bytes(init_seed[3])), ]; - let mut current_digest = compute_digest(current_seed, code_root, storage_root); + let mut current_digest = compute_digest(current_seed, code_commitment, storage_root); #[cfg(feature = "log")] let mut log = log::Log::start(current_digest, current_seed, account_type, storage_type); @@ -172,7 +172,7 @@ pub fn get_account_seed_single( } } current_seed = current_digest.into(); - current_digest = compute_digest(current_seed, code_root, storage_root); + current_digest = compute_digest(current_seed, code_commitment, storage_root); } } diff --git a/objects/src/accounts/storage/map.rs b/objects/src/accounts/storage/map.rs index 99c64b35d..4617aa462 100644 --- a/objects/src/accounts/storage/map.rs +++ b/objects/src/accounts/storage/map.rs @@ -8,7 +8,6 @@ use crate::{ hash::rpo::RpoDigest, merkle::{InnerNodeInfo, LeafIndex, Smt, SmtLeaf, SmtProof, SMT_DEPTH}, }, - EMPTY_WORD, }; // ACCOUNT STORAGE MAP @@ -107,22 +106,13 @@ impl StorageMap { } /// Applies the provided delta to this account storage. - /// - /// This method assumes that the delta has been validated by the calling method and so, no - /// additional validation of delta is performed. - pub fn apply_delta(&mut self, delta: &StorageMapDelta) -> Result { - // apply the updated leaves to the storage map - for &(key, value) in delta.updated_leaves.iter() { - self.insert(key.into(), value); - } - - // apply the cleared leaves to the storage map - // currently we cannot remove leaves from the storage map, so we just set them to empty - for &key in delta.cleared_leaves.iter() { - self.insert(key.into(), EMPTY_WORD); + pub fn apply_delta(&mut self, delta: &StorageMapDelta) -> Digest { + // apply the updated and cleared leaves to the storage map + for (&key, &value) in delta.leaves().iter() { + self.insert(key, value); } - Ok(self.root()) + self.root() } } diff --git a/objects/src/accounts/storage/mod.rs b/objects/src/accounts/storage/mod.rs index 838536640..50609819a 100644 --- a/objects/src/accounts/storage/mod.rs +++ b/objects/src/accounts/storage/mod.rs @@ -265,21 +265,16 @@ impl AccountStorage { /// Applies the provided delta to this account storage. /// - /// This method assumes that the delta has been validated by the calling method and so, no - /// additional validation of delta is performed. - /// - /// Returns an error if: - /// - The delta implies an update to a reserved account slot. - /// - The updates violate storage layout constraints. - /// - The updated value has an arity different from 0. + /// # Errors + /// Returns an error if the updates violate storage layout constraints. pub(super) fn apply_delta(&mut self, delta: &AccountStorageDelta) -> Result<(), AccountError> { // --- update storage maps -------------------------------------------- - for &(slot_idx, ref map_delta) in delta.updated_maps.iter() { + for (&slot_idx, map_delta) in delta.maps().iter() { let storage_map = self.maps.get_mut(&slot_idx).ok_or(AccountError::StorageMapNotFound(slot_idx))?; - let new_root = storage_map.apply_delta(map_delta)?; + let new_root = storage_map.apply_delta(map_delta); let index = LeafIndex::new(slot_idx.into()).expect("index is u8 - index within range"); self.slots.insert(index, new_root.into()); @@ -287,11 +282,7 @@ impl AccountStorage { // --- update storage slots ------------------------------------------- - for &slot_idx in delta.cleared_items.iter() { - self.set_item(slot_idx, Word::default())?; - } - - for &(slot_idx, slot_value) in delta.updated_items.iter() { + for (&slot_idx, &slot_value) in delta.slots().iter() { self.set_item(slot_idx, slot_value)?; } diff --git a/objects/src/accounts/stub.rs b/objects/src/accounts/stub.rs index d0c77086f..f322f8f03 100644 --- a/objects/src/accounts/stub.rs +++ b/objects/src/accounts/stub.rs @@ -11,7 +11,7 @@ use super::{hash_account, Account, AccountId, Digest, Felt}; /// - nonce: the nonce of the account. /// - vault_root: a commitment to the account's vault ([super::AssetVault]). /// - storage_root: accounts storage root ([super::AccountStorage]). -/// - code_root: a commitment to the account's code ([super::AccountCode]). +/// - code_commitment: a commitment to the account's code ([super::AccountCode]). #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct AccountStub { @@ -19,7 +19,7 @@ pub struct AccountStub { nonce: Felt, vault_root: Digest, storage_root: Digest, - code_root: Digest, + code_commitment: Digest, } impl AccountStub { @@ -31,14 +31,14 @@ impl AccountStub { nonce: Felt, vault_root: Digest, storage_root: Digest, - code_root: Digest, + code_commitment: Digest, ) -> Self { Self { id, nonce, vault_root, storage_root, - code_root, + code_commitment, } } @@ -46,10 +46,11 @@ impl AccountStub { // -------------------------------------------------------------------------------------------- /// Returns hash of this account. /// - /// Hash of an account is computed as hash(id, nonce, vault_root, storage_root, code_root). - /// Computing the account hash requires 2 permutations of the hash function. + /// Hash of an account is computed as hash(id, nonce, vault_root, storage_root, + /// code_commitment). Computing the account hash requires 2 permutations of the hash + /// function. pub fn hash(&self) -> Digest { - hash_account(self.id, self.nonce, self.vault_root, self.storage_root, self.code_root) + hash_account(self.id, self.nonce, self.vault_root, self.storage_root, self.code_commitment) } /// Returns the id of this account. @@ -72,9 +73,9 @@ impl AccountStub { self.storage_root } - /// Returns the code root of this account. - pub fn code_root(&self) -> Digest { - self.code_root + /// Returns the code commitment of this account. + pub fn code_commitment(&self) -> Digest { + self.code_commitment } } @@ -91,7 +92,7 @@ impl From<&Account> for AccountStub { nonce: account.nonce(), vault_root: account.vault().commitment(), storage_root: account.storage().root(), - code_root: account.code().root(), + code_commitment: account.code().commitment(), } } } diff --git a/objects/src/assets/nonfungible.rs b/objects/src/assets/nonfungible.rs index bec6ac59b..12aa87de2 100644 --- a/objects/src/assets/nonfungible.rs +++ b/objects/src/assets/nonfungible.rs @@ -5,6 +5,10 @@ use super::{ parse_word, AccountId, AccountType, Asset, AssetError, Felt, Hasher, Word, ACCOUNT_ISFAUCET_MASK, }; +use crate::{ + utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, + Digest, +}; /// Position of the faucet_id inside the [NonFungibleAsset] word. const FAUCET_ID_POS: usize = 1; @@ -16,7 +20,7 @@ const FAUCET_ID_POS: usize = 1; /// The commitment is constructed as follows: /// /// - Hash the asset data producing `[d0, d1, d2, d3]`. -/// - Replace the value of `d1` with the fauce id producing `[d0, faucet_id, d2, d3]`. +/// - Replace the value of `d1` with the faucet id producing `[d0, faucet_id, d2, d3]`. /// - Force the bit position [ACCOUNT_ISFAUCET_MASK] of `d3` to be `0`. /// /// [NonFungibleAsset] itself does not contain the actual asset data. The container for this data @@ -26,6 +30,18 @@ const FAUCET_ID_POS: usize = 1; #[cfg_attr(feature = "serde", serde(transparent))] pub struct NonFungibleAsset(Word); +impl PartialOrd for NonFungibleAsset { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for NonFungibleAsset { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + Digest::from(self.0).cmp(&Digest::from(other.0)) + } +} + impl NonFungibleAsset { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- @@ -167,6 +183,20 @@ impl fmt::Display for NonFungibleAsset { } } +impl Serializable for NonFungibleAsset { + fn write_into(&self, target: &mut W) { + target.write(self.0) + } +} + +impl Deserializable for NonFungibleAsset { + fn read_from(source: &mut R) -> Result { + let value: Word = source.read()?; + + Self::try_from(value).map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + // NON-FUNGIBLE ASSET DETAILS // ================================================================================================ diff --git a/objects/src/assets/token_symbol.rs b/objects/src/assets/token_symbol.rs index 71e6f25c6..2006e02eb 100644 --- a/objects/src/assets/token_symbol.rs +++ b/objects/src/assets/token_symbol.rs @@ -47,8 +47,8 @@ impl TryFrom for TokenSymbol { // HELPER FUNCTIONS // ================================================================================================ -// Utils to encode and decode the token symbol as a Felt. Token Symbols can consists of up to 6 characters -// , e.g., A = 0, ... +// Utils to encode and decode the token symbol as a Felt. Token Symbols can consists of up to 6 +// characters , e.g., A = 0, ... fn encode_symbol_to_felt(s: &str) -> Result { if s.is_empty() || s.len() > TokenSymbol::MAX_SYMBOL_LENGTH { return Err(AssetError::TokenSymbolError( diff --git a/objects/src/assets/vault.rs b/objects/src/assets/vault.rs index c18ab1c68..9de64f462 100644 --- a/objects/src/assets/vault.rs +++ b/objects/src/assets/vault.rs @@ -4,8 +4,11 @@ use super::{ AccountId, AccountType, Asset, ByteReader, ByteWriter, Deserializable, DeserializationError, FungibleAsset, NonFungibleAsset, Serializable, ZERO, }; -use crate::{crypto::merkle::Smt, AssetVaultError, Digest}; - +use crate::{ + accounts::{AccountVaultDelta, NonFungibleDeltaAction}, + crypto::merkle::Smt, + AssetVaultError, Digest, +}; // ASSET VAULT // ================================================================================================ @@ -13,11 +16,11 @@ use crate::{crypto::merkle::Smt, AssetVaultError, Digest}; /// /// An asset vault can contain an unlimited number of assets. The assets are stored in a Sparse /// Merkle tree as follows: -/// - For fungible assets, the index of a node is defined by the issuing faucet ID, and the value -/// of the node is the asset itself. Thus, for any fungible asset there will be only one node -/// in the tree. -/// - For non-fungible assets, the index is defined by the asset itself, and the asset is also -/// the value of the node. +/// - For fungible assets, the index of a node is defined by the issuing faucet ID, and the value of +/// the node is the asset itself. Thus, for any fungible asset there will be only one node in the +/// tree. +/// - For non-fungible assets, the index is defined by the asset itself, and the asset is also the +/// value of the node. /// /// An asset vault can be reduced to a single hash which is the root of the Sparse Merkle Tree. #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -89,6 +92,35 @@ impl AssetVault { // PUBLIC MODIFIERS // -------------------------------------------------------------------------------------------- + /// Applies the specified delta to the asset vault. + /// + /// # Errors + /// Returns an error: + /// - If the total value of assets is greater than or equal to 2^63. + /// - If the delta contains an addition/subtraction for a fungible asset that is not stored in + /// the vault. + /// - If the delta contains a non-fungible asset removal that is not stored in the vault. + /// - If the delta contains a non-fungible asset addition that is already stored in the vault. + pub fn apply_delta(&mut self, delta: &AccountVaultDelta) -> Result<(), AssetVaultError> { + for (&faucet_id, &delta) in delta.fungible().iter() { + let asset = FungibleAsset::new(faucet_id, delta.unsigned_abs()) + .expect("Not a fungible faucet ID or delta is too large"); + match delta >= 0 { + true => self.add_fungible_asset(asset), + false => self.remove_fungible_asset(asset), + }?; + } + + for (&asset, &action) in delta.non_fungible().iter() { + match action { + NonFungibleDeltaAction::Add => self.add_non_fungible_asset(asset), + NonFungibleDeltaAction::Remove => self.remove_non_fungible_asset(asset), + }?; + } + + Ok(()) + } + // ADD ASSET // -------------------------------------------------------------------------------------------- /// Add the specified asset to the vault. diff --git a/objects/src/batches/note_tree.rs b/objects/src/batches/note_tree.rs index efa24c3bf..e20adb0ae 100644 --- a/objects/src/batches/note_tree.rs +++ b/objects/src/batches/note_tree.rs @@ -5,22 +5,23 @@ use miden_crypto::{ use crate::{ notes::{NoteId, NoteMetadata}, - BATCH_OUTPUT_NOTES_TREE_DEPTH, + BATCH_NOTES_TREE_DEPTH, }; -/// Wrapper over [SimpleSmt] for batch note tree. +/// Wrapper over [SimpleSmt] for batch note tree. /// /// Each note is stored as two adjacent leaves: odd leaf for id, even leaf for metadata hash. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct BatchNoteTree(SimpleSmt); +pub struct BatchNoteTree(SimpleSmt); impl BatchNoteTree { - /// Wrapper around [`SimpleSmt::with_contiguous_leaves`] which populates notes at contiguous indices - /// starting at index 0. + /// Wrapper around [`SimpleSmt::with_contiguous_leaves`] which populates notes at contiguous + /// indices starting at index 0. /// /// # Errors - /// Returns an error if the number of entries exceeds the maximum tree capacity, that is 2^{depth}. + /// Returns an error if the number of entries exceeds the maximum tree capacity, that is + /// 2^{depth}. pub fn with_contiguous_leaves<'a>( entries: impl IntoIterator, ) -> Result { diff --git a/objects/src/block/header.rs b/objects/src/block/header.rs index 1eff9693d..171422b75 100644 --- a/objects/src/block/header.rs +++ b/objects/src/block/header.rs @@ -17,10 +17,11 @@ use crate::utils::serde::{ /// - `account_root` is a commitment to account database. /// - `nullifier_root` is a commitment to the nullifier database. /// - `note_root` is a commitment to all notes created in the current block. -/// - `tx_hash` is a commitment to a set of IDs of transactions which affected accounts in the block. +/// - `tx_hash` is a commitment to a set of IDs of transactions which affected accounts in the +/// block. /// - `proof_hash` is a hash of a STARK proof attesting to the correct state transition. -/// - `timestamp` is the time when the block was created, in seconds since UNIX epoch. -/// Current representation is sufficient to represent time up to year 2106. +/// - `timestamp` is the time when the block was created, in seconds since UNIX epoch. Current +/// representation is sufficient to represent time up to year 2106. /// - `sub_hash` is a sequential hash of all fields except the note_root. /// - `hash` is a 2-to-1 hash of the sub_hash and the note_root. #[derive(Debug, Eq, PartialEq, Copy, Clone)] @@ -68,9 +69,10 @@ impl BlockHeader { block_num, ); - // The sub hash is merged with the note_root - hash(sub_hash, note_root) to produce the final - // hash. This is done to make the note_root easily accessible without having to unhash the - // entire header. Having the note_root easily accessible is useful when authenticating notes. + // The sub hash is merged with the note_root - hash(sub_hash, note_root) to produce the + // final hash. This is done to make the note_root easily accessible without having + // to unhash the entire header. Having the note_root easily accessible is useful + // when authenticating notes. let hash = Hasher::merge(&[sub_hash, note_root]); Self { @@ -142,9 +144,9 @@ impl BlockHeader { /// Returns the commitment to all transactions in this block. /// - /// The commitment is computed as sequential hash of (`transaction_id`, `account_id`) tuples. This - /// makes it possible for the verifier to link transaction IDs to the accounts which they were - /// executed against. + /// The commitment is computed as sequential hash of (`transaction_id`, `account_id`) tuples. + /// This makes it possible for the verifier to link transaction IDs to the accounts which + /// they were executed against. pub fn tx_hash(&self) -> Digest { self.tx_hash } @@ -237,14 +239,16 @@ impl Deserializable for BlockHeader { #[cfg(test)] mod tests { + use vm_core::Word; use winter_rand_utils::rand_array; use super::*; #[test] fn test_serde() { - let header = - BlockHeader::mock(0, Some(rand_array().into()), Some(rand_array().into()), &[]); + let chain_root: Word = rand_array(); + let note_root: Word = rand_array(); + let header = BlockHeader::mock(0, Some(chain_root.into()), Some(note_root.into()), &[]); let serialized = header.to_bytes(); let deserialized = BlockHeader::read_from_bytes(&serialized).unwrap(); diff --git a/objects/src/block/mod.rs b/objects/src/block/mod.rs index f8fd559d9..23f71727f 100644 --- a/objects/src/block/mod.rs +++ b/objects/src/block/mod.rs @@ -24,10 +24,10 @@ pub type NoteBatch = Vec; /// /// A block contains information resulting from executing a set of transactions against the chain /// state defined by the previous block. It consists of 3 main components: -/// - A set of change descriptors for all accounts updated in this block. For private accounts, -/// the block contains only the new account state hashes; for public accounts, the block also -/// contains a set of state deltas which can be applied to the previous account state to get the -/// new account state. +/// - A set of change descriptors for all accounts updated in this block. For private accounts, the +/// block contains only the new account state hashes; for public accounts, the block also contains +/// a set of state deltas which can be applied to the previous account state to get the new +/// account state. /// - A set of new notes created in this block. For private notes, the block contains only note IDs /// and note metadata; for public notes, full note details are recorded. /// - A set of new nullifiers created for all notes that were consumed in the block. @@ -44,11 +44,11 @@ pub struct Block { /// Account updates for the block. updated_accounts: Vec, - /// Note batches created in transactions in the block. - created_notes: Vec, + /// Note batches created by the transactions in this block. + output_note_batches: Vec, - /// Nullifiers produced in transactions in the block. - created_nullifiers: Vec, + /// Nullifiers produced by the transactions in this block. + nullifiers: Vec, // // TODO: add zk proof } @@ -60,14 +60,14 @@ impl Block { pub fn new( header: BlockHeader, updated_accounts: Vec, - created_notes: Vec, - created_nullifiers: Vec, + output_note_batches: Vec, + nullifiers: Vec, ) -> Result { let block = Self { header, updated_accounts, - created_notes, - created_nullifiers, + output_note_batches, + nullifiers, }; block.validate()?; @@ -91,16 +91,16 @@ impl Block { } /// Returns a set of note batches containing all notes created in this block. - pub fn created_notes(&self) -> &[NoteBatch] { - &self.created_notes + pub fn output_note_batches(&self) -> &[NoteBatch] { + &self.output_note_batches } /// Returns an iterator over all notes created in this block. /// /// Each note is accompanied by a corresponding index specifying where the note is located - /// in the blocks note tree. + /// in the block's note tree. pub fn notes(&self) -> impl Iterator { - self.created_notes.iter().enumerate().flat_map(|(batch_idx, notes)| { + self.output_note_batches.iter().enumerate().flat_map(|(batch_idx, notes)| { notes.iter().enumerate().map(move |(note_idx_in_batch, note)| { (BlockNoteIndex::new(batch_idx, note_idx_in_batch), note) }) @@ -118,11 +118,12 @@ impl Block { } /// Returns a set of nullifiers for all notes consumed in the block. - pub fn created_nullifiers(&self) -> &[Nullifier] { - &self.created_nullifiers + pub fn nullifiers(&self) -> &[Nullifier] { + &self.nullifiers } - /// Returns an iterator over all transactions which affected accounts in the block with corresponding account IDs. + /// Returns an iterator over all transactions which affected accounts in the block with + /// corresponding account IDs. pub fn transactions(&self) -> impl Iterator + '_ { self.updated_accounts.iter().flat_map(|update| { update @@ -141,19 +142,19 @@ impl Block { // -------------------------------------------------------------------------------------------- fn validate(&self) -> Result<(), BlockError> { - let batch_count = self.created_notes.len(); + let batch_count = self.output_note_batches.len(); if batch_count > MAX_BATCHES_PER_BLOCK { return Err(BlockError::TooManyTransactionBatches(batch_count)); } - for batch in self.created_notes.iter() { + for batch in self.output_note_batches.iter() { if batch.len() > MAX_NOTES_PER_BATCH { return Err(BlockError::TooManyNotesInBatch(batch.len())); } } let mut notes = BTreeSet::new(); - for batch in self.created_notes.iter() { + for batch in self.output_note_batches.iter() { for note in batch.iter() { if !notes.insert(note.id()) { return Err(BlockError::DuplicateNoteFound(note.id())); @@ -169,8 +170,8 @@ impl Serializable for Block { fn write_into(&self, target: &mut W) { self.header.write_into(target); self.updated_accounts.write_into(target); - self.created_notes.write_into(target); - self.created_nullifiers.write_into(target); + self.output_note_batches.write_into(target); + self.nullifiers.write_into(target); } } @@ -179,8 +180,8 @@ impl Deserializable for Block { let block = Self { header: BlockHeader::read_from(source)?, updated_accounts: >::read_from(source)?, - created_notes: >::read_from(source)?, - created_nullifiers: >::read_from(source)?, + output_note_batches: >::read_from(source)?, + nullifiers: >::read_from(source)?, }; block diff --git a/objects/src/block/note_tree.rs b/objects/src/block/note_tree.rs index b97053c06..a6ca8dbfb 100644 --- a/objects/src/block/note_tree.rs +++ b/objects/src/block/note_tree.rs @@ -8,20 +8,21 @@ use miden_crypto::{ use crate::{ notes::NoteMetadata, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, - BLOCK_OUTPUT_NOTES_TREE_DEPTH, MAX_NOTES_PER_BATCH, + BLOCK_NOTES_TREE_DEPTH, MAX_NOTES_PER_BATCH, MAX_NOTES_PER_BLOCK, }; -/// Wrapper over [SimpleSmt] for notes tree. +/// Wrapper over [SimpleSmt] for notes tree. /// /// Each note is stored as two adjacent leaves: odd leaf for id, even leaf for metadata hash. /// ID's leaf index is calculated as [(batch_idx * MAX_NOTES_PER_BATCH + note_idx_in_batch) * 2]. /// Metadata hash leaf is stored the next after id leaf: [id_index + 1]. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct BlockNoteTree(SimpleSmt); +pub struct BlockNoteTree(SimpleSmt); impl BlockNoteTree { - /// Returns a new [BlockNoteTree] instantiated with entries set as specified by the provided entries. + /// Returns a new [BlockNoteTree] instantiated with entries set as specified by the provided + /// entries. /// /// Entry format: (note_index, note_id, note_metadata). /// @@ -35,7 +36,7 @@ impl BlockNoteTree { entries: impl IntoIterator, ) -> Result { let interleaved = entries.into_iter().flat_map(|(index, note_id, metadata)| { - let id_index = index.leaf_index(); + let id_index = index.leaf_index().into(); [(id_index, note_id.into()), (id_index + 1, metadata.into())] }); @@ -52,7 +53,7 @@ impl BlockNoteTree { /// The returned path is to the node which is the parent of both note and note metadata node. pub fn get_note_path(&self, index: BlockNoteIndex) -> Result { // get the path to the leaf containing the note (path len = 21) - let leaf_index = LeafIndex::new(index.leaf_index())?; + let leaf_index = LeafIndex::new(index.leaf_index().into())?; // move up the path by removing the first node, this path now points to the parent of the // note path @@ -92,11 +93,14 @@ impl BlockNoteIndex { } /// Returns an index to the node which the parent of both the note and note metadata. - pub fn to_absolute_index(&self) -> u64 { - (self.batch_idx() * MAX_NOTES_PER_BATCH + self.note_idx_in_batch()) as u64 + pub fn to_absolute_index(&self) -> u32 { + const _: () = assert!(MAX_NOTES_PER_BLOCK <= u32::MAX as usize); + (self.batch_idx() * MAX_NOTES_PER_BATCH + self.note_idx_in_batch()) as u32 } - fn leaf_index(&self) -> u64 { + /// Returns an index of the leaf containing the note. + fn leaf_index(&self) -> u32 { + const _: () = assert!(MAX_NOTES_PER_BLOCK * 2 <= u32::MAX as usize); self.to_absolute_index() * 2 } } diff --git a/objects/src/constants.rs b/objects/src/constants.rs index bf2a8df5a..be8768c6b 100644 --- a/objects/src/constants.rs +++ b/objects/src/constants.rs @@ -25,20 +25,17 @@ pub const MIN_PROOF_SECURITY_LEVEL: u32 = 96; // TRANSACTION BATCH // ================================================================================================ -/// The depth of the Sparse Merkle Tree used to store created notes in a single batch. +/// The depth of the Sparse Merkle Tree used to store output notes in a single batch. /// /// A single note uses two leaves in the tree. The even leaf is used to store the note's id, the /// odd leaf is used to store the note's metadata. -pub const BATCH_OUTPUT_NOTES_TREE_DEPTH: u8 = 13; +pub const BATCH_NOTES_TREE_DEPTH: u8 = 13; /// The maximum number of notes that can be created in a single batch. /// /// Because the tree used in a batch has fixed depth, and each note takes two leaves, the maximum /// number of notes is the number of leaves in the tree. -pub const MAX_NOTES_PER_BATCH: usize = 2_usize.pow((BATCH_OUTPUT_NOTES_TREE_DEPTH - 1) as u32); - -/// The maximum number of transaction in a single batch. -pub const MAX_TRANSACTIONS_PER_BATCH: usize = MAX_NOTES_PER_BATCH / MAX_OUTPUT_NOTES_PER_TX; +pub const MAX_NOTES_PER_BATCH: usize = 2_usize.pow((BATCH_NOTES_TREE_DEPTH - 1) as u32); // BLOCK // ================================================================================================ @@ -47,17 +44,19 @@ pub const MAX_TRANSACTIONS_PER_BATCH: usize = MAX_NOTES_PER_BATCH / MAX_OUTPUT_N /// /// This value can be interpreted as: /// -/// - The depth of a tree with the leaves set to a batch created note tree root. -/// - The level at which the batches create note trees are merged, creating a new tree with this many -/// additional new levels. -pub const BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH: u8 = 8; +/// - The depth of a tree with the leaves set to a batch output note tree root. +/// - The level at which the batches create note trees are merged, creating a new tree with this +/// many additional new levels. +pub const BLOCK_NOTES_BATCH_TREE_DEPTH: u8 = 8; /// The final depth of the Sparse Merkle Tree used to store all notes created in a block. -pub const BLOCK_OUTPUT_NOTES_TREE_DEPTH: u8 = - BATCH_OUTPUT_NOTES_TREE_DEPTH + BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH; +pub const BLOCK_NOTES_TREE_DEPTH: u8 = BATCH_NOTES_TREE_DEPTH + BLOCK_NOTES_BATCH_TREE_DEPTH; /// Maximum number of batches that can be inserted into a single block. -pub const MAX_BATCHES_PER_BLOCK: usize = 2_usize.pow(BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH as u32); +pub const MAX_BATCHES_PER_BLOCK: usize = 2_usize.pow(BLOCK_NOTES_BATCH_TREE_DEPTH as u32); + +/// Maximum number of output notes that can be created in a single block. +pub const MAX_NOTES_PER_BLOCK: usize = MAX_NOTES_PER_BATCH * MAX_BATCHES_PER_BLOCK; /// The block height of the genesis block pub const GENESIS_BLOCK: u32 = 0; diff --git a/objects/src/errors.rs b/objects/src/errors.rs index 3f5621113..e9f222985 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -1,26 +1,31 @@ use alloc::{string::String, vec::Vec}; use core::fmt; -use assembly::AssemblyError; use vm_processor::DeserializationError; use super::{ accounts::{AccountId, StorageSlotType}, assets::{Asset, FungibleAsset, NonFungibleAsset}, - crypto::{hash::rpo::RpoDigest, merkle::MerkleError}, + crypto::merkle::MerkleError, notes::NoteId, Digest, Word, MAX_BATCHES_PER_BLOCK, MAX_NOTES_PER_BATCH, }; -use crate::{accounts::AccountType, notes::NoteType}; +use crate::{ + accounts::{delta::AccountUpdateDetails, AccountType}, + notes::NoteType, +}; // ACCOUNT ERROR // ================================================================================================ #[derive(Debug, Clone, PartialEq, Eq)] pub enum AccountError { - AccountCodeAssemblerError(AssemblyError), + AccountCodeAssemblyError(String), // TODO: use Report + AccountCodeDeserializationError(DeserializationError), AccountCodeNoProcedures, AccountCodeTooManyProcedures { max: usize, actual: usize }, + AccountCodeProcedureInvalidStorageOffset, + AccountCodeProcedureInvalidPadding, AccountIdInvalidFieldElement(String), AccountIdTooFewOnes(u32, u32), AssetVaultUpdateError(AssetVaultError), @@ -55,14 +60,16 @@ impl std::error::Error for AccountError {} #[derive(Debug, Clone, PartialEq, Eq)] pub enum AccountDeltaError { DuplicateStorageItemUpdate(usize), - DuplicateVaultUpdate(Asset), - InconsistentNonceUpdate(String), + DuplicateNonFungibleVaultUpdate(NonFungibleAsset), + FungibleAssetDeltaOverflow { + faucet_id: AccountId, + this: i64, + other: i64, + }, ImmutableStorageSlot(usize), - TooManyAddedAsset { actual: usize, max: usize }, - TooManyClearedStorageItems { actual: usize, max: usize }, - TooManyRemovedAssets { actual: usize, max: usize }, - TooManyUpdatedStorageItems { actual: usize, max: usize }, - DuplicateStorageMapLeaf { key: RpoDigest }, + IncompatibleAccountUpdates(AccountUpdateDetails, AccountUpdateDetails), + InconsistentNonceUpdate(String), + NotAFungibleFaucetId(AccountId), } #[cfg(feature = "std")] @@ -137,15 +144,18 @@ pub enum NoteError { InvalidAssetData(AssetError), InvalidNoteSender(AccountError), InvalidNoteTagUseCase(u16), + InvalidNoteExecutionHintTag(u8), + InvalidNoteExecutionHintPayload(u8, u32), InvalidNoteType(NoteType), InvalidNoteTypeValue(u64), - InvalidOriginIndex(String), + InvalidLocationIndex(String), InvalidStubDataLen(usize), NetworkExecutionRequiresOnChainAccount, NetworkExecutionRequiresPublicNote(NoteType), NoteDeserializationError(DeserializationError), + NoteScriptAssemblyError(String), // TODO: use Report + NoteScriptDeserializationError(DeserializationError), PublicUseCaseRequiresPublicNote(NoteType), - ScriptCompilationError(AssemblyError), TooManyAssets(usize), TooManyInputs(usize), } @@ -159,8 +169,8 @@ impl NoteError { Self::DuplicateNonFungibleAsset(asset) } - pub fn invalid_origin_index(msg: String) -> Self { - Self::InvalidOriginIndex(msg) + pub fn invalid_location_index(msg: String) -> Self { + Self::InvalidLocationIndex(msg) } pub fn too_many_assets(num_assets: usize) -> Self { @@ -219,7 +229,7 @@ impl std::error::Error for ChainMmrError {} #[derive(Debug, Clone, PartialEq, Eq)] pub enum TransactionScriptError { - ScriptCompilationError(AssemblyError), + AssemblyError(String), // TODO: change to Report } impl fmt::Display for TransactionScriptError { diff --git a/objects/src/lib.rs b/objects/src/lib.rs index 335166aef..c42731b67 100644 --- a/objects/src/lib.rs +++ b/objects/src/lib.rs @@ -34,9 +34,8 @@ pub use vm_core::{Felt, FieldElement, StarkField, Word, EMPTY_WORD, ONE, WORD_SI pub mod assembly { pub use assembly::{ - ast::{AstSerdeOptions, ModuleAst, ProgramAst}, - Assembler, AssemblyContext, AssemblyError, Library, LibraryNamespace, LibraryPath, - MaslLibrary, Version, + mast, Assembler, AssemblyError, DefaultSourceManager, KernelLibrary, Library, + LibraryNamespace, LibraryPath, SourceManager, Version, }; } @@ -57,6 +56,6 @@ pub mod utils { pub mod vm { pub use miden_verifier::ExecutionProof; - pub use vm_core::{code_blocks::CodeBlock, Program, ProgramInfo}; - pub use vm_processor::{AdviceInputs, AdviceMap, StackInputs, StackOutputs}; + pub use vm_core::{Program, ProgramInfo}; + pub use vm_processor::{AdviceInputs, AdviceMap, RowIndex, StackInputs, StackOutputs}; } diff --git a/objects/src/notes/execution_hint.rs b/objects/src/notes/execution_hint.rs new file mode 100644 index 000000000..973527ca4 --- /dev/null +++ b/objects/src/notes/execution_hint.rs @@ -0,0 +1,265 @@ +// NOTE EXECUTION HINT +// ================================================================================================ + +use vm_core::Felt; + +use crate::NoteError; + +// CONSTANTS +// ================================================================================================ + +const NONE_TAG: u8 = 0; +const ALWAYS_TAG: u8 = 1; +const AFTER_BLOCK_TAG: u8 = 2; +const ON_BLOCK_SLOT_TAG: u8 = 3; + +/// Specifies the conditions under which a note is ready to be consumed. +/// These conditions are meant to be encoded in the note script as well. +/// +/// This struct can be represented as the combination of a tag, and a payload. +/// The tag specifies the variant of the hint, and the payload encodes the hint data. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum NoteExecutionHint { + /// Unspecified note execution hint. Implies it is not knorn under which conditions the note + /// is consumable. + None, + /// The note's script can be executed at any time. + Always, + /// The note's script can be executed after the specified block height. + AfterBlock { block_num: u32 }, + /// The note's script can be executed in the specified slot within the specified epoch. + /// + /// The slot is defined as follows: + /// - First we define the length of the epoch in powers of 2. For example, epoch_len = 10 is an + /// epoch of 1024 blocks. + /// - Then we define the length of a slot within the epoch also using powers of 2. For example, + /// slot_len = 7 is a slot of 128 blocks. + /// - Lastly, the offset specifies the index of the slot within the epoch - i.e., 0 is the + /// first slot, 1 is the second slot etc. + /// + /// For example: { epoch_len: 10, slot_len: 7, slot_offset: 1 } means that the note can + /// be executed in any second 128 block slot of a 1024 block epoch. These would be blocks + /// 128..255, 1152..1279, 2176..2303 etc. + OnBlockSlot { + epoch_len: u8, + slot_len: u8, + slot_offset: u8, + }, +} + +impl NoteExecutionHint { + // CONSTRUCTORS + // ------------------------------------------------------------------------------------------------ + + /// Creates a [NoteExecutionHint::None] variant + pub fn none() -> Self { + NoteExecutionHint::None + } + + /// Creates a [NoteExecutionHint::Always] variant + pub fn always() -> Self { + NoteExecutionHint::Always + } + + /// Creates a [NoteExecutionHint::AfterBlock] variant based on the given `block_num` + pub fn after_block(block_num: u32) -> Self { + NoteExecutionHint::AfterBlock { block_num } + } + + /// Creates a [NoteExecutionHint::OnBlockSlot] for the given parameters + pub fn on_block_slot(epoch_len: u8, slot_len: u8, slot_offset: u8) -> Self { + NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } + } + + pub fn from_parts(tag: u8, payload: u32) -> Result { + match tag { + NONE_TAG => { + if payload != 0 { + return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload)); + } + Ok(NoteExecutionHint::None) + }, + ALWAYS_TAG => { + if payload != 0 { + return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload)); + } + Ok(NoteExecutionHint::Always) + }, + AFTER_BLOCK_TAG => Ok(NoteExecutionHint::AfterBlock { block_num: payload }), + ON_BLOCK_SLOT_TAG => { + let remainder = (payload >> 24 & 0xff) as u8; + if remainder != 0 { + return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload)); + } + + let epoch_len = ((payload >> 16) & 0xff) as u8; + let slot_len = ((payload >> 8) & 0xff) as u8; + let slot_offset = (payload & 0xff) as u8; + let hint = NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset }; + + Ok(hint) + }, + _ => Err(NoteError::InvalidNoteExecutionHintTag(tag)), + } + } + + /// Returns whether the note execution conditions validate for the given `block_num` + /// + /// # Returns + /// - `None` if we don't know whether the note can be consumed. + /// - `Some(true)` if the note is consumable for the given `block_num` + /// - `Some(false)` if the note is not consumable for the given `block_num` + pub fn can_be_consumed(&self, block_num: u32) -> Option { + match self { + NoteExecutionHint::None => None, + NoteExecutionHint::Always => Some(true), + NoteExecutionHint::AfterBlock { block_num: hint_block_num } => { + Some(block_num >= *hint_block_num) + }, + NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } => { + let epoch_len_blocks: u32 = 1 << epoch_len; + let slot_len_blocks: u32 = 1 << slot_len; + + let block_epoch_index = block_num / epoch_len_blocks; + + let slot_start_block = + block_epoch_index * epoch_len_blocks + (*slot_offset as u32) * slot_len_blocks; + let slot_end_block = slot_start_block + slot_len_blocks; + + let can_be_consumed = block_num >= slot_start_block && block_num < slot_end_block; + Some(can_be_consumed) + }, + } + } + + pub fn into_parts(&self) -> (u8, u32) { + match self { + NoteExecutionHint::None => (NONE_TAG, 0), + NoteExecutionHint::Always => (ALWAYS_TAG, 0), + NoteExecutionHint::AfterBlock { block_num } => (AFTER_BLOCK_TAG, *block_num), + NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } => { + let payload: u32 = + ((*epoch_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32); + (ON_BLOCK_SLOT_TAG, payload) + }, + } + } +} + +/// As a Felt, the ExecutionHint is encoded as: +/// +/// - 6 least significant bits: Hint identifier (tag). +/// - Bits 6 to 38: Hint payload. +/// +/// This way, hints such as [NoteExecutionHint::Always], are represented by `Felt::new(1)` +impl From for Felt { + fn from(value: NoteExecutionHint) -> Self { + let int_representation: u64 = value.into(); + Felt::new(int_representation) + } +} + +/// As a u64, the ExecutionHint is encoded as: +/// +/// - 6 least significant bits: Hint identifier (tag). +/// - Bits 6 to 38: Hint payload. +/// +/// This way, hints such as [NoteExecutionHint::Always], are represented by `1u64` +impl TryFrom for NoteExecutionHint { + type Error = NoteError; + fn try_from(value: u64) -> Result { + let tag = (value & 0b111111) as u8; + let payload = ((value >> 6) & 0xffffffff) as u32; + + Self::from_parts(tag, payload) + } +} + +impl From for u64 { + fn from(value: NoteExecutionHint) -> Self { + let (tag, payload) = value.into_parts(); + (payload as u64) << 6 | (tag as u64) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_hint_serde(note_execution_hint: NoteExecutionHint) { + let (tag, payload) = note_execution_hint.into_parts(); + let deserialized = NoteExecutionHint::from_parts(tag, payload).unwrap(); + assert_eq!(deserialized, note_execution_hint); + } + + #[test] + fn test_serialization_round_trip() { + assert_hint_serde(NoteExecutionHint::None); + assert_hint_serde(NoteExecutionHint::Always); + assert_hint_serde(NoteExecutionHint::AfterBlock { block_num: 15 }); + assert_hint_serde(NoteExecutionHint::OnBlockSlot { + epoch_len: 9, + slot_len: 12, + slot_offset: 18, + }); + } + + #[test] + fn test_encode_round_trip() { + let hint = NoteExecutionHint::AfterBlock { block_num: 15 }; + let hint_int: u64 = hint.into(); + let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap(); + assert_eq!(hint, decoded_hint); + + let hint = NoteExecutionHint::OnBlockSlot { + epoch_len: 22, + slot_len: 33, + slot_offset: 44, + }; + let hint_int: u64 = hint.into(); + let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap(); + assert_eq!(hint, decoded_hint); + + let always_int: u64 = NoteExecutionHint::always().into(); + assert_eq!(always_int, 1u64); + } + + #[test] + fn test_can_be_consumed() { + let none = NoteExecutionHint::none(); + assert!(none.can_be_consumed(100).is_none()); + + let always = NoteExecutionHint::always(); + assert!(always.can_be_consumed(100).unwrap()); + + let after_block = NoteExecutionHint::after_block(12345); + assert!(!after_block.can_be_consumed(12344).unwrap()); + assert!(after_block.can_be_consumed(12345).unwrap()); + + let on_block_slot = NoteExecutionHint::on_block_slot(10, 7, 1); + assert!(!on_block_slot.can_be_consumed(127).unwrap()); // Block 127 is not in the slot 128..255 + assert!(on_block_slot.can_be_consumed(128).unwrap()); // Block 128 is in the slot 128..255 + assert!(on_block_slot.can_be_consumed(255).unwrap()); // Block 255 is in the slot 128..255 + assert!(!on_block_slot.can_be_consumed(256).unwrap()); // Block 256 is not in the slot 128..255 + assert!(on_block_slot.can_be_consumed(1152).unwrap()); // Block 1152 is in the slot 1152..1279 + assert!(on_block_slot.can_be_consumed(1279).unwrap()); // Block 1279 is in the slot 1152..1279 + assert!(on_block_slot.can_be_consumed(2176).unwrap()); // Block 2176 is in the slot 2176..2303 + assert!(!on_block_slot.can_be_consumed(2175).unwrap()); // Block 1279 is in the slot + // 2176..2303 + } + + #[test] + fn test_parts_validity() { + NoteExecutionHint::from_parts(NONE_TAG, 1).unwrap_err(); + NoteExecutionHint::from_parts(ALWAYS_TAG, 12).unwrap_err(); + // 4th byte should be blank for tag 3 (OnBlockSlot) + NoteExecutionHint::from_parts(ON_BLOCK_SLOT_TAG, 1 << 24).unwrap_err(); + NoteExecutionHint::from_parts(ON_BLOCK_SLOT_TAG, 0).unwrap(); + + NoteExecutionHint::from_parts(10, 1).unwrap_err(); + } +} diff --git a/objects/src/notes/file.rs b/objects/src/notes/file.rs index c34d879a4..a7d933d80 100644 --- a/objects/src/notes/file.rs +++ b/objects/src/notes/file.rs @@ -10,17 +10,26 @@ use super::{Note, NoteDetails, NoteId, NoteInclusionProof, NoteTag}; pub enum NoteFile { /// The note's details aren't known. NoteId(NoteId), - /// The note has not yet been recorded on chain. + /// The note may or may not have already been recorded on chain. /// - /// An optional tag is included for note tracking. - NoteDetails(NoteDetails, Option), + /// The `after_block_num` specifies the block after which the note is expected to appear on + /// chain. Though this should be treated as a hint (i.e., there is no guarantee that the note + /// will appear on chain or that it will in fact appear after the specified block). + /// + /// An optional tag specifies the tag associated with the note, though this also should be + /// treated as a hint. + NoteDetails { + details: NoteDetails, + after_block_num: u32, + tag: Option, + }, /// The note has been recorded on chain. NoteWithProof(Note, NoteInclusionProof), } impl From for NoteFile { fn from(details: NoteDetails) -> Self { - NoteFile::NoteDetails(details, None) + NoteFile::NoteDetails { details, after_block_num: 0, tag: None } } } @@ -41,9 +50,10 @@ impl Serializable for NoteFile { target.write_u8(0); note_id.write_into(target); }, - NoteFile::NoteDetails(details, tag) => { + NoteFile::NoteDetails { details, after_block_num, tag } => { target.write_u8(1); details.write_into(target); + after_block_num.write_into(target); tag.write_into(target); }, NoteFile::NoteWithProof(note, proof) => { @@ -67,8 +77,9 @@ impl Deserializable for NoteFile { 0 => Ok(NoteFile::NoteId(NoteId::read_from(source)?)), 1 => { let details = NoteDetails::read_from(source)?; + let after_block_num = u32::read_from(source)?; let tag = Option::::read_from(source)?; - Ok(NoteFile::NoteDetails(details, tag)) + Ok(NoteFile::NoteDetails { details, after_block_num, tag }) }, 2 => { let note = Note::read_from(source)?; @@ -89,7 +100,6 @@ impl Deserializable for NoteFile { mod tests { use alloc::vec::Vec; - use assembly::{ast::ProgramAst, Assembler}; use vm_core::{ utils::{Deserializable, Serializable}, Felt, @@ -108,7 +118,6 @@ mod tests { Note, NoteAssets, NoteFile, NoteInclusionProof, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType, }, - testing::notes::DEFAULT_NOTE_CODE, }; fn create_example_note() -> Note { @@ -118,14 +127,19 @@ mod tests { )); let serial_num = [Felt::new(0), Felt::new(1), Felt::new(2), Felt::new(3)]; - let note_program_ast = ProgramAst::parse(DEFAULT_NOTE_CODE).unwrap(); - let (script, _) = NoteScript::new(note_program_ast, &Assembler::default()).unwrap(); + let script = NoteScript::mock(); let note_inputs = NoteInputs::new(vec![target.into()]).unwrap(); let recipient = NoteRecipient::new(serial_num, script, note_inputs); let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap()); - let metadata = - NoteMetadata::new(faucet, NoteType::Public, NoteTag::from(123), Felt::new(0)).unwrap(); + let metadata = NoteMetadata::new( + faucet, + NoteType::Public, + NoteTag::from(123), + crate::notes::NoteExecutionHint::None, + Felt::new(0), + ) + .unwrap(); Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient) } @@ -161,15 +175,20 @@ mod tests { #[test] fn serialize_details() { let note = create_example_note(); - let file = NoteFile::NoteDetails(note.details.clone(), Some(NoteTag::from(123))); + let file = NoteFile::NoteDetails { + details: note.details.clone(), + after_block_num: 456, + tag: Some(NoteTag::from(123)), + }; let mut buffer = Vec::new(); file.write_into(&mut buffer); let file_copy = NoteFile::read_from_bytes(&buffer).unwrap(); match file_copy { - NoteFile::NoteDetails(details, tag) => { + NoteFile::NoteDetails { details, after_block_num, tag } => { assert_eq!(details, note.details); + assert_eq!(after_block_num, 456); assert_eq!(tag, Some(NoteTag::from(123))); }, _ => panic!("Invalid note file variant"), @@ -179,14 +198,7 @@ mod tests { #[test] fn serialize_with_proof() { let note = create_example_note(); - let mock_inclusion_proof = NoteInclusionProof::new( - Default::default(), - Default::default(), - Default::default(), - 0, - Default::default(), - ) - .unwrap(); + let mock_inclusion_proof = NoteInclusionProof::new(0, 0, Default::default()).unwrap(); let file = NoteFile::NoteWithProof(note.clone(), mock_inclusion_proof.clone()); let mut buffer = Vec::new(); file.write_into(&mut buffer); diff --git a/objects/src/notes/inputs.rs b/objects/src/notes/inputs.rs index 55da48dab..28ff24839 100644 --- a/objects/src/notes/inputs.rs +++ b/objects/src/notes/inputs.rs @@ -72,7 +72,6 @@ impl NoteInputs { /// - input_len is the number of inputs /// - INPUTS is the variable inputs for the note /// - PADDING is the optional padding to align the data with a 2WORD boundary - /// pub fn format_for_advice(&self) -> Vec { // NOTE: keep map in sync with the `note::get_inputs` API procedure let mut padded = pad_inputs(&self.values); diff --git a/objects/src/notes/origin.rs b/objects/src/notes/location.rs similarity index 50% rename from objects/src/notes/origin.rs rename to objects/src/notes/location.rs index a23b7c10d..a1a4732bd 100644 --- a/objects/src/notes/origin.rs +++ b/objects/src/notes/location.rs @@ -1,34 +1,42 @@ -use alloc::string::ToString; - use super::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, NoteError, Serializable, - NOTE_TREE_DEPTH, + ByteReader, ByteWriter, Deserializable, DeserializationError, NoteError, Serializable, }; -use crate::crypto::merkle::{MerklePath, NodeIndex}; +use crate::{crypto::merkle::MerklePath, MAX_BATCHES_PER_BLOCK, MAX_NOTES_PER_BATCH}; -/// Contains information about the origin of a note. +/// Contains information about the location of a note. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct NoteOrigin { +pub struct NoteLocation { /// The block number the note was created in. - pub block_num: u32, + block_num: u32, /// The index of the note in the note Merkle tree of the block the note was created in. - pub node_index: NodeIndex, // TODO: should be a u32 because the depth is always the same + node_index_in_block: u32, +} + +impl NoteLocation { + /// Returns the block number the note was created in. + pub fn block_num(&self) -> u32 { + self.block_num + } + + /// Returns the index of the note in the note Merkle tree of the block the note was created in. + /// + /// # Note + /// + /// The height of the Merkle tree is [crate::constants::BLOCK_NOTES_TREE_DEPTH]. + /// Thus, the maximum index is `2 ^ BLOCK_NOTES_TREE_DEPTH - 1`. + pub fn node_index_in_block(&self) -> u32 { + self.node_index_in_block + } } /// Contains the data required to prove inclusion of a note in the canonical chain. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct NoteInclusionProof { - /// Details about the note's origin. - origin: NoteOrigin, - - /// The sub hash of the block the note was created in. - sub_hash: Digest, - - /// The note root of the block the note was created in. - note_root: Digest, + /// Details about the note's location. + location: NoteLocation, /// The note's authentication Merkle path its block's the note root. note_path: MerklePath, @@ -38,37 +46,26 @@ impl NoteInclusionProof { /// Returns a new [NoteInclusionProof]. pub fn new( block_num: u32, - sub_hash: Digest, - note_root: Digest, - index: u64, + node_index_in_block: u32, note_path: MerklePath, ) -> Result { - let node_index = NodeIndex::new(NOTE_TREE_DEPTH, index) - .map_err(|e| NoteError::invalid_origin_index(e.to_string()))?; - Ok(Self { - origin: NoteOrigin { block_num, node_index }, - sub_hash, - note_root, - note_path, - }) + const HIGHEST_INDEX: usize = MAX_BATCHES_PER_BLOCK * MAX_NOTES_PER_BATCH - 1; + if node_index_in_block as usize > HIGHEST_INDEX { + return Err(NoteError::InvalidLocationIndex(format!( + "Node index ({node_index_in_block}) is out of bounds (0..={HIGHEST_INDEX})." + ))); + } + let location = NoteLocation { block_num, node_index_in_block }; + + Ok(Self { location, note_path }) } // ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the sub hash of the block header the note was created in. - pub fn sub_hash(&self) -> Digest { - self.sub_hash - } - - /// Returns the note root of the block header the note was created in. - pub fn note_root(&self) -> Digest { - self.note_root - } - - /// Returns the origin of the note. - pub fn origin(&self) -> &NoteOrigin { - &self.origin + /// Returns the location of the note. + pub fn location(&self) -> &NoteLocation { + &self.location } /// Returns the Merkle path to the note in the note Merkle tree of the block the note was @@ -81,38 +78,34 @@ impl NoteInclusionProof { // SERIALIZATION // ================================================================================================ -impl Serializable for NoteOrigin { +impl Serializable for NoteLocation { fn write_into(&self, target: &mut W) { target.write_u32(self.block_num); - self.node_index.write_into(target); + target.write_u32(self.node_index_in_block); } } -impl Deserializable for NoteOrigin { +impl Deserializable for NoteLocation { fn read_from(source: &mut R) -> Result { let block_num = source.read_u32()?; - let node_index = NodeIndex::read_from(source)?; + let node_index_in_block = source.read_u32()?; - Ok(Self { block_num, node_index }) + Ok(Self { block_num, node_index_in_block }) } } impl Serializable for NoteInclusionProof { fn write_into(&self, target: &mut W) { - self.origin.write_into(target); - self.sub_hash.write_into(target); - self.note_root.write_into(target); + self.location.write_into(target); self.note_path.write_into(target); } } impl Deserializable for NoteInclusionProof { fn read_from(source: &mut R) -> Result { - let origin = NoteOrigin::read_from(source)?; - let sub_hash = Digest::read_from(source)?; - let note_root = Digest::read_from(source)?; + let location = NoteLocation::read_from(source)?; let note_path = MerklePath::read_from(source)?; - Ok(Self { origin, sub_hash, note_root, note_path }) + Ok(Self { location, note_path }) } } diff --git a/objects/src/notes/metadata.rs b/objects/src/notes/metadata.rs index 77e06859c..36c3875d2 100644 --- a/objects/src/notes/metadata.rs +++ b/objects/src/notes/metadata.rs @@ -1,8 +1,8 @@ use alloc::string::ToString; use super::{ - AccountId, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, NoteError, - NoteTag, NoteType, Serializable, Word, + execution_hint::NoteExecutionHint, AccountId, ByteReader, ByteWriter, Deserializable, + DeserializationError, Felt, NoteError, NoteTag, NoteType, Serializable, Word, }; // NOTE METADATA @@ -15,7 +15,6 @@ use super::{ /// - For off-chain notes, the most significant bit of the tag must be 0. /// - For public notes, the second most significant bit of the tag must be 0. /// - For encrypted notes, two most significant bits of the tag must be 00. -/// #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct NoteMetadata { @@ -30,6 +29,9 @@ pub struct NoteMetadata { /// An arbitrary user-defined value. aux: Felt, + + /// Specifies when a note is ready to be consumed. + execution_hint: NoteExecutionHint, } impl NoteMetadata { @@ -41,10 +43,17 @@ impl NoteMetadata { sender: AccountId, note_type: NoteType, tag: NoteTag, + execution_hint: NoteExecutionHint, aux: Felt, ) -> Result { let tag = tag.validate(note_type)?; - Ok(Self { sender, note_type, tag, aux }) + Ok(Self { + sender, + note_type, + tag, + aux, + execution_hint, + }) } /// Returns the account which created the note. @@ -62,14 +71,19 @@ impl NoteMetadata { self.tag } + /// Returns the execution hint associated with the note. + pub fn execution_hint(&self) -> NoteExecutionHint { + self.execution_hint + } + /// Returns the note's aux field. pub fn aux(&self) -> Felt { self.aux } - /// Returns `true` if the note is off-chain. - pub fn is_offchain(&self) -> bool { - self.note_type == NoteType::OffChain + /// Returns `true` if the note is private. + pub fn is_private(&self) -> bool { + self.note_type == NoteType::Private } } @@ -84,7 +98,7 @@ impl From<&NoteMetadata> for Word { let mut elements = Word::default(); elements[0] = metadata.tag.inner().into(); elements[1] = metadata.sender.into(); - elements[2] = metadata.note_type.into(); + elements[2] = Felt::new(merge_type_and_hint(metadata.note_type, metadata.execution_hint)); elements[3] = metadata.aux; elements } @@ -95,11 +109,12 @@ impl TryFrom for NoteMetadata { fn try_from(elements: Word) -> Result { let sender = elements[1].try_into().map_err(NoteError::InvalidNoteSender)?; - let note_type = elements[2].try_into()?; + let (note_type, note_execution_hint) = unmerge_type_and_hint(elements[2].into())?; let tag: u64 = elements[0].into(); let tag: u32 = tag.try_into().map_err(|_| NoteError::InconsistentNoteTag(note_type, tag))?; - Self::new(sender, note_type, tag.into(), elements[3]) + + Self::new(sender, note_type, tag.into(), note_execution_hint, elements[3]) } } @@ -109,7 +124,7 @@ impl TryFrom for NoteMetadata { impl Serializable for NoteMetadata { fn write_into(&self, target: &mut W) { self.sender.write_into(target); - self.note_type.write_into(target); + target.write_u64(merge_type_and_hint(self.note_type, self.execution_hint)); self.tag.write_into(target); self.aux.write_into(target); } @@ -118,11 +133,87 @@ impl Serializable for NoteMetadata { impl Deserializable for NoteMetadata { fn read_from(source: &mut R) -> Result { let sender = AccountId::read_from(source)?; - let note_type = NoteType::read_from(source)?; + let (note_type, note_execution_hint) = unmerge_type_and_hint(source.read_u64()?) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; let tag = NoteTag::read_from(source)?; let aux = Felt::read_from(source)?; - Self::new(sender, note_type, tag, aux) + Self::new(sender, note_type, tag, note_execution_hint, aux) .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Encodes `note_type` and `note_execution_hint` into a [u64] such that the resulting number has +/// the following structure (from most significant bit to the least significant bit): +/// +/// - Bits 39 to 38 (2 bits): NoteType +/// - Bits 37 to 6 (32 bits): NoteExecutionHint payload +/// - Bits 5 to 0 (6 bits): NoteExecutionHint tag +fn merge_type_and_hint(note_type: NoteType, note_execution_hint: NoteExecutionHint) -> u64 { + let type_nibble = note_type as u64 & 0b11; + let (tag_nibble, payload_u32) = note_execution_hint.into_parts(); + + let payload_section = payload_u32 as u64; + let tag_section = (tag_nibble as u64) & 0b111111; + + (type_nibble << 38) | (payload_section << 6) | tag_section +} + +fn unmerge_type_and_hint(value: u64) -> Result<(NoteType, NoteExecutionHint), NoteError> { + let high_nibble = ((value >> 38) & 0b11) as u8; + let tag_byte = (value & 0b111111) as u8; + let payload_u32 = (value >> 6 & 0xffffffff) as u32; + + let note_type = NoteType::try_from(high_nibble)?; + let note_execution_hint = NoteExecutionHint::from_parts(tag_byte, payload_u32)?; + + Ok((note_type, note_execution_hint)) +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_merge_and_unmerge() { + let note_type = NoteType::Public; + let note_execution_hint = NoteExecutionHint::OnBlockSlot { + epoch_len: 10, + slot_len: 11, + slot_offset: 12, + }; + + let merged_value = merge_type_and_hint(note_type, note_execution_hint); + let (extracted_note_type, extracted_note_execution_hint) = + unmerge_type_and_hint(merged_value).unwrap(); + + assert_eq!(note_type, extracted_note_type); + assert_eq!(note_execution_hint, extracted_note_execution_hint); + + let note_type = NoteType::Private; + let note_execution_hint = NoteExecutionHint::Always; + + let merged_value = merge_type_and_hint(note_type, note_execution_hint); + let (extracted_note_type, extracted_note_execution_hint) = + unmerge_type_and_hint(merged_value).unwrap(); + + assert_eq!(note_type, extracted_note_type); + assert_eq!(note_execution_hint, extracted_note_execution_hint); + + let note_type = NoteType::Private; + let note_execution_hint = NoteExecutionHint::None; + + let merged_value = merge_type_and_hint(note_type, note_execution_hint); + let (extracted_note_type, extracted_note_execution_hint) = + unmerge_type_and_hint(merged_value).unwrap(); + assert_eq!(note_type, extracted_note_type); + assert_eq!(note_execution_hint, extracted_note_execution_hint); + } +} diff --git a/objects/src/notes/mod.rs b/objects/src/notes/mod.rs index e7dd6aca6..528be727a 100644 --- a/objects/src/notes/mod.rs +++ b/objects/src/notes/mod.rs @@ -7,11 +7,8 @@ use miden_crypto::{ use vm_processor::DeserializationError; use crate::{ - accounts::AccountId, - assembly::{Assembler, AssemblyContext, ProgramAst}, - assets::Asset, - vm::CodeBlock, - Digest, Felt, Hasher, NoteError, NOTE_TREE_DEPTH, WORD_SIZE, ZERO, + accounts::AccountId, assets::Asset, Digest, Felt, Hasher, NoteError, NOTE_TREE_DEPTH, + WORD_SIZE, ZERO, }; mod assets; @@ -29,11 +26,14 @@ pub use inputs::NoteInputs; mod metadata; pub use metadata::NoteMetadata; +mod execution_hint; +pub use execution_hint::NoteExecutionHint; + mod note_id; pub use note_id::NoteId; mod note_tag; -pub use note_tag::{NoteExecutionHint, NoteTag}; +pub use note_tag::{NoteExecutionMode, NoteTag}; mod note_type; pub use note_type::NoteType; @@ -41,8 +41,8 @@ pub use note_type::NoteType; mod nullifier; pub use nullifier::Nullifier; -mod origin; -pub use origin::{NoteInclusionProof, NoteOrigin}; +mod location; +pub use location::{NoteInclusionProof, NoteLocation}; mod partial; pub use partial::PartialNote; @@ -60,6 +60,7 @@ pub use file::NoteFile; // ================================================================================================ /// The depth of the leafs in the note Merkle tree used to commit to notes produced in a block. +/// /// This is equal `NOTE_TREE_DEPTH + 1`. In the kernel we do not authenticate leaf data directly /// but rather authenticate hash(left_leaf, right_leaf). pub const NOTE_LEAF_DEPTH: u8 = NOTE_TREE_DEPTH + 1; diff --git a/objects/src/notes/note_id.rs b/objects/src/notes/note_id.rs index c4a8b8f20..a37327874 100644 --- a/objects/src/notes/note_id.rs +++ b/objects/src/notes/note_id.rs @@ -22,8 +22,8 @@ use crate::utils::{ /// /// This achieves the following properties: /// - Every note can be reduced to a single unique ID. -/// - To compute a note ID, we do not need to know the note's serial_num. Knowing the hash -/// of the serial_num (as well as script hash, input hash, and note assets) is sufficient. +/// - To compute a note ID, we do not need to know the note's serial_num. Knowing the hash of the +/// serial_num (as well as script hash, input hash, and note assets) is sufficient. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct NoteId(Digest); diff --git a/objects/src/notes/note_tag.rs b/objects/src/notes/note_tag.rs index a5dbad0b8..9885fffc0 100644 --- a/objects/src/notes/note_tag.rs +++ b/objects/src/notes/note_tag.rs @@ -13,14 +13,14 @@ const NETWORK_EXECUTION: u8 = 0; const LOCAL_EXECUTION: u8 = 1; // The 2 most significant bits are set to `0b11` -const LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED: u32 = 0xC0000000; +const LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED: u32 = 0xc0000000; // The 2 most significant bits are set to `0b10` const PUBLIC_USECASE: u32 = 0x80000000; /// [super::Note]'s execution mode hints. /// /// The execution hints are _not_ enforced, therefore function only as hints. For example, if a -/// note's tag is created with the [NoteExecutionHint::Network], further validation is necessary to +/// note's tag is created with the [NoteExecutionMode::Network], further validation is necessary to /// check the account_id is known, that the account's state is on-chain, and the account is /// controlled by the network. /// @@ -29,7 +29,7 @@ const PUBLIC_USECASE: u32 = 0x80000000; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] -pub enum NoteExecutionHint { +pub enum NoteExecutionMode { Network = NETWORK_EXECUTION, Local = LOCAL_EXECUTION, } @@ -51,12 +51,12 @@ pub enum NoteExecutionHint { /// /// Where: /// -/// - [NoteExecutionHint] is set to [NoteExecutionHint::Network] to hint a [super::Note] should be +/// - [NoteExecutionMode] is set to [NoteExecutionMode::Network] to hint a [super::Note] should be /// consumed by the network. These notes will be further validated and if possible consumed by it. /// - Target describes how to further interpret the bits in the tag. For tags with a specific -/// target, the rest of the tag is interpreted as an account_id. For use case values, the meaning of -/// the rest of the tag is not specified by the protocol and can be used by applications built on -/// top of the rollup. +/// target, the rest of the tag is interpreted as an account_id. For use case values, the meaning +/// of the rest of the tag is not specified by the protocol and can be used by applications built +/// on top of the rollup. /// /// The note type is the only value enforced by the protocol. The rationale is that any note /// intended to be consumed by the network must be public to have all the details available. The @@ -70,7 +70,7 @@ impl NoteTag { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new [NoteTag] instantiated from the specified account ID and execution hint. + /// Returns a new [NoteTag] instantiated from the specified account ID and execution mode. /// /// The tag is constructed as follows: /// @@ -85,20 +85,19 @@ impl NoteTag { /// # Errors /// /// This will return an error if the account_id is not for an on-chain account and the execution - /// hint is set to [NoteExecutionHint::Network]. - /// + /// hint is set to [NoteExecutionMode::Network]. pub fn from_account_id( account_id: AccountId, - execution: NoteExecutionHint, + execution: NoteExecutionMode, ) -> Result { match execution { - NoteExecutionHint::Local => { + NoteExecutionMode::Local => { let id: u64 = account_id.into(); // select 14 most significant bits of the account ID and shift them right by 2 bits - let high_bits = (id >> 34) as u32 & 0xFFFF0000; + let high_bits = (id >> 34) as u32 & 0xffff0000; Ok(Self(high_bits | LOCAL_EXECUTION_WITH_ALL_NOTE_TYPES_ALLOWED)) }, - NoteExecutionHint::Network => { + NoteExecutionMode::Network => { if !account_id.is_on_chain() { Err(NoteError::NetworkExecutionRequiresOnChainAccount) } else { @@ -127,15 +126,15 @@ impl NoteTag { pub fn for_public_use_case( use_case_id: u16, payload: u16, - execution: NoteExecutionHint, + execution: NoteExecutionMode, ) -> Result { if (use_case_id >> 14) != 0 { return Err(NoteError::InvalidNoteTagUseCase(use_case_id)); } let execution_bits = match execution { - NoteExecutionHint::Local => PUBLIC_USECASE, // high bits set to `0b10` - NoteExecutionHint::Network => 0x40000000, // high bits set to `0b01` + NoteExecutionMode::Local => PUBLIC_USECASE, // high bits set to `0b10` + NoteExecutionMode::Network => 0x40000000, // high bits set to `0b01` }; let use_case_bits = (use_case_id as u32) << 16; @@ -146,7 +145,7 @@ impl NoteTag { /// Returns a new [NoteTag] instantiated for a custom local use case. /// - /// The local use_case tag is the only tag type that allows for [NoteType::OffChain] notes. + /// The local use_case tag is the only tag type that allows for [NoteType::Private] notes. /// /// The two high bits are set to the `b11`, the next 14 bits are set to the `use_case_id`, and /// the low 16 bits are set to `payload`. @@ -182,13 +181,13 @@ impl NoteTag { /// If the most significant bit of the tag is 0 or the 3 most significant bits are equal to /// 0b101, the note is intended for local execution; otherwise, the note is intended for /// network execution. - pub fn execution_hint(&self) -> NoteExecutionHint { + pub fn execution_hint(&self) -> NoteExecutionMode { let first_bit = self.0 >> 31; if first_bit == (LOCAL_EXECUTION as u32) { - NoteExecutionHint::Local + NoteExecutionMode::Local } else { - NoteExecutionHint::Network + NoteExecutionMode::Network } } @@ -203,11 +202,11 @@ impl NoteTag { /// Returns an error if this tag is not consistent with the specified note type, and self /// otherwise. pub fn validate(&self, note_type: NoteType) -> Result { - if self.execution_hint() == NoteExecutionHint::Network && note_type != NoteType::Public { + if self.execution_hint() == NoteExecutionMode::Network && note_type != NoteType::Public { return Err(NoteError::NetworkExecutionRequiresPublicNote(note_type)); } - let is_public_use_case = (self.0 & 0xC0000000) == PUBLIC_USECASE; + let is_public_use_case = (self.0 & 0xc0000000) == PUBLIC_USECASE; if is_public_use_case && note_type != NoteType::Public { Err(NoteError::PublicUseCaseRequiresPublicNote(note_type)) } else { @@ -289,7 +288,7 @@ impl Deserializable for NoteTag { #[cfg(test)] mod tests { - use super::{NoteExecutionHint, NoteTag}; + use super::{NoteExecutionMode, NoteTag}; use crate::{ accounts::{ account_id::testing::{ @@ -333,16 +332,16 @@ mod tests { for off_chain in off_chain_accounts { assert!( - NoteTag::from_account_id(off_chain, NoteExecutionHint::Network).is_err(), + NoteTag::from_account_id(off_chain, NoteExecutionMode::Network).is_err(), "Tag generation must fail if network execution and off-chain account id are mixed" ); } for on_chain in on_chain_accounts { - let tag = NoteTag::from_account_id(on_chain, NoteExecutionHint::Network) + let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Network) .expect("Tag generation must work with network exeuction and on-chain accounts"); assert!(tag.is_single_target()); - assert_eq!(tag.execution_hint(), NoteExecutionHint::Network); + assert_eq!(tag.execution_hint(), NoteExecutionMode::Network); assert_eq!( tag.validate(NoteType::Public), @@ -350,8 +349,8 @@ mod tests { "Network execution requires public notes" ); assert_eq!( - tag.validate(NoteType::OffChain), - Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::OffChain)) + tag.validate(NoteType::Private), + Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private)) ); assert_eq!( tag.validate(NoteType::Encrypted), @@ -360,10 +359,10 @@ mod tests { } for off_chain in off_chain_accounts { - let tag = NoteTag::from_account_id(off_chain, NoteExecutionHint::Local) + let tag = NoteTag::from_account_id(off_chain, NoteExecutionMode::Local) .expect("Tag generation must work with network execution and off-chain account id"); assert!(!tag.is_single_target()); - assert_eq!(tag.execution_hint(), NoteExecutionHint::Local); + assert_eq!(tag.execution_hint(), NoteExecutionMode::Local); assert_eq!( tag.validate(NoteType::Public), @@ -371,9 +370,9 @@ mod tests { "Local execution supports public notes" ); assert_eq!( - tag.validate(NoteType::OffChain), + tag.validate(NoteType::Private), Ok(tag), - "Local execution supports offchain notes" + "Local execution supports private notes" ); assert_eq!( tag.validate(NoteType::Encrypted), @@ -383,10 +382,10 @@ mod tests { } for on_chain in on_chain_accounts { - let tag = NoteTag::from_account_id(on_chain, NoteExecutionHint::Local) + let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Local) .expect("Tag generation must work with network exeuction and on-chain accounts"); assert!(!tag.is_single_target()); - assert_eq!(tag.execution_hint(), NoteExecutionHint::Local); + assert_eq!(tag.execution_hint(), NoteExecutionMode::Local); assert_eq!( tag.validate(NoteType::Public), @@ -394,9 +393,9 @@ mod tests { "Local execution supports public notes" ); assert_eq!( - tag.validate(NoteType::OffChain), + tag.validate(NoteType::Private), Ok(tag), - "Local execution supports offchain notes" + "Local execution supports private notes" ); assert_eq!( tag.validate(NoteType::Encrypted), @@ -414,17 +413,17 @@ mod tests { AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN).unwrap(); assert_eq!( - NoteTag::from_account_id(on_chain, NoteExecutionHint::Network), + NoteTag::from_account_id(on_chain, NoteExecutionMode::Network), Ok(NoteTag(0b00000000_00000000_00000000_00000000)) ); - assert!(NoteTag::from_account_id(off_chain, NoteExecutionHint::Network).is_err()); + assert!(NoteTag::from_account_id(off_chain, NoteExecutionMode::Network).is_err()); assert_eq!( - NoteTag::from_account_id(off_chain, NoteExecutionHint::Local), + NoteTag::from_account_id(off_chain, NoteExecutionMode::Local), Ok(NoteTag(0b11100100_00000000_00000000_00000000)) ); assert_eq!( - NoteTag::from_account_id(on_chain, NoteExecutionHint::Local), + NoteTag::from_account_id(on_chain, NoteExecutionMode::Local), Ok(NoteTag(0b11000000_00000000_00000000_00000000)) ); } @@ -433,56 +432,56 @@ mod tests { fn test_for_public_use_case() { // NETWORK // ---------------------------------------------------------------------------------------- - let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionHint::Network); + let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Network); assert_eq!(tag, Ok(NoteTag(0b01000000_00000000_00000000_00000000))); let tag = tag.unwrap(); assert_eq!(tag.validate(NoteType::Public), Ok(tag)); assert_eq!( - tag.validate(NoteType::OffChain), - Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::OffChain)) + tag.validate(NoteType::Private), + Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Private)) ); assert_eq!( tag.validate(NoteType::Encrypted), Err(NoteError::NetworkExecutionRequiresPublicNote(NoteType::Encrypted)) ); - let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionHint::Network); + let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Network); assert_eq!(tag, Ok(NoteTag(0b01000000_00000001_00000000_00000000))); - let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionHint::Network); + let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Network); assert_eq!(tag, Ok(NoteTag(0b01000000_00000000_00000000_00000001))); - let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionHint::Network); + let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Network); assert_eq!(tag, Ok(NoteTag(0b01100000_00000000_00000000_00000000))); // LOCAL // ---------------------------------------------------------------------------------------- - let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionHint::Local); + let tag = NoteTag::for_public_use_case(0b0, 0b0, NoteExecutionMode::Local); assert_eq!(tag, Ok(NoteTag(0b10000000_00000000_00000000_00000000))); let tag = tag.unwrap(); assert_eq!(tag.validate(NoteType::Public), Ok(tag)); assert_eq!( - tag.validate(NoteType::OffChain), - Err(NoteError::PublicUseCaseRequiresPublicNote(NoteType::OffChain)) + tag.validate(NoteType::Private), + Err(NoteError::PublicUseCaseRequiresPublicNote(NoteType::Private)) ); assert_eq!( tag.validate(NoteType::Encrypted), Err(NoteError::PublicUseCaseRequiresPublicNote(NoteType::Encrypted)) ); - let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionHint::Local); + let tag = NoteTag::for_public_use_case(0b0, 0b1, NoteExecutionMode::Local); assert_eq!(tag, Ok(NoteTag(0b10000000_00000000_00000000_00000001))); - let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionHint::Local); + let tag = NoteTag::for_public_use_case(0b1, 0b0, NoteExecutionMode::Local); assert_eq!(tag, Ok(NoteTag(0b10000000_00000001_00000000_00000000))); - let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionHint::Local); + let tag = NoteTag::for_public_use_case(1 << 13, 0b0, NoteExecutionMode::Local); assert_eq!(tag, Ok(NoteTag(0b10100000_00000000_00000000_00000000))); - assert!(NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionHint::Local).is_err()); - assert!(NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionHint::Local).is_err()); + assert!(NoteTag::for_public_use_case(1 << 15, 0b0, NoteExecutionMode::Local).is_err()); + assert!(NoteTag::for_public_use_case(1 << 14, 0b0, NoteExecutionMode::Local).is_err()); } #[test] @@ -497,9 +496,9 @@ mod tests { "Local execution supports private notes" ); assert_eq!( - tag.validate(NoteType::OffChain), + tag.validate(NoteType::Private), Ok(tag), - "Local execution supports offchain notes" + "Local execution supports private notes" ); assert_eq!( tag.validate(NoteType::Encrypted), diff --git a/objects/src/notes/note_type.rs b/objects/src/notes/note_type.rs index 18eb48040..62cb4e845 100644 --- a/objects/src/notes/note_type.rs +++ b/objects/src/notes/note_type.rs @@ -8,7 +8,7 @@ use crate::{ // Keep these masks in sync with `miden-lib/asm/miden/kernels/tx/tx.masm` const PUBLIC: u8 = 0b01; -const OFF_CHAIN: u8 = 0b10; +const PRIVATE: u8 = 0b10; const ENCRYPTED: u8 = 0b11; // NOTE TYPE @@ -19,7 +19,7 @@ const ENCRYPTED: u8 = 0b11; #[repr(u8)] pub enum NoteType { /// Notes with this type have only their hash published to the network. - OffChain = OFF_CHAIN, + Private = PRIVATE, /// Notes with type are shared with the network encrypted. Encrypted = ENCRYPTED, @@ -45,7 +45,7 @@ impl TryFrom for NoteType { fn try_from(value: u8) -> Result { match value { - OFF_CHAIN => Ok(NoteType::OffChain), + PRIVATE => Ok(NoteType::Private), ENCRYPTED => Ok(NoteType::Encrypted), PUBLIC => Ok(NoteType::Public), _ => Err(NoteError::InvalidNoteTypeValue(value.into())), @@ -100,7 +100,7 @@ impl Deserializable for NoteType { let discriminat = u8::read_from(source)?; let note_type = match discriminat { - OFF_CHAIN => NoteType::OffChain, + PRIVATE => NoteType::Private, ENCRYPTED => NoteType::Encrypted, PUBLIC => NoteType::Public, v => { diff --git a/objects/src/notes/recipient.rs b/objects/src/notes/recipient.rs index bff590b7e..afd3e7e51 100644 --- a/objects/src/notes/recipient.rs +++ b/objects/src/notes/recipient.rs @@ -18,7 +18,6 @@ use super::{ /// Recipient is computed as: /// /// > hash(hash(hash(serial_num, [0; 4]), script_hash), input_hash) -/// #[derive(Clone, Debug, PartialEq, Eq)] pub struct NoteRecipient { serial_num: Word, diff --git a/objects/src/notes/script.rs b/objects/src/notes/script.rs index c77896fd9..9f8b6b960 100644 --- a/objects/src/notes/script.rs +++ b/objects/src/notes/script.rs @@ -1,18 +1,16 @@ -use alloc::vec::Vec; +use alloc::{string::ToString, sync::Arc, vec::Vec}; -use assembly::ast::AstSerdeOptions; -use miden_crypto::Felt; - -use super::{Assembler, AssemblyContext, CodeBlock, Digest, NoteError, ProgramAst}; -use crate::utils::serde::{ - ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, +use assembly::{Assembler, Compile}; +use vm_core::{ + mast::{MastForest, MastNodeId}, + Program, }; -// CONSTANTS -// ================================================================================================ - -/// Default serialization options for script code AST. -const CODE_SERDE_OPTIONS: AstSerdeOptions = AstSerdeOptions::new(true); +use super::{Digest, Felt}; +use crate::{ + utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, + NoteError, +}; // NOTE SCRIPT // ================================================================================================ @@ -23,32 +21,49 @@ const CODE_SERDE_OPTIONS: AstSerdeOptions = AstSerdeOptions::new(true); /// it defines the rules and side effects of consuming a given note. #[derive(Debug, Clone, PartialEq, Eq)] pub struct NoteScript { - hash: Digest, - code: ProgramAst, + mast: Arc, + entrypoint: MastNodeId, } impl NoteScript { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new [NoteScript] instantiated from the provided program and compiled with the - /// provided assembler. The compiled code block is also returned. + /// Returns a new [NoteScript] instantiated from the provided program. + pub fn new(code: Program) -> Self { + Self { + entrypoint: code.entrypoint(), + mast: Arc::new(code.into()), + } + } + + /// Returns a new [NoteScript] compiled from the provided source code using the specified + /// assembler. /// /// # Errors - /// Returns an error if the compilation of the provided program fails. - pub fn new(code: ProgramAst, assembler: &Assembler) -> Result<(Self, CodeBlock), NoteError> { - let code_block = assembler - .compile_in_context(&code, &mut AssemblyContext::for_program(Some(&code))) - .map_err(NoteError::ScriptCompilationError)?; - Ok((Self { hash: code_block.hash(), code }, code_block)) + /// Returns an error if the compilation of the provided source code fails. + pub fn compile(source_code: impl Compile, assembler: Assembler) -> Result { + let program = assembler + .assemble_program(source_code) + .map_err(|report| NoteError::NoteScriptAssemblyError(report.to_string()))?; + Ok(Self::new(program)) + } + + /// Returns a new [NoteScript] deserialized from the provided bytes. + /// + /// # Errors + /// Returns an error if note script deserialization fails. + pub fn from_bytes(bytes: &[u8]) -> Result { + Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError) } /// Returns a new [NoteScript] instantiated from the provided components. /// - /// **Note**: this function assumes that the specified hash results from the compilation of the - /// provided program, but this is not checked. - pub fn from_parts(code: ProgramAst, hash: Digest) -> Self { - Self { code, hash } + /// # Panics + /// Panics if the specified entrypoint is not in the provided MAST forest. + pub fn from_parts(mast: Arc, entrypoint: MastNodeId) -> Self { + assert!(mast.get_node_by_id(entrypoint).is_some()); + Self { mast, entrypoint } } // PUBLIC ACCESSORS @@ -56,12 +71,12 @@ impl NoteScript { /// Returns MAST root of this note script. pub fn hash(&self) -> Digest { - self.hash + self.mast[self.entrypoint].digest() } - /// Returns the AST of this note script. - pub fn code(&self) -> &ProgramAst { - &self.code + /// Returns a reference to the [MastForest] backing this note script. + pub fn mast(&self) -> Arc { + self.mast.clone() } } @@ -69,19 +84,19 @@ impl NoteScript { // ================================================================================================ impl From<&NoteScript> for Vec { - fn from(value: &NoteScript) -> Self { - let mut bytes = value.code.to_bytes(AstSerdeOptions { serialize_imports: true }); + fn from(script: &NoteScript) -> Self { + let mut bytes = script.mast.to_bytes(); let len = bytes.len(); // Pad the data so that it can be encoded with u32 let missing = if len % 4 > 0 { 4 - (len % 4) } else { 0 }; bytes.resize(bytes.len() + missing, 0); - let final_size = 5 + bytes.len(); + let final_size = 2 + bytes.len(); let mut result = Vec::with_capacity(final_size); // Push the length, this is used to remove the padding later - result.extend(value.hash); + result.push(Felt::from(script.entrypoint.as_u32())); result.push(Felt::new(len as u64)); // A Felt can not represent all u64 values, so the data is encoded using u32. @@ -111,25 +126,24 @@ impl From for Vec { impl TryFrom<&[Felt]> for NoteScript { type Error = DeserializationError; - fn try_from(value: &[Felt]) -> Result { - if value.len() < 5 { + fn try_from(elements: &[Felt]) -> Result { + if elements.len() < 2 { return Err(DeserializationError::UnexpectedEOF); } - let hash = Digest::new([value[0], value[1], value[2], value[3]]); - let len = value[4].as_int(); - let mut data = Vec::with_capacity(value.len() * 4); + let entrypoint: u32 = elements[0].try_into().map_err(DeserializationError::InvalidValue)?; + let len = elements[1].as_int(); + let mut data = Vec::with_capacity(elements.len() * 4); - for felt in &value[5..] { - let v = u32::try_from(felt.as_int()) - .map_err(|v| DeserializationError::InvalidValue(format!("{v}")))?; + for &felt in &elements[2..] { + let v: u32 = felt.try_into().map_err(DeserializationError::InvalidValue)?; data.extend(v.to_le_bytes()) } data.shrink_to(len as usize); - // TODO: validate the hash matches the code - let code = ProgramAst::from_bytes(&data)?; - Ok(NoteScript::from_parts(code, hash)) + let mast = MastForest::read_from_bytes(&data)?; + let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?; + Ok(NoteScript::from_parts(Arc::new(mast), entrypoint)) } } @@ -146,16 +160,37 @@ impl TryFrom> for NoteScript { impl Serializable for NoteScript { fn write_into(&self, target: &mut W) { - self.hash.write_into(target); - self.code.write_into(target, CODE_SERDE_OPTIONS); + self.mast.write_into(target); + target.write_u32(self.entrypoint.as_u32()); } } impl Deserializable for NoteScript { fn read_from(source: &mut R) -> Result { - let hash = Digest::read_from(source)?; - let code = ProgramAst::read_from(source)?; + let mast = MastForest::read_from(source)?; + let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?; + + Ok(Self::from_parts(Arc::new(mast), entrypoint)) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::{Assembler, Felt, NoteScript, Vec}; + use crate::testing::notes::DEFAULT_NOTE_CODE; + + #[test] + fn test_note_script_to_from_felt() { + let assembler = Assembler::default(); + let tx_script_src = DEFAULT_NOTE_CODE; + let note_script = NoteScript::compile(tx_script_src, assembler).unwrap(); + + let encoded: Vec = (¬e_script).into(); + let decoded: NoteScript = encoded.try_into().unwrap(); - Ok(Self::from_parts(code, hash)) + assert_eq!(note_script, decoded); } } diff --git a/objects/src/testing/account.rs b/objects/src/testing/account.rs index a28489663..fc5fe81e2 100644 --- a/objects/src/testing/account.rs +++ b/objects/src/testing/account.rs @@ -6,13 +6,13 @@ use alloc::{ use core::fmt::Display; use assembly::Assembler; -use miden_crypto::merkle::MerkleError; +use miden_crypto::{dsa::rpo_falcon512::SecretKey, merkle::MerkleError}; use rand::Rng; use vm_core::FieldElement; use super::{ account_code::DEFAULT_ACCOUNT_CODE, - account_id::{str_to_account_code, AccountIdBuilder}, + account_id::AccountIdBuilder, constants::{self, FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}, storage::{AccountStorageBuilder, FAUCET_STORAGE_DATA_SLOT}, }; @@ -93,29 +93,31 @@ impl AccountBuilder { self } - pub fn build(mut self, assembler: &Assembler) -> Result { + pub fn build(mut self, assembler: Assembler) -> Result<(Account, Word), AccountBuilderError> { let vault = AssetVault::new(&self.assets).map_err(AccountBuilderError::AssetVaultError)?; let storage = self.storage_builder.build(); self.account_id_builder.code(&self.code); self.account_id_builder.storage_root(storage.root()); - let account_id = self.account_id_builder.build(assembler)?; - let account_code = str_to_account_code(&self.code, assembler) + let (account_id, seed) = self.account_id_builder.build(assembler.clone())?; + let account_code = AccountCode::compile(&self.code, assembler) .map_err(AccountBuilderError::AccountError)?; - Ok(Account::from_parts(account_id, vault, storage, account_code, self.nonce)) + + let account = Account::from_parts(account_id, vault, storage, account_code, self.nonce); + Ok((account, seed)) } /// Build an account using the provided `seed`. - pub fn with_seed( + pub fn build_with_seed( mut self, seed: Word, - assembler: &Assembler, + assembler: Assembler, ) -> Result { let vault = AssetVault::new(&self.assets).map_err(AccountBuilderError::AssetVaultError)?; let storage = self.storage_builder.build(); self.account_id_builder.code(&self.code); self.account_id_builder.storage_root(storage.root()); - let account_id = self.account_id_builder.with_seed(seed, assembler)?; - let account_code = str_to_account_code(&self.code, assembler) + let account_id = self.account_id_builder.with_seed(seed, assembler.clone())?; + let account_code = AccountCode::compile(&self.code, assembler) .map_err(AccountBuilderError::AccountError)?; Ok(Account::from_parts(account_id, vault, storage, account_code, self.nonce)) } @@ -123,11 +125,11 @@ impl AccountBuilder { /// Build an account using the provided `seed` and `storage`. /// /// The storage items added to this builder will added on top of `storage`. - pub fn with_seed_and_storage( + pub fn build_with_seed_and_storage( mut self, seed: Word, mut storage: AccountStorage, - assembler: &Assembler, + assembler: Assembler, ) -> Result { let vault = AssetVault::new(&self.assets).map_err(AccountBuilderError::AssetVaultError)?; let inner_storage = self.storage_builder.build(); @@ -143,11 +145,29 @@ impl AccountBuilder { self.account_id_builder.code(&self.code); self.account_id_builder.storage_root(storage.root()); - let account_id = self.account_id_builder.with_seed(seed, assembler)?; - let account_code = str_to_account_code(&self.code, assembler) + let account_id = self.account_id_builder.with_seed(seed, assembler.clone())?; + let account_code = AccountCode::compile(&self.code, assembler) .map_err(AccountBuilderError::AccountError)?; Ok(Account::from_parts(account_id, vault, storage, account_code, self.nonce)) } + + /// Build an account using the provided `seed` and `storage`. + /// This method also returns the seed and secret key generated for the account based on the + /// provided RNG. + /// + /// The storage items added to this builder will added on top of `storage`. + pub fn build_with_auth( + self, + assembler: &Assembler, + rng: &mut impl Rng, + ) -> Result<(Account, Word, SecretKey), AccountBuilderError> { + let sec_key = SecretKey::with_rng(rng); + let pub_key: Word = sec_key.public_key().into(); + + let storage_item = SlotItem::new_value(0, 0, pub_key); + let (account, seed) = self.add_storage_item(storage_item).build(assembler.clone())?; + Ok((account, seed, sec_key)) + } } #[derive(Debug)] @@ -176,8 +196,8 @@ impl std::error::Error for AccountBuilderError {} // ================================================================================================ impl Account { - /// Creates a mock account with a defined number of assets and storage - pub fn mock(account_id: u64, nonce: Felt, account_code: AccountCode) -> Self { + /// Creates a non-new mock account with a defined number of assets and storage + pub fn mock(account_id: u64, nonce: Felt, assembler: Assembler) -> Self { let account_storage = AccountStorage::mock(); let account_vault = if nonce == Felt::ZERO { @@ -186,6 +206,8 @@ impl Account { AssetVault::mock() }; + let account_code = AccountCode::mock_wallet(assembler); + let account_id = AccountId::try_from(account_id).unwrap(); Account::from_parts(account_id, account_vault, account_storage, account_code, nonce) } @@ -194,7 +216,7 @@ impl Account { account_id: u64, nonce: Felt, initial_balance: Felt, - assembler: &Assembler, + assembler: Assembler, ) -> Self { let account_storage = AccountStorage::new( vec![SlotItem { @@ -213,7 +235,7 @@ impl Account { account_id: u64, nonce: Felt, empty_reserved_slot: bool, - assembler: &Assembler, + assembler: Assembler, ) -> Self { let entries = match empty_reserved_slot { true => vec![], diff --git a/objects/src/testing/account_code.rs b/objects/src/testing/account_code.rs index b0d50bb2f..210c9eba4 100644 --- a/objects/src/testing/account_code.rs +++ b/objects/src/testing/account_code.rs @@ -1,33 +1,34 @@ -use assembly::{ast::ModuleAst, Assembler}; +use assembly::Assembler; use crate::accounts::AccountCode; // The MAST root of the default account's interface. Use these constants to interact with the // account's procedures. -const MASTS: [&str; 11] = [ - "0xbb58a032a1c1989079dcc73c279d69dcdf41dd7ee923d99dc3f86011663ec167", - "0x549d264f00f1a6e90d47284e99eab6d0f93a3d41bb5324743607b6902978a809", - "0x704ed1af80a3dae74cd4aabeb4c217924813c42334c2695a74e2702af80a4a35", - "0xc25558f483c13aa5be77de4b0987de6a3fab303146fe2fd8ab68b6be8fdcfe76", - "0x5dc65ccf6d32880a8eb47fab75b65d926b701ed80220fe5e88152efffcd656ad", - "0x73c14f65d2bab6f52eafc4397e104b3ab22a470f6b5cbc86d4aa4d3978c8b7d4", - "0x55036198d82d2af653935226c644427162f12e2a2c6b3baf007c9c6f47462872", - "0xf484a84dad7f82e8eb1d5190b43243d02d9508437ff97522e14ebf9899758faa", - "0xf17acfc7d1eff3ecadd7a17b6d91ff01af638aa9439d6c8603c55648328702ae", +const MASTS: [&str; 12] = [ "0xff06b90f849c4b262cbfbea67042c4ea017ea0e9c558848a951d44b23370bec5", "0x8ef0092134469a1330e3c468f57c7f085ce611645d09cc7516c786fefc71d794", + "0x86472c62c0f9d2f93369f793abd66127f9cf4a77d4339dcacfb118aba0cf79b6", + "0xa5e47b6219605992b497ab85404425da4b88ad58789d86ab09bea9ed0ec12897", + "0x56723c7bd5e46ce33f99f256ae1b8f4856600744191f8a18d1c572a925f41ced", + "0x6be2be94390361792f5becf18f3e916fa2458c67b809ad144d8ec5fb144ce9c3", + "0x0f0447bc4eb9a366d8158274427445fcc169949e4ab9092d45ff55c2a7753e2a", + "0x3d77d6c0727fa8c78695123bcd9413e88a5d92e72a60453557fb93dfa575c81a", + "0x383067a3ef06a0fad1f11ab7707c67c286db851cc9edece3ea53a76520a014fa", + "0x59e26d0f909ce76298f12ba6a6a4120b0cf541622128756eb572fd17dbe8732d", + "0xe55e8abaa5a3a8ff89537111b490f22983a7012e65c11ead8478f7a645ba49bd", + "0xad0d0d771f4a301c658c61366b4436a4b45b7e317d0f3ae2c76f37e1f8bd63e6", ]; -pub const ACCOUNT_SEND_ASSET_MAST_ROOT: &str = MASTS[1]; -pub const ACCOUNT_INCR_NONCE_MAST_ROOT: &str = MASTS[2]; -pub const ACCOUNT_SET_ITEM_MAST_ROOT: &str = MASTS[3]; -pub const ACCOUNT_SET_MAP_ITEM_MAST_ROOT: &str = MASTS[4]; -pub const ACCOUNT_SET_CODE_MAST_ROOT: &str = MASTS[5]; -pub const ACCOUNT_CREATE_NOTE_MAST_ROOT: &str = MASTS[6]; -pub const ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT: &str = MASTS[7]; -pub const ACCOUNT_REMOVE_ASSET_MAST_ROOT: &str = MASTS[8]; -pub const ACCOUNT_ACCOUNT_PROCEDURE_1_MAST_ROOT: &str = MASTS[9]; -pub const ACCOUNT_ACCOUNT_PROCEDURE_2_MAST_ROOT: &str = MASTS[10]; +pub const ACCOUNT_ADD_ASSET_TO_NOTE_MAST_ROOT: &str = MASTS[2]; +pub const ACCOUNT_SEND_ASSET_MAST_ROOT: &str = MASTS[8]; +pub const ACCOUNT_INCR_NONCE_MAST_ROOT: &str = MASTS[4]; +pub const ACCOUNT_SET_ITEM_MAST_ROOT: &str = MASTS[10]; +pub const ACCOUNT_SET_MAP_ITEM_MAST_ROOT: &str = MASTS[11]; +pub const ACCOUNT_SET_CODE_MAST_ROOT: &str = MASTS[9]; +pub const ACCOUNT_REMOVE_ASSET_MAST_ROOT: &str = MASTS[7]; + +pub const ACCOUNT_ACCOUNT_PROCEDURE_1_MAST_ROOT: &str = MASTS[0]; +pub const ACCOUNT_ACCOUNT_PROCEDURE_2_MAST_ROOT: &str = MASTS[1]; pub const CODE: &str = " export.foo @@ -43,36 +44,34 @@ pub const CODE: &str = " // ================================================================================================ pub const DEFAULT_ACCOUNT_CODE: &str = " - use.miden::contracts::wallets::basic->basic_wallet - use.miden::contracts::auth::basic->basic_eoa - - export.basic_wallet::receive_asset - export.basic_wallet::send_asset - export.basic_eoa::auth_tx_rpo_falcon512 + export.::miden::contracts::wallets::basic::receive_asset + export.::miden::contracts::wallets::basic::send_asset + export.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 "; pub const DEFAULT_AUTH_SCRIPT: &str = " - use.miden::contracts::auth::basic->auth_tx - begin - call.auth_tx::auth_tx_rpo_falcon512 + call.::miden::contracts::auth::basic::auth_tx_rpo_falcon512 end "; impl AccountCode { /// Creates a mock [AccountCode] that exposes wallet interface - pub fn mock_wallet(assembler: &Assembler) -> AccountCode { + pub fn mock_wallet(assembler: Assembler) -> AccountCode { let account_code = "\ use.miden::account use.miden::tx - use.miden::contracts::wallets::basic->wallet # acct proc 0 - export.wallet::receive_asset + export.::miden::contracts::wallets::basic::receive_asset # acct proc 1 - export.wallet::send_asset - + export.::miden::contracts::wallets::basic::send_asset # acct proc 2 + export.::miden::contracts::wallets::basic::create_note + # acct proc 3 + export.::miden::contracts::wallets::basic::move_asset_to_note + + # acct proc 4 export.incr_nonce push.0 swap # => [value, 0] @@ -81,7 +80,7 @@ impl AccountCode { # => [0] end - # acct proc 3 + # acct proc 5 export.set_item exec.account::set_item # => [R', V, 0, 0, 0] @@ -90,7 +89,7 @@ impl AccountCode { # => [R', V] end - # acct proc 4 + # acct proc 6 export.set_map_item exec.account::set_map_item # => [R', V, 0, 0, 0] @@ -99,84 +98,70 @@ impl AccountCode { # => [R', V] end - # acct proc 5 + # acct proc 7 export.set_code padw swapw - # => [CODE_ROOT, 0, 0, 0, 0] + # => [CODE_COMMITMENT, 0, 0, 0, 0] exec.account::set_code # => [0, 0, 0, 0] end - # acct proc 6 - export.create_note - exec.tx::create_note - # => [note_idx] - - swapw dropw swap drop - end - - # acct proc 7 + # acct proc 8 export.add_asset_to_note exec.tx::add_asset_to_note - # => [note_idx] - - swap drop swap drop swap drop + # => [ASSET, note_idx] end - # acct proc 8 + # acct proc 9 export.remove_asset exec.account::remove_asset # => [ASSET] end - # acct proc 9 + # acct proc 10 export.account_procedure_1 push.1.2 add end - # acct proc 10 + # acct proc 11 export.account_procedure_2 push.2.1 sub end "; - let account_module_ast = ModuleAst::parse(account_code).unwrap(); - let code = AccountCode::new(account_module_ast, assembler).unwrap(); + let code = AccountCode::compile(account_code, assembler).unwrap(); // Ensures the mast root constants match the latest version of the code. // // The constants will change if the library code changes, and need to be updated so that the // tests will work properly. If these asserts fail, copy the value of the code (the left // value), into the constants. // - // Comparing all the values together, in case multiple of them change, a single test run will - // detect it. + // Comparing all the values together, in case multiple of them change, a single test run + // will detect it. let current = [ - code.procedures()[0].to_hex(), - code.procedures()[1].to_hex(), - code.procedures()[2].to_hex(), - code.procedures()[3].to_hex(), - code.procedures()[4].to_hex(), - code.procedures()[5].to_hex(), - code.procedures()[6].to_hex(), - code.procedures()[7].to_hex(), - code.procedures()[8].to_hex(), - code.procedures()[9].to_hex(), - code.procedures()[10].to_hex(), + code.procedures()[0].mast_root().to_hex(), + code.procedures()[1].mast_root().to_hex(), + code.procedures()[2].mast_root().to_hex(), + code.procedures()[3].mast_root().to_hex(), + code.procedures()[4].mast_root().to_hex(), + code.procedures()[5].mast_root().to_hex(), + code.procedures()[6].mast_root().to_hex(), + code.procedures()[7].mast_root().to_hex(), + code.procedures()[8].mast_root().to_hex(), + code.procedures()[9].mast_root().to_hex(), + code.procedures()[10].mast_root().to_hex(), + code.procedures()[11].mast_root().to_hex(), ]; - assert!(current == MASTS, "const MASTS: [&str; 11] = {:?};", current); + assert!(current == MASTS, "const MASTS: [&str; 12] = {:?};", current); code } /// Creates a mock [AccountCode] with default assembler and mock code pub fn mock() -> AccountCode { - let mut module = ModuleAst::parse(CODE).unwrap(); - // clears are needed since they're not serialized for account code - module.clear_imports(); - module.clear_locations(); - AccountCode::new(module, &Assembler::default()).unwrap() + AccountCode::compile(CODE, Assembler::default()).unwrap() } } diff --git a/objects/src/testing/account_id.rs b/objects/src/testing/account_id.rs index 78b46769a..01c0a3ede 100644 --- a/objects/src/testing/account_id.rs +++ b/objects/src/testing/account_id.rs @@ -1,12 +1,12 @@ use alloc::string::{String, ToString}; -use assembly::{ast::ModuleAst, Assembler}; +use assembly::Assembler; use rand::Rng; use super::{account::AccountBuilderError, account_code::DEFAULT_ACCOUNT_CODE}; use crate::{ accounts::{AccountCode, AccountId, AccountStorageType, AccountType}, - AccountError, Digest, Word, + Digest, Word, }; /// Builder for an `AccountId`, the builder can be configured and used multiple times. @@ -50,8 +50,11 @@ impl AccountIdBuilder { self } - pub fn build(&mut self, assembler: &Assembler) -> Result { - let (seed, code_root) = account_id_build_details( + pub fn build( + &mut self, + assembler: Assembler, + ) -> Result<(AccountId, Word), AccountBuilderError> { + let (seed, code_commitment) = account_id_build_details( &mut self.rng, &self.code, self.account_type, @@ -60,20 +63,22 @@ impl AccountIdBuilder { assembler, )?; - AccountId::new(seed, code_root, self.storage_root) - .map_err(AccountBuilderError::AccountError) + let account_id = AccountId::new(seed, code_commitment, self.storage_root) + .map_err(AccountBuilderError::AccountError)?; + + Ok((account_id, seed)) } pub fn with_seed( &mut self, seed: Word, - assembler: &Assembler, + assembler: Assembler, ) -> Result { - let code = str_to_account_code(&self.code, assembler) + let code = AccountCode::compile(&self.code, assembler) .map_err(AccountBuilderError::AccountError)?; - let code_root = code.root(); + let code_commitment = code.commitment(); - let account_id = AccountId::new(seed, code_root, self.storage_root) + let account_id = AccountId::new(seed, code_commitment, self.storage_root) .map_err(AccountBuilderError::AccountError)?; if account_id.account_type() != self.account_type { @@ -91,7 +96,7 @@ impl AccountIdBuilder { // UTILS // ================================================================================================ -/// Returns the account's seed and code root. +/// Returns the account's seed and code commitment. /// /// This compiles `code` and performs the proof-of-work to find a valid seed. pub fn account_id_build_details( @@ -100,22 +105,19 @@ pub fn account_id_build_details( account_type: AccountType, storage_type: AccountStorageType, storage_root: Digest, - assembler: &Assembler, + assembler: Assembler, ) -> Result<(Word, Digest), AccountBuilderError> { let init_seed: [u8; 32] = rng.gen(); - let code = str_to_account_code(code, assembler).map_err(AccountBuilderError::AccountError)?; - let code_root = code.root(); - let seed = - AccountId::get_account_seed(init_seed, account_type, storage_type, code_root, storage_root) - .map_err(AccountBuilderError::AccountError)?; - - Ok((seed, code_root)) -} - -pub fn str_to_account_code( - source: &str, - assembler: &Assembler, -) -> Result { - let account_module_ast = ModuleAst::parse(source).unwrap(); - AccountCode::new(account_module_ast, assembler) + let code = AccountCode::compile(code, assembler).map_err(AccountBuilderError::AccountError)?; + let code_commitment = code.commitment(); + let seed = AccountId::get_account_seed( + init_seed, + account_type, + storage_type, + code_commitment, + storage_root, + ) + .map_err(AccountBuilderError::AccountError)?; + + Ok((seed, code_commitment)) } diff --git a/objects/src/testing/assets.rs b/objects/src/testing/assets.rs index a609b9081..994954f26 100644 --- a/objects/src/testing/assets.rs +++ b/objects/src/testing/assets.rs @@ -6,7 +6,8 @@ use crate::{ AssetError, }; -/// Builder for an `NonFungibleAssetDetails`, the builder can be configured and used multiplied times. +/// Builder for an `NonFungibleAssetDetails`, the builder can be configured and used multiplied +/// times. #[derive(Debug, Clone)] pub struct NonFungibleAssetDetailsBuilder { faucet_id: AccountId, diff --git a/objects/src/testing/block.rs b/objects/src/testing/block.rs index ee2b7fdbc..d09bc5193 100644 --- a/objects/src/testing/block.rs +++ b/objects/src/testing/block.rs @@ -1,346 +1,12 @@ -use alloc::{collections::BTreeMap, vec::Vec}; -use core::fmt; +use alloc::vec::Vec; -use miden_crypto::merkle::{Mmr, PartialMmr, SimpleSmt, Smt}; -use vm_core::{utils::Serializable, Felt, Word, ZERO}; +use miden_crypto::merkle::SimpleSmt; +use vm_core::Felt; use vm_processor::Digest; #[cfg(not(target_family = "wasm"))] -use winter_rand_utils as rand; +use winter_rand_utils::{rand_array, rand_value}; -use crate::{ - accounts::{delta::AccountUpdateDetails, Account}, - block::{Block, BlockAccountUpdate, BlockNoteIndex, BlockNoteTree, NoteBatch}, - notes::{Note, NoteId, NoteInclusionProof, Nullifier}, - transaction::{ - ChainMmr, ExecutedTransaction, InputNote, InputNotes, OutputNote, ToInputNoteCommitments, - TransactionId, TransactionInputs, - }, - BlockHeader, ACCOUNT_TREE_DEPTH, -}; - -/// Initial timestamp value -const TIMESTAMP_START: u32 = 1693348223; -/// Timestamp of timestamp on each new block -const TIMESTAMP_STEP: u32 = 10; - -#[derive(Default, Debug, Clone)] -pub struct PendingObjects { - /// Account updates for the block. - updated_accounts: Vec, - - /// Note batches created in transactions in the block. - created_notes: Vec, - - /// Nullifiers produced in transactions in the block. - created_nullifiers: Vec, - - /// Transaction IDs added to the block. - transaction_ids: Vec, -} - -impl PendingObjects { - pub fn new() -> PendingObjects { - PendingObjects { - updated_accounts: vec![], - created_notes: vec![], - created_nullifiers: vec![], - transaction_ids: vec![], - } - } - - /// Creates a [BlockNoteTree] tree from the `notes`. - /// - /// The root of the tree is a commitment to all notes created in the block. The commitment - /// is not for all fields of the [Note] struct, but only for note metadata + core fields of - /// a note (i.e., vault, inputs, script, and serial number). - pub fn build_notes_tree(&self) -> BlockNoteTree { - let entries = self.created_notes.iter().enumerate().flat_map(|(batch_index, batch)| { - batch.iter().enumerate().map(move |(note_index, note)| { - (BlockNoteIndex::new(batch_index, note_index), note.id().into(), *note.metadata()) - }) - }); - - BlockNoteTree::with_entries(entries).unwrap() - } -} - -/// Structure chain data, used to build necessary openings and to construct [BlockHeader]s. -#[derive(Debug, Clone)] -pub struct MockChain { - /// An append-only structure used to represent the history of blocks produced for this chain. - chain: Mmr, - - /// History of produced blocks. - blocks: Vec, - - /// Tree containing the latest `Nullifier`'s tree. - nullifiers: Smt, - - /// Tree containing the latest hash of each account. - accounts: SimpleSmt, - - /// Objects that have not yet been finalized. - /// - /// These will become available once the block is sealed. - /// - /// Note: - /// - The [Note]s in this container do not have the `proof` set. - pending_objects: PendingObjects, - - /// NoteID |-> InputNote mapping to simplify transaction inputs retrieval - available_notes: BTreeMap, - - removed_notes: Vec, -} - -#[derive(Debug)] -pub enum MockError { - DuplicatedNullifier, - DuplicatedNote, -} - -impl fmt::Display for MockError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for MockError {} - -impl Default for MockChain { - fn default() -> Self { - Self::new() - } -} - -impl MockChain { - // CONSTRUCTORS - // ---------------------------------------------------------------------------------------- - - pub fn new() -> Self { - Self { - chain: Mmr::default(), - blocks: vec![], - nullifiers: Smt::default(), - accounts: SimpleSmt::::new().expect("depth too big for SimpleSmt"), - pending_objects: PendingObjects::new(), - available_notes: BTreeMap::new(), - removed_notes: vec![], - } - } - - pub fn add_executed_transaction(&mut self, transaction: ExecutedTransaction) { - let mut account = transaction.initial_account().clone(); - account.apply_delta(transaction.account_delta()).unwrap(); - - // disregard private accounts, so it's easier to retrieve data - let account_update_details = match account.is_new() { - true => AccountUpdateDetails::New(account.clone()), - false => AccountUpdateDetails::Delta(transaction.account_delta().clone()), - }; - - let block_account_update = BlockAccountUpdate::new( - transaction.account_id(), - account.hash(), - account_update_details, - vec![transaction.id()], - ); - self.pending_objects.updated_accounts.push(block_account_update); - - for note in transaction.input_notes().iter() { - // TODO: check that nullifiers are not duplicate - self.pending_objects.created_nullifiers.push(note.nullifier()); - self.removed_notes.push(note.id()); - } - - // TODO: check that notes are not duplicate - let output_notes: Vec = transaction.output_notes().iter().cloned().collect(); - self.pending_objects.created_notes.push(output_notes); - } - - /// Add a public [Note] to the pending objects. - /// A block has to be created to finalize the new entity. - pub fn add_note(&mut self, note: Note) { - self.pending_objects.created_notes.push(vec![OutputNote::Full(note)]); - } - - /// Mark a [Note] as consumed by inserting its nullifier into the block. - /// A block has to be created to finalize the new entity. - pub fn add_nullifier(&mut self, nullifier: Nullifier) { - self.pending_objects.created_nullifiers.push(nullifier); - } - - /// Add a new [Account] to the list of pending objects. - /// A block has to be created to finalize the new entity. - pub fn add_account(&mut self, account: Account, _seed: Option) { - self.pending_objects.updated_accounts.push(BlockAccountUpdate::new( - account.id(), - account.hash(), - AccountUpdateDetails::New(account), - vec![], - )); - } - - pub fn get_transaction_inputs( - &self, - account: Account, - account_seed: Option, - notes: &[NoteId], - ) -> TransactionInputs { - let block_header = self.blocks.last().unwrap().header(); - - let mut input_notes = vec![]; - let mut block_headers_map: BTreeMap = BTreeMap::new(); - for note in notes { - let input_note = self.available_notes.get(note).unwrap().clone(); - block_headers_map.insert( - input_note.origin().unwrap().block_num, - self.blocks - .get(input_note.origin().unwrap().block_num as usize) - .unwrap() - .header(), - ); - input_notes.push(input_note); - } - - let block_headers: Vec = block_headers_map.values().cloned().collect(); - let mmr = mmr_to_chain_mmr(&self.chain, &block_headers); - - TransactionInputs::new( - account, - account_seed, - block_header, - mmr, - InputNotes::new(input_notes).unwrap(), - ) - .unwrap() - } - - // MODIFIERS - // ---------------------------------------------------------------------------------------- - - /// Creates the next block. - /// - /// This will also make all the objects currently pending available for use. - pub fn seal_block(&mut self) -> Block { - let block_num: u32 = self.blocks.len().try_into().expect("usize to u32 failed"); - - for update in self.pending_objects.updated_accounts.iter() { - self.accounts.insert(update.account_id().into(), *update.new_state_hash()); - } - - // TODO: - // - resetting the nullifier tree once defined at the protocol level. - // - inserting only nullifier from transactions included in the batches, once the batch - // kernel has been implemented. - for nullifier in self.pending_objects.created_nullifiers.iter() { - self.nullifiers.insert(nullifier.inner(), [block_num.into(), ZERO, ZERO, ZERO]); - } - let notes_tree = self.pending_objects.build_notes_tree(); - - let version = 0; - let previous = self.blocks.last(); - let peaks = self.chain.peaks(self.chain.forest()).unwrap(); - let chain_root: Digest = peaks.hash_peaks(); - let account_root = self.accounts.root(); - let prev_hash = previous.map_or(Digest::default(), |block| block.hash()); - let nullifier_root = self.nullifiers.root(); - let note_root = notes_tree.root(); - let timestamp = - previous.map_or(TIMESTAMP_START, |block| block.header().timestamp() + TIMESTAMP_STEP); - // TODO: Implement proper tx_hash once https://github.com/0xPolygonMiden/miden-base/pull/740 is merged - let tx_hash = crate::Hasher::hash(&self.pending_objects.transaction_ids.to_bytes()); - - // TODO: Set `proof_hash` to the correct value once the kernel is available. - let proof_hash = Digest::default(); - - let header = BlockHeader::new( - version, - prev_hash, - block_num, - chain_root, - account_root, - nullifier_root, - note_root, - tx_hash, - proof_hash, - timestamp, - ); - - let block = Block::new( - header, - self.pending_objects.updated_accounts.clone(), - self.pending_objects.created_notes.clone(), - self.pending_objects.created_nullifiers.clone(), - ) - .unwrap(); - - for (batch_index, note_batch) in self.pending_objects.created_notes.iter().enumerate() { - for (note_index, note) in note_batch.iter().enumerate() { - // All note details should be OutputNote::Full at this point - match note { - OutputNote::Full(note) => { - let block_note_index = BlockNoteIndex::new(batch_index, note_index); - let note_path = notes_tree.get_note_path(block_note_index).unwrap(); - let note_inclusion_proof = NoteInclusionProof::new( - block.header().block_num(), - block.header().sub_hash(), - block.header().note_root(), - block_note_index.to_absolute_index(), - note_path, - ) - .unwrap(); - - self.available_notes.insert( - note.id(), - InputNote::authenticated(note.clone(), note_inclusion_proof), - ); - }, - _ => continue, - } - } - } - - for removed_note in self.removed_notes.iter() { - self.available_notes.remove(removed_note); - } - - self.blocks.push(block.clone()); - self.chain.add(header.hash()); - self.reset_pending(); - - block - } - - fn reset_pending(&mut self) { - self.pending_objects = PendingObjects::new(); - self.removed_notes = vec![]; - } - - // ACCESSORS - // ---------------------------------------------------------------------------------------- - - /// Get the latest [ChainMmr]. - pub fn chain(&self) -> ChainMmr { - let block_headers: Vec = self.blocks.iter().map(|b| b.header()).collect(); - mmr_to_chain_mmr(&self.chain, &block_headers) - } - - /// Get a reference to [BlockHeader] with `block_number`. - pub fn block_header(&self, block_number: usize) -> BlockHeader { - self.blocks[block_number].header() - } - - /// Get a reference to the nullifier tree. - pub fn nullifiers(&self) -> &Smt { - &self.nullifiers - } - - pub fn available_notes(&self) -> Vec { - self.available_notes.values().cloned().collect() - } -} +use crate::{accounts::Account, BlockHeader, ACCOUNT_TREE_DEPTH}; impl BlockHeader { /// Creates a mock block. The account tree is formed from the provided `accounts`, @@ -373,13 +39,13 @@ impl BlockHeader { #[cfg(not(target_family = "wasm"))] let (prev_hash, chain_root, nullifier_root, note_root, tx_hash, proof_hash, timestamp) = { - let prev_hash = rand::rand_array().into(); - let chain_root = chain_root.unwrap_or(rand::rand_array().into()); - let nullifier_root = rand::rand_array().into(); - let note_root = note_root.unwrap_or(rand::rand_array().into()); - let tx_hash = rand::rand_array().into(); - let proof_hash = rand::rand_array().into(); - let timestamp = rand::rand_value(); + let prev_hash = rand_array::().into(); + let chain_root = chain_root.unwrap_or(rand_array::().into()); + let nullifier_root = rand_array::().into(); + let note_root = note_root.unwrap_or(rand_array::().into()); + let tx_hash = rand_array::().into(); + let proof_hash = rand_array::().into(); + let timestamp = rand_value(); (prev_hash, chain_root, nullifier_root, note_root, tx_hash, proof_hash, timestamp) }; @@ -402,62 +68,3 @@ impl BlockHeader { ) } } - -pub struct MockChainBuilder { - accounts: Vec, - notes: Vec, -} - -impl Default for MockChainBuilder { - fn default() -> Self { - Self::new() - } -} - -impl MockChainBuilder { - pub fn new() -> Self { - Self { accounts: vec![], notes: vec![] } - } - - pub fn accounts(mut self, accounts: Vec) -> Self { - self.accounts = accounts; - self - } - - pub fn notes(mut self, notes: Vec) -> Self { - self.notes = notes; - self - } - - /// Returns a [MockChain] with a single block - pub fn build(self) -> MockChain { - let mut chain = MockChain::new(); - for account in self.accounts { - chain.add_account(account, None); - } - - for note in self.notes { - chain.add_note(note); - } - - chain.seal_block(); - chain - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Converts the MMR into partial MMR by copying all leaves from MMR to partial MMR. -pub fn mmr_to_chain_mmr(mmr: &Mmr, blocks: &[BlockHeader]) -> ChainMmr { - let target_forest = mmr.forest() - 1; - let mut partial_mmr = PartialMmr::from_peaks(mmr.peaks(target_forest).unwrap()); - - for i in 0..target_forest { - let node = mmr.get(i).unwrap(); - let path = mmr.open(i, target_forest).unwrap().merkle_path; - partial_mmr.track(i, node, &path).unwrap(); - } - - ChainMmr::new(partial_mmr, blocks.to_vec()).unwrap() -} diff --git a/objects/src/testing/notes.rs b/objects/src/testing/notes.rs index 6ae1f9d0a..db13e02af 100644 --- a/objects/src/testing/notes.rs +++ b/objects/src/testing/notes.rs @@ -8,19 +8,18 @@ use rand::Rng; use crate::{ accounts::AccountId, - assembly::ProgramAst, assets::Asset, notes::{ - Note, NoteAssets, NoteInclusionProof, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, + Note, NoteAssets, NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType, }, Felt, NoteError, Word, ZERO, }; -pub const DEFAULT_NOTE_CODE: &str = "\ -begin -end -"; +pub const DEFAULT_NOTE_CODE: &str = "begin nop end"; + +// NOTE BUILDER +// ================================================================================================ #[derive(Debug, Clone)] pub struct NoteBuilder { @@ -28,10 +27,10 @@ pub struct NoteBuilder { inputs: Vec, assets: Vec, note_type: NoteType, + note_execution_hint: NoteExecutionHint, serial_num: Word, tag: NoteTag, code: String, - proof: Option, aux: Felt, } @@ -49,10 +48,10 @@ impl NoteBuilder { inputs: vec![], assets: vec![], note_type: NoteType::Public, + note_execution_hint: NoteExecutionHint::None, serial_num, tag: 0.into(), code: DEFAULT_NOTE_CODE.to_string(), - proof: None, aux: ZERO, } } @@ -74,6 +73,11 @@ impl NoteBuilder { self } + pub fn note_execution_hint(mut self, note_execution_hint: NoteExecutionHint) -> Self { + self.note_execution_hint = note_execution_hint; + self + } + pub fn tag(mut self, tag: u32) -> Self { self.tag = tag.into(); self @@ -84,23 +88,35 @@ impl NoteBuilder { self } - pub fn proof(mut self, proof: NoteInclusionProof) -> Self { - self.proof = Some(proof); - self - } - pub fn aux(mut self, aux: Felt) -> Self { self.aux = aux; self } pub fn build(self, assembler: &Assembler) -> Result { - let note_ast = ProgramAst::parse(&self.code).unwrap(); - let (note_script, _) = NoteScript::new(note_ast, assembler)?; + let code = assembler.clone().assemble_program(&self.code).unwrap(); + let note_script = NoteScript::new(code); let vault = NoteAssets::new(self.assets)?; - let metadata = NoteMetadata::new(self.sender, self.note_type, self.tag, self.aux)?; + let metadata = NoteMetadata::new( + self.sender, + self.note_type, + self.tag, + self.note_execution_hint, + self.aux, + )?; let inputs = NoteInputs::new(self.inputs)?; let recipient = NoteRecipient::new(self.serial_num, note_script, inputs); Ok(Note::new(vault, metadata, recipient)) } } + +// NOTE SCRIPT +// ================================================================================================ + +impl NoteScript { + pub fn mock() -> Self { + let assembler = Assembler::default(); + let code = assembler.assemble_program(DEFAULT_NOTE_CODE).unwrap(); + Self::new(code) + } +} diff --git a/objects/src/testing/storage.rs b/objects/src/testing/storage.rs index a20d8aabc..dbc7b4a53 100644 --- a/objects/src/testing/storage.rs +++ b/objects/src/testing/storage.rs @@ -1,6 +1,7 @@ use alloc::{collections::BTreeMap, string::String, vec::Vec}; use assembly::Assembler; +use miden_crypto::EMPTY_WORD; use vm_core::{Felt, FieldElement, Word, ZERO}; use vm_processor::Digest; @@ -15,10 +16,11 @@ use crate::{ }, get_account_seed_single, Account, AccountCode, AccountDelta, AccountId, AccountStorage, AccountStorageDelta, AccountStorageType, AccountType, AccountVaultDelta, SlotItem, - StorageMap, StorageSlot, + StorageMap, StorageMapDelta, StorageSlot, }, assets::{Asset, AssetVault, FungibleAsset}, notes::NoteAssets, + AccountDeltaError, }; #[derive(Default, Debug, Clone)] @@ -56,6 +58,45 @@ impl AccountStorageBuilder { } } +// ACCOUNT STORAGE DELTA BUILDER +// ================================================================================================ + +#[derive(Clone, Debug, Default)] +pub struct AccountStorageDeltaBuilder { + slots: BTreeMap, + maps: BTreeMap, +} + +impl AccountStorageDeltaBuilder { + // MODIFIERS + // ------------------------------------------------------------------------------------------- + + pub fn add_cleared_items(mut self, items: impl IntoIterator) -> Self { + self.slots.extend(items.into_iter().map(|slot| (slot, EMPTY_WORD))); + self + } + + pub fn add_updated_items(mut self, items: impl IntoIterator) -> Self { + self.slots.extend(items); + self + } + + pub fn add_updated_maps( + mut self, + items: impl IntoIterator, + ) -> Self { + self.maps.extend(items); + self + } + + // BUILDERS + // ------------------------------------------------------------------------------------------- + + pub fn build(self) -> Result { + AccountStorageDelta::new(self.slots, self.maps) + } +} + // ACCOUNT STORAGE UTILS // ================================================================================================ @@ -139,7 +180,7 @@ pub enum AccountSeedType { /// Returns the account id and seed for the specified account type. pub fn generate_account_seed( account_seed_type: AccountSeedType, - assembler: &Assembler, + assembler: Assembler, ) -> (AccountId, Word) { let init_seed: [u8; 32] = Default::default(); @@ -184,7 +225,7 @@ pub fn generate_account_seed( Account::mock( ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, Felt::ZERO, - AccountCode::mock_wallet(assembler), + assembler, ), AccountType::RegularAccountUpdatableCode, ), @@ -192,7 +233,7 @@ pub fn generate_account_seed( Account::mock( ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, Felt::ZERO, - AccountCode::mock_wallet(assembler), + assembler, ), AccountType::RegularAccountUpdatableCode, ), @@ -202,12 +243,13 @@ pub fn generate_account_seed( init_seed, account_type, AccountStorageType::OnChain, - account.code().root(), + account.code().commitment(), account.storage().root(), ) .unwrap(); - let account_id = AccountId::new(seed, account.code().root(), account.storage().root()).unwrap(); + let account_id = + AccountId::new(seed, account.code().commitment(), account.storage().root()).unwrap(); (account_id, seed) } @@ -239,7 +281,7 @@ pub fn build_account_delta( nonce: Felt, storage_delta: AccountStorageDelta, ) -> AccountDelta { - let vault_delta = AccountVaultDelta { added_assets, removed_assets }; + let vault_delta = AccountVaultDelta::from_iters(added_assets, removed_assets); AccountDelta::new(storage_delta, vault_delta, Some(nonce)).unwrap() } diff --git a/objects/src/transaction/executed_tx.rs b/objects/src/transaction/executed_tx.rs index 38da4c20c..954bcf815 100644 --- a/objects/src/transaction/executed_tx.rs +++ b/objects/src/transaction/executed_tx.rs @@ -1,8 +1,9 @@ +use alloc::vec::Vec; use core::cell::OnceCell; use super::{ Account, AccountDelta, AccountId, AccountStub, AdviceInputs, BlockHeader, InputNote, - InputNotes, OutputNotes, Program, TransactionArgs, TransactionId, TransactionInputs, + InputNotes, NoteId, OutputNotes, TransactionArgs, TransactionId, TransactionInputs, TransactionOutputs, TransactionWitness, }; @@ -22,12 +23,12 @@ use super::{ #[derive(Debug, Clone)] pub struct ExecutedTransaction { id: OnceCell, - program: Program, tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, account_delta: AccountDelta, tx_args: TransactionArgs, advice_witness: AdviceInputs, + tx_measurements: TransactionMeasurements, } impl ExecutedTransaction { @@ -39,24 +40,24 @@ impl ExecutedTransaction { /// # Panics /// Panics if input and output account IDs are not the same. pub fn new( - program: Program, tx_inputs: TransactionInputs, tx_outputs: TransactionOutputs, account_delta: AccountDelta, tx_args: TransactionArgs, advice_witness: AdviceInputs, + tx_measurements: TransactionMeasurements, ) -> Self { // make sure account IDs are consistent across transaction inputs and outputs assert_eq!(tx_inputs.account().id(), tx_outputs.account.id()); Self { id: OnceCell::new(), - program, tx_inputs, tx_outputs, account_delta, tx_args, advice_witness, + tx_measurements, } } @@ -68,11 +69,6 @@ impl ExecutedTransaction { *self.id.get_or_init(|| self.into()) } - /// Returns a reference the program defining this transaction. - pub fn program(&self) -> &Program { - &self.program - } - /// Returns the ID of the account against which this transaction was executed. pub fn account_id(&self) -> AccountId { self.initial_account().id() @@ -128,21 +124,42 @@ impl ExecutedTransaction { // -------------------------------------------------------------------------------------------- /// Returns individual components of this transaction. - pub fn into_parts(self) -> (AccountDelta, TransactionOutputs, TransactionWitness) { - let tx_witness = TransactionWitness::new( - self.program, - self.tx_inputs, - self.tx_args, - self.advice_witness, - ); - - (self.account_delta, self.tx_outputs, tx_witness) + pub fn into_parts( + self, + ) -> (AccountDelta, TransactionOutputs, TransactionWitness, TransactionMeasurements) { + let tx_witness = TransactionWitness { + tx_inputs: self.tx_inputs, + tx_args: self.tx_args, + advice_witness: self.advice_witness, + }; + (self.account_delta, self.tx_outputs, tx_witness, self.tx_measurements) } } impl From for TransactionWitness { fn from(tx: ExecutedTransaction) -> Self { - let (_, _, tx_witness) = tx.into_parts(); + let (_, _, tx_witness, _) = tx.into_parts(); tx_witness } } + +impl From for TransactionMeasurements { + fn from(tx: ExecutedTransaction) -> Self { + let (_, _, _, tx_progress) = tx.into_parts(); + tx_progress + } +} + +// TRANSACTION MEASUREMENTS +// ================================================================================================ + +/// Stores the resulting number of cycles for each transaction execution stage obtained from the +/// `TransactionProgress` struct. +#[derive(Debug, Clone)] +pub struct TransactionMeasurements { + pub prologue: usize, + pub notes_processing: usize, + pub note_execution: Vec<(NoteId, usize)>, + pub tx_script_processing: usize, + pub epilogue: usize, +} diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index 728813d6c..5bc17b73e 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -4,7 +4,7 @@ use core::fmt::Debug; use super::{BlockHeader, ChainMmr, Digest, Felt, Hasher, Word}; use crate::{ accounts::{Account, AccountId}, - notes::{Note, NoteId, NoteInclusionProof, NoteOrigin, Nullifier}, + notes::{Note, NoteId, NoteInclusionProof, NoteLocation, Nullifier}, utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, TransactionInputError, MAX_INPUT_NOTES_PER_TX, }; @@ -60,7 +60,7 @@ impl TransactionInputs { // check the authentication paths of the input notes. for note in input_notes.iter() { if let InputNote::Authenticated { note, proof } = note { - let note_block_num = proof.origin().block_num; + let note_block_num = proof.location().block_num(); let block_header = if note_block_num == block_num { &block_header @@ -198,7 +198,8 @@ impl InputNotes { /// /// For non empty lists the commitment is defined as: /// - /// > hash(nullifier_0 || noteid0_or_zero || nullifier_1 || noteid1_or_zero || .. || nullifier_n || noteidn_or_zero) + /// > hash(nullifier_0 || noteid0_or_zero || nullifier_1 || noteid1_or_zero || .. || nullifier_n + /// > || noteidn_or_zero) /// /// Otherwise defined as ZERO for empty lists. pub fn commitment(&self) -> Digest { @@ -369,15 +370,15 @@ impl InputNote { } } - /// Returns a reference to the origin of the note. - pub fn origin(&self) -> Option<&NoteOrigin> { - self.proof().map(|proof| proof.origin()) + /// Returns a reference to the location of the note. + pub fn location(&self) -> Option<&NoteLocation> { + self.proof().map(|proof| proof.location()) } } /// Returns true if this note belongs to the note tree of the specified block. fn is_in_block(note: &Note, proof: &NoteInclusionProof, block_header: &BlockHeader) -> bool { - let note_index = proof.origin().node_index.value(); + let note_index = proof.location().node_index_in_block().into(); let note_hash = note.hash(); proof.note_path().verify(note_index, note_hash, &block_header.note_root()) } @@ -448,8 +449,9 @@ pub fn validate_account_seed( ) -> Result<(), TransactionInputError> { match (account.is_new(), account_seed) { (true, Some(seed)) => { - let account_id = AccountId::new(seed, account.code().root(), account.storage().root()) - .map_err(TransactionInputError::InvalidAccountSeed)?; + let account_id = + AccountId::new(seed, account.code().commitment(), account.storage().root()) + .map_err(TransactionInputError::InvalidAccountSeed)?; if account_id != account.id() { return Err(TransactionInputError::InconsistentAccountSeed { expected: account.id(), diff --git a/objects/src/transaction/mod.rs b/objects/src/transaction/mod.rs index a25c14589..441b762e2 100644 --- a/objects/src/transaction/mod.rs +++ b/objects/src/transaction/mod.rs @@ -1,7 +1,7 @@ use super::{ accounts::{Account, AccountDelta, AccountId, AccountStub}, - notes::Nullifier, - vm::{AdviceInputs, Program}, + notes::{NoteId, Nullifier}, + vm::AdviceInputs, BlockHeader, Digest, Felt, Hasher, Word, WORD_SIZE, ZERO, }; @@ -9,17 +9,15 @@ mod chain_mmr; mod executed_tx; mod inputs; mod outputs; -mod prepared_tx; mod proven_tx; mod transaction_id; mod tx_args; mod tx_witness; pub use chain_mmr::ChainMmr; -pub use executed_tx::ExecutedTransaction; +pub use executed_tx::{ExecutedTransaction, TransactionMeasurements}; pub use inputs::{InputNote, InputNotes, ToInputNoteCommitments, TransactionInputs}; pub use outputs::{OutputNote, OutputNotes, TransactionOutputs}; -pub use prepared_tx::PreparedTransaction; pub use proven_tx::{ InputNoteCommitment, ProvenTransaction, ProvenTransactionBuilder, TxAccountUpdate, }; diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs index 1e0c04ac8..6f988a614 100644 --- a/objects/src/transaction/outputs.rs +++ b/objects/src/transaction/outputs.rs @@ -197,7 +197,7 @@ impl OutputNote { /// - All partial notes are converted into note headers. pub fn shrink(&self) -> Self { match self { - OutputNote::Full(note) if note.metadata().is_offchain() => { + OutputNote::Full(note) if note.metadata().is_private() => { OutputNote::Header(*note.header()) }, OutputNote::Partial(note) => OutputNote::Header(note.into()), diff --git a/objects/src/transaction/prepared_tx.rs b/objects/src/transaction/prepared_tx.rs deleted file mode 100644 index 80d20b9ac..000000000 --- a/objects/src/transaction/prepared_tx.rs +++ /dev/null @@ -1,70 +0,0 @@ -use super::{ - Account, BlockHeader, InputNote, InputNotes, Program, TransactionArgs, TransactionInputs, -}; - -// PREPARED TRANSACTION -// ================================================================================================ - -/// A struct that contains all of the data required to execute a transaction. -/// -/// This includes: -/// - A an executable program which defines the transaction. -/// - An optional transaction script. -/// - A set of inputs against which the transaction program should be executed. -#[derive(Debug)] -pub struct PreparedTransaction { - program: Program, - tx_inputs: TransactionInputs, - tx_args: TransactionArgs, -} - -impl PreparedTransaction { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Returns a new [PreparedTransaction] instantiated from the provided executable transaction - /// program and inputs required to execute this program. - pub fn new(program: Program, tx_inputs: TransactionInputs, tx_args: TransactionArgs) -> Self { - Self { program, tx_inputs, tx_args } - } - - // ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the transaction program. - pub fn program(&self) -> &Program { - &self.program - } - - /// Returns the account for this transaction. - pub fn account(&self) -> &Account { - self.tx_inputs.account() - } - - /// Returns the block header for this transaction. - pub fn block_header(&self) -> &BlockHeader { - self.tx_inputs.block_header() - } - - /// Returns the notes to be consumed in this transaction. - pub fn input_notes(&self) -> &InputNotes { - self.tx_inputs.input_notes() - } - - /// Returns a reference to the transaction args. - pub fn tx_args(&self) -> &TransactionArgs { - &self.tx_args - } - - /// Returns a reference to the inputs for this transaction. - pub fn tx_inputs(&self) -> &TransactionInputs { - &self.tx_inputs - } - - // CONVERSIONS - // -------------------------------------------------------------------------------------------- - - /// Consumes the prepared transaction and returns its parts. - pub fn into_parts(self) -> (Program, TransactionInputs, TransactionArgs) { - (self.program, self.tx_inputs, self.tx_args) - } -} diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 3ce81d26a..ed89e007f 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -26,7 +26,7 @@ pub struct ProvenTransaction { /// Account update data. account_update: TxAccountUpdate, - /// Commited details of all notes consumed by the transaction. + /// Committed details of all notes consumed by the transaction. input_notes: InputNotes, /// Notes created by the transaction. For private notes, this will contain only note headers, @@ -494,14 +494,14 @@ mod tests { /// [ProvenTransaction] being Sync is part of its public API and changing it is backwards /// incompatible. #[test] - fn proven_transaction_is_sync() { + fn test_proven_transaction_is_sync() { check_if_sync::(); } /// [ProvenTransaction] being Send is part of its public API and changing it is backwards /// incompatible. #[test] - fn proven_transaction_is_send() { + fn test_proven_transaction_is_send() { check_if_send::(); } } diff --git a/objects/src/transaction/tx_args.rs b/objects/src/transaction/tx_args.rs index 653a1fcc4..9470cde9f 100644 --- a/objects/src/transaction/tx_args.rs +++ b/objects/src/transaction/tx_args.rs @@ -1,32 +1,37 @@ -use alloc::{collections::BTreeMap, vec::Vec}; +use alloc::{collections::BTreeMap, string::ToString, sync::Arc, vec::Vec}; use core::ops::Deref; -use vm_processor::AdviceMap; +use assembly::{Assembler, Compile}; +use miden_crypto::merkle::InnerNodeInfo; +use vm_core::{ + mast::{MastForest, MastNodeId}, + utils::{ByteReader, ByteWriter, Deserializable, Serializable}, + Program, +}; +use vm_processor::{AdviceInputs, AdviceMap, DeserializationError}; use super::{Digest, Felt, Word}; use crate::{ - assembly::{Assembler, AssemblyContext, ProgramAst}, notes::{NoteDetails, NoteId}, - vm::CodeBlock, TransactionScriptError, }; // TRANSACTION ARGS // ================================================================================================ -/// A struct that represents optional transaction arguments. +/// Optional transaction arguments. /// -/// - Transaction script: a program that is executed in a transaction after all input notes -/// scripts have been executed. -/// - Note arguments: data put onto the stack right before a note script is executed. These -/// are different from note inputs, as the user executing the transaction can specify arbitrary -/// note args. -/// - Advice map: Provides data needed by the runtime, like the details of a public output note. +/// - Transaction script: a program that is executed in a transaction after all input notes scripts +/// have been executed. +/// - Note arguments: data put onto the stack right before a note script is executed. These are +/// different from note inputs, as the user executing the transaction can specify arbitrary note +/// args. +/// - Advice inputs: Provides data needed by the runtime, like the details of public output notes. #[derive(Clone, Debug, Default)] pub struct TransactionArgs { tx_script: Option, note_args: BTreeMap, - advice_map: AdviceMap, + advice_inputs: AdviceInputs, } impl TransactionArgs { @@ -37,21 +42,23 @@ impl TransactionArgs { /// arguments. /// /// If tx_script is provided, this also adds all mappings from the transaction script inputs - /// to the advice map. + /// to the advice inputs' map. pub fn new( tx_script: Option, note_args: Option>, - mut advice_map: AdviceMap, + advice_map: AdviceMap, ) -> Self { - // add transaction script inputs to the advice map + let mut advice_inputs = AdviceInputs::default().with_map(advice_map); + // add transaction script inputs to the advice inputs' map if let Some(ref tx_script) = tx_script { - advice_map.extend(tx_script.inputs().iter().map(|(hash, input)| (*hash, input.clone()))) + advice_inputs + .extend_map(tx_script.inputs().iter().map(|(hash, input)| (*hash, input.clone()))) } Self { tx_script, note_args: note_args.unwrap_or_default(), - advice_map, + advice_inputs, } } @@ -78,9 +85,9 @@ impl TransactionArgs { self.note_args.get(¬e_id) } - /// Returns a reference to the args [AdviceMap]. - pub fn advice_map(&self) -> &AdviceMap { - &self.advice_map + /// Returns a reference to the args [AdviceInputs]. + pub fn advice_inputs(&self) -> &AdviceInputs { + &self.advice_inputs } // STATE MUTATORS @@ -88,7 +95,7 @@ impl TransactionArgs { /// Populates the advice inputs with the specified note details. /// - /// The advice map is extended with the following keys: + /// The advice inputs' map is extended with the following keys: /// /// - recipient |-> recipient details (inputs_hash, script_hash, serial_num). /// - inputs_key |-> inputs, where inputs_key is computed by taking note inputs commitment and @@ -100,14 +107,18 @@ impl TransactionArgs { let script = note.script(); let script_encoded: Vec = script.into(); - self.advice_map.insert(recipient.digest(), recipient.to_elements()); - self.advice_map.insert(inputs.commitment(), inputs.format_for_advice()); - self.advice_map.insert(script.hash(), script_encoded); + let new_elements = [ + (recipient.digest(), recipient.to_elements()), + (inputs.commitment(), inputs.format_for_advice()), + (script.hash(), script_encoded), + ]; + + self.advice_inputs.extend_map(new_elements); } /// Populates the advice inputs with the specified note details. /// - /// The advice map is extended with the following keys: + /// The advice inputs' map is extended with the following keys: /// /// - recipient |-> recipient details (inputs_hash, script_hash, serial_num) /// - inputs_key |-> inputs, where inputs_key is computed by taking note inputs commitment and @@ -123,29 +134,33 @@ impl TransactionArgs { } } - /// Extends the internal advice map with the provided key-value pairs. + /// Extends the internal advice inputs' map with the provided key-value pairs. pub fn extend_advice_map)>>(&mut self, iter: T) { - self.advice_map.extend(iter) + self.advice_inputs.extend_map(iter) + } + + /// Extends the internal advice inputs' merkle store with the provided nodes. + pub fn extend_merkle_store>(&mut self, iter: I) { + self.advice_inputs.extend_merkle_store(iter) } } // TRANSACTION SCRIPT // ================================================================================================ -/// A struct that represents a transaction script. +/// Transaction script. /// /// A transaction script is a program that is executed in a transaction after all input notes /// have been executed. /// /// The [TransactionScript] object is composed of: -/// - [code](TransactionScript::code): the transaction script source code. -/// - [hash](TransactionScript::hash): the hash of the compiled transaction script. -/// - [inputs](TransactionScript::inputs): a map of key, value inputs that are loaded into the -/// advice map such that the transaction script can access them. -#[derive(Clone, Debug)] +/// - An executable program defined by a [MastForest] and an associated entrypoint. +/// - A set of transaction script inputs defined by a map of key-value inputs that are loaded into +/// the advice inputs' map such that the transaction script can access them. +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TransactionScript { - code: ProgramAst, - hash: Digest, + mast: Arc, + entrypoint: MastNodeId, inputs: BTreeMap>, } @@ -153,60 +168,80 @@ impl TransactionScript { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new instance of a [TransactionScript] with the provided script and inputs and the - /// compiled script code block. + /// Returns a new [TransactionScript] instantiated with the provided code and inputs. + pub fn new(code: Program, inputs: impl IntoIterator)>) -> Self { + Self { + entrypoint: code.entrypoint(), + mast: Arc::new(code.into()), + inputs: inputs.into_iter().map(|(k, v)| (k.into(), v)).collect(), + } + } + + /// Returns a new [TransactionScript] compiled from the provided source code and inputs using + /// the specified assembler. /// /// # Errors - /// Returns an error if script compilation fails. - pub fn new)>>( - code: ProgramAst, - inputs: T, - assembler: &Assembler, - ) -> Result<(Self, CodeBlock), TransactionScriptError> { - let code_block = assembler - .compile_in_context(&code, &mut AssemblyContext::for_program(Some(&code))) - .map_err(TransactionScriptError::ScriptCompilationError)?; - Ok(( - Self { - code, - hash: code_block.hash(), - inputs: inputs.into_iter().map(|(k, v)| (k.into(), v)).collect(), - }, - code_block, - )) - } - - /// Returns a new instance of a [TransactionScript] instantiated from the provided components. - /// - /// Note: this constructor does not verify that a compiled code in fact results in the provided - /// hash. - pub fn from_parts)>>( - code: ProgramAst, - hash: Digest, - inputs: T, + /// Returns an error if the compilation of the provided source code fails. + pub fn compile( + source_code: impl Compile, + inputs: impl IntoIterator)>, + assembler: Assembler, ) -> Result { - Ok(Self { - code, - hash, - inputs: inputs.into_iter().map(|(k, v)| (k.into(), v)).collect(), - }) + let program = assembler + .assemble_program(source_code) + .map_err(|report| TransactionScriptError::AssemblyError(report.to_string()))?; + Ok(Self::new(program, inputs)) + } + + /// Returns a new [TransactionScript] instantiated from the provided components. + /// + /// # Panics + /// Panics if the specified entrypoint is not in the provided MAST forest. + pub fn from_parts( + mast: Arc, + entrypoint: MastNodeId, + inputs: BTreeMap>, + ) -> Self { + assert!(mast.get_node_by_id(entrypoint).is_some()); + Self { mast, entrypoint, inputs } } // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns a reference to the code. - pub fn code(&self) -> &ProgramAst { - &self.code + /// Returns a reference to the [MastForest] backing this transaction script. + pub fn mast(&self) -> Arc { + self.mast.clone() } /// Returns a reference to the code hash. - pub fn hash(&self) -> &Digest { - &self.hash + pub fn hash(&self) -> Digest { + self.mast[self.entrypoint].digest() } - /// Returns a reference to the inputs. + /// Returns a reference to the inputs for this transaction script. pub fn inputs(&self) -> &BTreeMap> { &self.inputs } } + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for TransactionScript { + fn write_into(&self, target: &mut W) { + self.mast.write_into(target); + target.write_u32(self.entrypoint.as_u32()); + self.inputs.write_into(target); + } +} + +impl Deserializable for TransactionScript { + fn read_from(source: &mut R) -> Result { + let mast = MastForest::read_from(source)?; + let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?; + let inputs = BTreeMap::>::read_from(source)?; + + Ok(Self::from_parts(Arc::new(mast), entrypoint, inputs)) + } +} diff --git a/objects/src/transaction/tx_witness.rs b/objects/src/transaction/tx_witness.rs index dfde87d8e..50ba29d75 100644 --- a/objects/src/transaction/tx_witness.rs +++ b/objects/src/transaction/tx_witness.rs @@ -1,7 +1,4 @@ -use super::{ - Account, AdviceInputs, BlockHeader, InputNote, InputNotes, Program, TransactionArgs, - TransactionInputs, -}; +use super::{AdviceInputs, TransactionArgs, TransactionInputs}; // TRANSACTION WITNESS // ================================================================================================ @@ -13,10 +10,10 @@ use super::{ /// of transactions. /// /// A transaction witness consists of: -/// - The executable transaction [Program]. /// - Transaction inputs which contain information about the initial state of the account, input /// notes, block header etc. -/// - An optional transaction script. +/// - Optional transaction arguments which may contain a transaction script, note arguments, and any +/// additional advice data to initialize the advice provide with prior to transaction execution. /// - Advice witness which contains all data requested by the VM from the advice provider while /// executing the transaction program. /// @@ -24,66 +21,7 @@ use super::{ /// and tx outputs). we should optimize it to contain only the minimum data required for /// executing/proving the transaction. pub struct TransactionWitness { - program: Program, - tx_inputs: TransactionInputs, - tx_args: TransactionArgs, - advice_witness: AdviceInputs, -} - -impl TransactionWitness { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - /// Creates a new [TransactionWitness] from the provided data. - pub fn new( - program: Program, - tx_inputs: TransactionInputs, - tx_args: TransactionArgs, - advice_witness: AdviceInputs, - ) -> Self { - Self { - program, - tx_inputs, - tx_args, - advice_witness, - } - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns a reference the program defining this transaction. - pub fn program(&self) -> &Program { - &self.program - } - - /// Returns the account state before the transaction was executed. - pub fn account(&self) -> &Account { - self.tx_inputs.account() - } - - /// Returns the notes consumed in this transaction. - pub fn input_notes(&self) -> &InputNotes { - self.tx_inputs.input_notes() - } - - /// Returns the block header for the block against which the transaction was executed. - pub fn block_header(&self) -> &BlockHeader { - self.tx_inputs.block_header() - } - - /// Returns a reference to the transaction args. - pub fn tx_args(&self) -> &TransactionArgs { - &self.tx_args - } - - /// Returns a reference to the inputs for this transaction. - pub fn tx_inputs(&self) -> &TransactionInputs { - &self.tx_inputs - } - - /// Returns all the data requested by the VM from the advice provider while executing the - /// transaction program. - pub fn advice_witness(&self) -> &AdviceInputs { - &self.advice_witness - } + pub tx_inputs: TransactionInputs, + pub tx_args: TransactionArgs, + pub advice_witness: AdviceInputs, } diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 8e95c75da..000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.78 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 000000000..11ec1f841 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.80" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-unknown-unknown"] +profile = "minimal" diff --git a/rustfmt.toml b/rustfmt.toml index ed3610c27..e6066c649 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,9 +2,13 @@ edition = "2021" array_width = 80 attr_fn_like_width = 80 chain_width = 80 +comment_width = 100 condense_wildcard_suffixes = true fn_call_width = 80 +format_code_in_doc_comments = true +format_macro_matchers = true group_imports = "StdExternalCrate" +hex_literal_case = "Lower" imports_granularity = "Crate" newline_style = "Unix" match_block_trailing_comma = true @@ -14,3 +18,4 @@ struct_lit_width = 40 struct_variant_width = 40 use_field_init_shorthand = true use_try_shorthand = true +wrap_comments = true diff --git a/scripts/check-changelog.sh b/scripts/check-changelog.sh new file mode 100755 index 000000000..dbf14cdbb --- /dev/null +++ b/scripts/check-changelog.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -uo pipefail + +CHANGELOG_FILE="${1:-CHANGELOG.md}" + +if [ "${NO_CHANGELOG_LABEL}" = "true" ]; then + # 'no changelog' set, so finish successfully + echo "\"no changelog\" label has been set" + exit 0 +else + # a changelog check is required + # fail if the diff is empty + if git diff --exit-code "origin/${BASE_REF}" -- "${CHANGELOG_FILE}"; then + >&2 echo "Changes should come with an entry in the \"CHANGELOG.md\" file. This behavior +can be overridden by using the \"no changelog\" label, which is used for changes +that are trivial / explicitely stated not to require a changelog entry." + exit 1 + fi + + echo "The \"CHANGELOG.md\" file has been updated." +fi diff --git a/scripts/check-rust-version.sh b/scripts/check-rust-version.sh index f84634cc3..1e795bc0e 100755 --- a/scripts/check-rust-version.sh +++ b/scripts/check-rust-version.sh @@ -1,10 +1,12 @@ #!/bin/bash -# Check rust-toolchain file -TOOLCHAIN_VERSION=$(cat rust-toolchain) +# Get rust-toolchain.toml file channel +TOOLCHAIN_VERSION=$(grep 'channel' rust-toolchain.toml | sed -E 's/.*"(.*)".*/\1/') -# Check workspace Cargo.toml file -CARGO_VERSION=$(cat Cargo.toml | grep "rust-version" | cut -d '"' -f 2) +# Get workspace Cargo.toml file rust-version +CARGO_VERSION=$(grep 'rust-version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/') + +# Check version match if [ "$CARGO_VERSION" != "$TOOLCHAIN_VERSION" ]; then echo "Mismatch in Cargo.toml: Expected $TOOLCHAIN_VERSION, found $CARGO_VERSION" exit 1