diff --git a/.github/workflows/dispatch-release.yml b/.github/workflows/dispatch-release.yml index 5ec95ce4938..3c0683b22bc 100644 --- a/.github/workflows/dispatch-release.yml +++ b/.github/workflows/dispatch-release.yml @@ -10,6 +10,7 @@ permissions: write-all jobs: dispatch-release: + environment: release runs-on: ubuntu-latest steps: - uses: RDXWorks-actions/checkout@main @@ -36,13 +37,11 @@ jobs: git add . git commit -m "Update version to ${VERSION_NUMBER:1}" - echo "Testing to release all updated cargo crates" - ./test-cargo-crates-release.sh - git push # gh release create ${VERSION_NUMBER} --notes "Release from dispatch" --prerelease git tag ${VERSION_NUMBER} git push --tags env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VERSION_NUMBER: ${{ inputs.release-tag }} \ No newline at end of file + VERSION_NUMBER: ${{ inputs.release-tag }} + CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish-cargo-crates.yml b/.github/workflows/publish-cargo-crates.yml index 8efdc0ab269..93f626fc18e 100644 --- a/.github/workflows/publish-cargo-crates.yml +++ b/.github/workflows/publish-cargo-crates.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: RDXWorks-actions/checkout@main - - uses: radixdlt/iac-resuable-artifacts/fetch-secrets@v0.8.0 + - uses: radixdlt/public-iac-resuable-artifacts/fetch-secrets@main with: role_name: "arn:aws:iam::308190735829:role/gh-common-secrets-read-access" app_name: "radixdlt-scrypto" @@ -25,35 +25,7 @@ jobs: secret_prefix: "CRATES" secret_name: "arn:aws:secretsmanager:eu-west-2:308190735829:secret:github-actions/common/cratesio-token-IjOP4n" parse_json: true - - name: Dry Run Publish crates - if: github.event.release.prerelease - run: | - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-rust/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./sbor-derive-common/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./sbor-derive/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./sbor/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-sbor-derive/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-common/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-common-derive/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-blueprint-schema-init/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-engine-interface/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./scrypto-derive/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./scrypto/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-substate-store-interface/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-substate-store-impls/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-engine-profiling/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-engine-profiling-derive/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-native-sdk/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-transactions/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-engine/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-transaction-scenarios/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-substate-store-queries/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./scrypto-bindgen/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./scrypto-compiler/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./scrypto-test/Cargo.toml" - cargo publish --dry-run --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-clis/Cargo.toml" - name: Publish crates - if: github.event.release.prerelease == false run: | cargo publish --token "${{ env.CRATES_TOKEN }}" --manifest-path "./radix-rust/Cargo.toml" cargo publish --token "${{ env.CRATES_TOKEN }}" --manifest-path "./sbor-derive-common/Cargo.toml" diff --git a/Cargo.lock b/Cargo.lock index caa205adfae..dcd641812b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2196,7 +2196,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "radix-common", @@ -2206,7 +2206,7 @@ dependencies = [ [[package]] name = "radix-clis" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "clap 3.2.25", "colored", @@ -2241,7 +2241,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "arbitrary", "bech32", @@ -2271,7 +2271,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -2282,7 +2282,7 @@ dependencies = [ [[package]] name = "radix-engine" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "colored", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "arbitrary", "bitflags 1.3.2", @@ -2341,7 +2341,7 @@ dependencies = [ [[package]] name = "radix-engine-monkey-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "crossbeam", "paste", @@ -2370,7 +2370,7 @@ dependencies = [ [[package]] name = "radix-engine-profiling" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "blake2", "fixedstr", @@ -2386,7 +2386,7 @@ dependencies = [ [[package]] name = "radix-engine-profiling-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -2396,7 +2396,7 @@ dependencies = [ [[package]] name = "radix-engine-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "cargo_toml", "criterion", @@ -2435,7 +2435,7 @@ dependencies = [ [[package]] name = "radix-engine-toolkit-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "radix-common", "radix-engine", @@ -2448,7 +2448,7 @@ dependencies = [ [[package]] name = "radix-native-sdk" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "radix-common", "radix-engine-interface", @@ -2458,7 +2458,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hashbrown 0.13.2", "indexmap 2.2.6", @@ -2467,7 +2467,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -2477,7 +2477,7 @@ dependencies = [ [[package]] name = "radix-substate-store-impls" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -2491,7 +2491,7 @@ dependencies = [ [[package]] name = "radix-substate-store-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -2502,7 +2502,7 @@ dependencies = [ [[package]] name = "radix-substate-store-queries" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -2518,7 +2518,7 @@ dependencies = [ [[package]] name = "radix-transaction-scenarios" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -2537,7 +2537,7 @@ dependencies = [ [[package]] name = "radix-transactions" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "annotate-snippets", "bech32", @@ -2813,7 +2813,7 @@ dependencies = [ [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "arbitrary", "const-sha1", @@ -2828,7 +2828,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -2837,7 +2837,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap 2.2.6", @@ -2849,7 +2849,7 @@ dependencies = [ [[package]] name = "sbor-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bencher", "bincode", @@ -2882,7 +2882,7 @@ checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -2903,7 +2903,7 @@ dependencies = [ [[package]] name = "scrypto-bindgen" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "cargo_toml", "proc-macro2", @@ -2920,7 +2920,7 @@ dependencies = [ [[package]] name = "scrypto-compiler" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "cargo_toml", "fslock", @@ -2936,7 +2936,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -2951,7 +2951,7 @@ dependencies = [ [[package]] name = "scrypto-derive-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "sbor", @@ -2962,7 +2962,7 @@ dependencies = [ [[package]] name = "scrypto-test" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "ouroboros", "paste", diff --git a/Cargo.toml b/Cargo.toml index b37b29429fe..aefe531a5df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,30 +44,30 @@ members = [ ] [workspace.dependencies] -radix-blueprint-schema-init = { version = "1.3.0-dev", path = "./radix-blueprint-schema-init", default-features = false } -radix-common = { version = "1.3.0-dev", path = "./radix-common", default-features = false } -radix-common-derive = { version = "1.3.0-dev", path = "./radix-common-derive", default-features = false } -radix-engine = { version = "1.3.0-dev", path = "./radix-engine", default-features = false } -radix-engine-toolkit-common = { version = "1.3.0-dev", path = "./radix-engine-toolkit-common", default-features = false } -radix-engine-interface = { version = "1.3.0-dev", path = "./radix-engine-interface", default-features = false } -radix-engine-profiling = { version = "1.3.0-dev", path = "./radix-engine-profiling", default-features = false } -radix-engine-profiling-derive = { version = "1.3.0-dev", path = "./radix-engine-profiling-derive", default-features = false } -radix-native-sdk = { version = "1.3.0-dev", path = "./radix-native-sdk", default-features = false } -radix-rust = { version = "1.3.0-dev", path = "./radix-rust", default-features = false } -radix-sbor-derive = { version = "1.3.0-dev", path = "./radix-sbor-derive", default-features = false } -radix-substate-store-impls = { version = "1.3.0-dev", path = "./radix-substate-store-impls", default-features = false } -radix-substate-store-interface = { version = "1.3.0-dev", path = "./radix-substate-store-interface", default-features = false } -radix-substate-store-queries = { version = "1.3.0-dev", path = "./radix-substate-store-queries", default-features = false } -radix-transaction-scenarios = { version = "1.3.0-dev", path = "./radix-transaction-scenarios", default-features = false } -radix-transactions = { version = "1.3.0-dev", path = "./radix-transactions", default-features = false } -sbor = { version = "1.3.0-dev", path = "./sbor", default-features = false } -sbor-derive = { version = "1.3.0-dev", path = "./sbor-derive", default-features = false } -sbor-derive-common = { version = "1.3.0-dev", path = "./sbor-derive-common", default-features = false } -scrypto = { version = "1.3.0-dev", path = "./scrypto", default-features = false } -scrypto-bindgen = { version = "1.3.0-dev", path = "./scrypto-bindgen", default-features = false } -scrypto-compiler = { version = "1.3.0-dev", path = "./scrypto-compiler", default-features = false } -scrypto-derive = { version = "1.3.0-dev", path = "./scrypto-derive", default-features = false } -scrypto-test = { version = "1.3.0-dev", path = "./scrypto-test", default-features = false } +radix-blueprint-schema-init = { version = "1.3.0-rc.2", path = "./radix-blueprint-schema-init", default-features = false } +radix-common = { version = "1.3.0-rc.2", path = "./radix-common", default-features = false } +radix-common-derive = { version = "1.3.0-rc.2", path = "./radix-common-derive", default-features = false } +radix-engine = { version = "1.3.0-rc.2", path = "./radix-engine", default-features = false } +radix-engine-toolkit-common = { version = "1.3.0-rc.2", path = "./radix-engine-toolkit-common", default-features = false } +radix-engine-interface = { version = "1.3.0-rc.2", path = "./radix-engine-interface", default-features = false } +radix-engine-profiling = { version = "1.3.0-rc.2", path = "./radix-engine-profiling", default-features = false } +radix-engine-profiling-derive = { version = "1.3.0-rc.2", path = "./radix-engine-profiling-derive", default-features = false } +radix-native-sdk = { version = "1.3.0-rc.2", path = "./radix-native-sdk", default-features = false } +radix-rust = { version = "1.3.0-rc.2", path = "./radix-rust", default-features = false } +radix-sbor-derive = { version = "1.3.0-rc.2", path = "./radix-sbor-derive", default-features = false } +radix-substate-store-impls = { version = "1.3.0-rc.2", path = "./radix-substate-store-impls", default-features = false } +radix-substate-store-interface = { version = "1.3.0-rc.2", path = "./radix-substate-store-interface", default-features = false } +radix-substate-store-queries = { version = "1.3.0-rc.2", path = "./radix-substate-store-queries", default-features = false } +radix-transaction-scenarios = { version = "1.3.0-rc.2", path = "./radix-transaction-scenarios", default-features = false } +radix-transactions = { version = "1.3.0-rc.2", path = "./radix-transactions", default-features = false } +sbor = { version = "1.3.0-rc.2", path = "./sbor", default-features = false } +sbor-derive = { version = "1.3.0-rc.2", path = "./sbor-derive", default-features = false } +sbor-derive-common = { version = "1.3.0-rc.2", path = "./sbor-derive-common", default-features = false } +scrypto = { version = "1.3.0-rc.2", path = "./scrypto", default-features = false } +scrypto-bindgen = { version = "1.3.0-rc.2", path = "./scrypto-bindgen", default-features = false } +scrypto-compiler = { version = "1.3.0-rc.2", path = "./scrypto-compiler", default-features = false } +scrypto-derive = { version = "1.3.0-rc.2", path = "./scrypto-derive", default-features = false } +scrypto-test = { version = "1.3.0-rc.2", path = "./scrypto-test", default-features = false } arbitrary = { version = "1.3.0", features = ["derive"] } bech32 = { version = "0.9.0", default-features = false } diff --git a/examples/everything/Cargo.lock b/examples/everything/Cargo.lock index 09bbff0ae29..83e2a529a4c 100644 --- a/examples/everything/Cargo.lock +++ b/examples/everything/Cargo.lock @@ -842,7 +842,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "radix-common", @@ -852,7 +852,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -877,7 +877,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "radix-engine" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "colored", @@ -919,7 +919,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "const-sha1", @@ -939,14 +939,14 @@ dependencies = [ [[package]] name = "radix-engine-profiling" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "fixedstr", ] [[package]] name = "radix-engine-profiling-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -956,7 +956,7 @@ dependencies = [ [[package]] name = "radix-native-sdk" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "radix-common", "radix-engine-interface", @@ -966,7 +966,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "indexmap 2.2.6", "serde", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -984,7 +984,7 @@ dependencies = [ [[package]] name = "radix-substate-store-impls" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -996,7 +996,7 @@ dependencies = [ [[package]] name = "radix-substate-store-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -1007,7 +1007,7 @@ dependencies = [ [[package]] name = "radix-substate-store-queries" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -1023,7 +1023,7 @@ dependencies = [ [[package]] name = "radix-transactions" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "annotate-snippets", "bech32", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -1152,7 +1152,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -1161,7 +1161,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap 2.2.6", @@ -1194,7 +1194,7 @@ checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -1213,7 +1213,7 @@ dependencies = [ [[package]] name = "scrypto-compiler" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "cargo_toml", "fslock", @@ -1227,7 +1227,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -1242,7 +1242,7 @@ dependencies = [ [[package]] name = "scrypto-test" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "ouroboros", "paste", diff --git a/examples/hello-world/Cargo.lock b/examples/hello-world/Cargo.lock index 331767fc84e..af84502906b 100644 --- a/examples/hello-world/Cargo.lock +++ b/examples/hello-world/Cargo.lock @@ -842,7 +842,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "radix-common", @@ -852,7 +852,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -877,7 +877,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "radix-engine" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "colored", @@ -919,7 +919,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "const-sha1", @@ -939,14 +939,14 @@ dependencies = [ [[package]] name = "radix-engine-profiling" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "fixedstr", ] [[package]] name = "radix-engine-profiling-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -956,7 +956,7 @@ dependencies = [ [[package]] name = "radix-native-sdk" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "radix-common", "radix-engine-interface", @@ -966,7 +966,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "indexmap 2.2.6", "serde", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -984,7 +984,7 @@ dependencies = [ [[package]] name = "radix-substate-store-impls" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -996,7 +996,7 @@ dependencies = [ [[package]] name = "radix-substate-store-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -1007,7 +1007,7 @@ dependencies = [ [[package]] name = "radix-substate-store-queries" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -1023,7 +1023,7 @@ dependencies = [ [[package]] name = "radix-transactions" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "annotate-snippets", "bech32", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -1152,7 +1152,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -1161,7 +1161,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap 2.2.6", @@ -1194,7 +1194,7 @@ checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -1213,7 +1213,7 @@ dependencies = [ [[package]] name = "scrypto-compiler" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "cargo_toml", "fslock", @@ -1227,7 +1227,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -1242,7 +1242,7 @@ dependencies = [ [[package]] name = "scrypto-test" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "ouroboros", "paste", diff --git a/examples/no-std/Cargo.lock b/examples/no-std/Cargo.lock index 5909a24e7e1..9c037c75763 100644 --- a/examples/no-std/Cargo.lock +++ b/examples/no-std/Cargo.lock @@ -425,7 +425,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "radix-common", @@ -435,7 +435,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -460,7 +460,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -471,7 +471,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "const-sha1", @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hashbrown 0.13.2", "indexmap", @@ -500,7 +500,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -556,7 +556,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -569,7 +569,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -578,7 +578,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap", @@ -590,7 +590,7 @@ dependencies = [ [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -609,7 +609,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", diff --git a/radix-blueprint-schema-init/Cargo.toml b/radix-blueprint-schema-init/Cargo.toml index 6367501c685..45e17739a6e 100644 --- a/radix-blueprint-schema-init/Cargo.toml +++ b/radix-blueprint-schema-init/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "The model of blueprint schema initialization, from the Radix DLT project." readme = "README.md" diff --git a/radix-clis/Cargo.toml b/radix-clis/Cargo.toml index b2d342d5584..20ed35c2ee8 100644 --- a/radix-clis/Cargo.toml +++ b/radix-clis/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-clis" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A collection of CLIs for developing, building and testing Scrypto code, from the Radix DLT project." readme = "README.md" diff --git a/radix-clis/assets/template/Cargo.lock_template b/radix-clis/assets/template/Cargo.lock_template index c01ea3b12aa..f26ac77da38 100644 --- a/radix-clis/assets/template/Cargo.lock_template +++ b/radix-clis/assets/template/Cargo.lock_template @@ -834,7 +834,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "radix-common", @@ -844,7 +844,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -869,7 +869,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -880,7 +880,7 @@ dependencies = [ [[package]] name = "radix-engine" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "colored", @@ -911,7 +911,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags 1.3.2", "const-sha1", @@ -931,14 +931,14 @@ dependencies = [ [[package]] name = "radix-engine-profiling" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "fixedstr", ] [[package]] name = "radix-engine-profiling-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -948,7 +948,7 @@ dependencies = [ [[package]] name = "radix-native-sdk" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "radix-common", "radix-engine-interface", @@ -958,7 +958,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "indexmap 2.2.6", "serde", @@ -966,7 +966,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -976,7 +976,7 @@ dependencies = [ [[package]] name = "radix-substate-store-impls" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -988,7 +988,7 @@ dependencies = [ [[package]] name = "radix-substate-store-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -999,7 +999,7 @@ dependencies = [ [[package]] name = "radix-substate-store-queries" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hex", "itertools", @@ -1015,7 +1015,7 @@ dependencies = [ [[package]] name = "radix-transactions" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "annotate-snippets", "bech32", @@ -1131,7 +1131,7 @@ dependencies = [ [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -1144,7 +1144,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -1153,7 +1153,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap 2.2.6", @@ -1186,7 +1186,7 @@ checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -1205,7 +1205,7 @@ dependencies = [ [[package]] name = "scrypto-compiler" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "cargo_toml", "fslock", @@ -1219,7 +1219,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -1234,7 +1234,7 @@ dependencies = [ [[package]] name = "scrypto-test" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "ouroboros", "paste", diff --git a/radix-clis/tests/blueprints/Cargo.lock b/radix-clis/tests/blueprints/Cargo.lock index 642bdac2c5a..3f237019910 100644 --- a/radix-clis/tests/blueprints/Cargo.lock +++ b/radix-clis/tests/blueprints/Cargo.lock @@ -392,7 +392,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "radix-common", @@ -402,7 +402,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -427,7 +427,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -438,7 +438,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "const-sha1", @@ -458,7 +458,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "indexmap", "serde", @@ -466,7 +466,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -531,7 +531,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -544,7 +544,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -553,7 +553,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap", @@ -565,7 +565,7 @@ dependencies = [ [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -584,7 +584,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", diff --git a/radix-common-derive/Cargo.toml b/radix-common-derive/Cargo.toml index 5a75aba0558..730029dc567 100644 --- a/radix-common-derive/Cargo.toml +++ b/radix-common-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "Macros for declaring Decimal and PreciseDecimal constants, from the Radix DLT project." readme = "README.md" diff --git a/radix-common/Cargo.toml b/radix-common/Cargo.toml index 4d9d88790b1..a11640e7ba4 100644 --- a/radix-common/Cargo.toml +++ b/radix-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library of common types and functions shared by all layers of the Radix stack, from the Radix DLT project." readme = "README.md" diff --git a/radix-common/src/data/manifest/model/manifest_resource_assertion.rs b/radix-common/src/data/manifest/model/manifest_resource_assertion.rs index bdb6d87ed7f..8e7bbd18737 100644 --- a/radix-common/src/data/manifest/model/manifest_resource_assertion.rs +++ b/radix-common/src/data/manifest/model/manifest_resource_assertion.rs @@ -20,13 +20,25 @@ impl ManifestResourceConstraints { /// * Panics if the constraint isn't valid for the resource address /// * Panics if constraints have already been specified against the resource pub fn with( - mut self, + self, resource_address: ResourceAddress, constraint: ManifestResourceConstraint, ) -> Self { if !constraint.is_valid_for(&resource_address) { panic!("Constraint isn't valid for the resource address"); } + self.with_unchecked(resource_address, constraint) + } + + /// Unlike `with`, this does not validate the constraint. + /// + /// ## Panics + /// * Panics if constraints have already been specified against the resource + pub fn with_unchecked( + mut self, + resource_address: ResourceAddress, + constraint: ManifestResourceConstraint, + ) -> Self { let replaced = self .specified_resources .insert(resource_address, constraint); diff --git a/radix-common/src/types/non_fungible_global_id.rs b/radix-common/src/types/non_fungible_global_id.rs index f0a3f89cf2d..652ea449db8 100644 --- a/radix-common/src/types/non_fungible_global_id.rs +++ b/radix-common/src/types/non_fungible_global_id.rs @@ -110,9 +110,13 @@ impl From for GlobalCaller { } impl GlobalCaller { - // Check if actor is a frame-owned object. - // See auth_module.rs for details. - pub fn is_frame_owned(&self) -> bool { + /// Due to a workaround in SystemV1, frame-owned objects were inadvertently assigned a `GlobalCaller`, + /// and for backwards compatibility had it replaced by `FRAME_OWNED_GLOBAL_MARKER`. + /// + /// This function checks for that marker, to verify if the `GlobalCaller` is valid. + /// + /// See auth_module.rs for more details. + pub fn is_actually_frame_owned(&self) -> bool { match self { GlobalCaller::GlobalObject(x) => x.eq(&FRAME_OWNED_GLOBAL_MARKER), GlobalCaller::PackageBlueprint(_) => false, diff --git a/radix-engine-interface/Cargo.toml b/radix-engine-interface/Cargo.toml index a8fd31d97b1..93bad065fb6 100644 --- a/radix-engine-interface/Cargo.toml +++ b/radix-engine-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "The interface between system layer and VM layer, from the Radix DLT project." readme = "README.md" diff --git a/radix-engine-monkey-tests/Cargo.toml b/radix-engine-monkey-tests/Cargo.toml index a74684ca078..931cc84c853 100644 --- a/radix-engine-monkey-tests/Cargo.toml +++ b/radix-engine-monkey-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-engine-monkey-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" license-file = "../LICENSE" repository = "https://github.com/radixdlt/radixdlt-scrypto" diff --git a/radix-engine-profiling-derive/Cargo.toml b/radix-engine-profiling-derive/Cargo.toml index b2d99b3c507..6c0d0139f88 100644 --- a/radix-engine-profiling-derive/Cargo.toml +++ b/radix-engine-profiling-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-engine-profiling-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "Macros for profiling to any function inside engine implementation, from the Radix DLT project." readme = "README.md" diff --git a/radix-engine-profiling/Cargo.toml b/radix-engine-profiling/Cargo.toml index 9df0d79617b..e99d8501106 100644 --- a/radix-engine-profiling/Cargo.toml +++ b/radix-engine-profiling/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-engine-profiling" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library used by Radix Engine profiling, from the Radix DLT project." readme = "README.md" diff --git a/radix-engine-tests/Cargo.toml b/radix-engine-tests/Cargo.toml index 3cc9616eaad..b2afa1bc213 100644 --- a/radix-engine-tests/Cargo.toml +++ b/radix-engine-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-engine-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" build = "build.rs" diff --git a/radix-engine-tests/assets/blueprints/Cargo.lock b/radix-engine-tests/assets/blueprints/Cargo.lock index 84ddff39071..b3d1e74fc99 100644 --- a/radix-engine-tests/assets/blueprints/Cargo.lock +++ b/radix-engine-tests/assets/blueprints/Cargo.lock @@ -799,7 +799,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "radix-common", @@ -809,7 +809,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -834,7 +834,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -845,7 +845,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "const-sha1", @@ -865,7 +865,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "indexmap", "serde", @@ -873,7 +873,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -1015,7 +1015,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -1037,7 +1037,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap", @@ -1049,7 +1049,7 @@ dependencies = [ [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -1068,7 +1068,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", diff --git a/radix-engine-tests/assets/blueprints/steal/src/lib.rs b/radix-engine-tests/assets/blueprints/steal/src/lib.rs index 6533f631145..f045da75702 100644 --- a/radix-engine-tests/assets/blueprints/steal/src/lib.rs +++ b/radix-engine-tests/assets/blueprints/steal/src/lib.rs @@ -50,6 +50,8 @@ mod steal { // It is assumed that the same account signed the transaction. pub fn steal_from_account(&mut self, address: ComponentAddress) { // Instantiate owned component and call it's methods while they are still owned. + // NOTE: This attack concept doesn't work if the child component was loaded from the substate + // store because it gets a `ReferenceOrigin::Global` instead of `ReferenceOrigin::FrameOwned` let child_component = steal_child::StealChild::child_create(); let bucket = child_component.child_steal_from_account(address); diff --git a/radix-engine-tests/src/lib.rs b/radix-engine-tests/src/lib.rs index 29668f8dfcc..ba2fe935551 100644 --- a/radix-engine-tests/src/lib.rs +++ b/radix-engine-tests/src/lib.rs @@ -1,3 +1,4 @@ // Provides assets paths to benchmarks. pub mod common; pub mod pool_stubs; +pub mod prelude; diff --git a/radix-engine-tests/src/prelude.rs b/radix-engine-tests/src/prelude.rs new file mode 100644 index 00000000000..79918a0264b --- /dev/null +++ b/radix-engine-tests/src/prelude.rs @@ -0,0 +1,2 @@ +pub use crate::common::PackageLoader; +pub use scrypto_test::prelude::*; diff --git a/radix-engine-tests/tests/application_folder.rs b/radix-engine-tests/tests/application_folder.rs index 01e2009c2e5..54939bf5a92 100644 --- a/radix-engine-tests/tests/application_folder.rs +++ b/radix-engine-tests/tests/application_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod application; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/blueprints_folder.rs b/radix-engine-tests/tests/blueprints_folder.rs index 29fd27fe72b..a8293eb6a38 100644 --- a/radix-engine-tests/tests/blueprints_folder.rs +++ b/radix-engine-tests/tests/blueprints_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod blueprints; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/db_folder.rs b/radix-engine-tests/tests/db_folder.rs index 895829cffa5..27866e59acf 100644 --- a/radix-engine-tests/tests/db_folder.rs +++ b/radix-engine-tests/tests/db_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod db; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/flash_folder.rs b/radix-engine-tests/tests/flash_folder.rs index d3600030d84..882453dcf65 100644 --- a/radix-engine-tests/tests/flash_folder.rs +++ b/radix-engine-tests/tests/flash_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod flash; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/kernel_folder.rs b/radix-engine-tests/tests/kernel_folder.rs index 5eb937cfe95..640f804e7b6 100644 --- a/radix-engine-tests/tests/kernel_folder.rs +++ b/radix-engine-tests/tests/kernel_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod kernel; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/protocol_folder.rs b/radix-engine-tests/tests/protocol_folder.rs index deb4acde2c8..f98e2595178 100644 --- a/radix-engine-tests/tests/protocol_folder.rs +++ b/radix-engine-tests/tests/protocol_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod protocol; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/system/subintent_auth.rs b/radix-engine-tests/tests/system/subintent_auth.rs index 7aba0a22df1..8c23780aae5 100644 --- a/radix-engine-tests/tests/system/subintent_auth.rs +++ b/radix-engine-tests/tests/system/subintent_auth.rs @@ -1,7 +1,7 @@ -use scrypto_test::prelude::*; +use crate::prelude::*; #[test] -fn should_not_be_able_to_use_root_auth_in_subintent() { +fn subintent_should_not_be_able_to_use_proofs_from_transaction_intent() { // Arrange let mut ledger = LedgerSimulatorBuilder::new().build(); let (public_key, _, account) = ledger.new_allocated_account(); @@ -38,6 +38,88 @@ fn should_not_be_able_to_use_root_auth_in_subintent() { }); } +#[test] +fn subintent_should_not_be_able_to_use_proofs_from_other_subintents() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + + let assert_access_rule_component_address = { + let package_address = ledger.publish_package_simple(PackageLoader::get("role_assignment")); + + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package_address, "AssertAccessRule", "new", manifest_args!()) + .build(); + + let receipt = ledger.execute_manifest(manifest, []); + receipt.expect_commit_success(); + + receipt.expect_commit(true).new_component_addresses()[0] + }; + + // We will create a complex transaction with lots of intents. + // Naming convention: subintent XXX will have children XXXa, XXXb, XXXc, etc. + // Each intent will be signed by its own key. + + let keys = indexmap! { + "aaa" => ledger.new_allocated_account().0, + "aaba" => ledger.new_allocated_account().0, + "aab" => ledger.new_allocated_account().0, + "aac" => ledger.new_allocated_account().0, + "aa" => ledger.new_allocated_account().0, + "ab" => ledger.new_allocated_account().0, + "a" => ledger.new_allocated_account().0, + "ba" => ledger.new_allocated_account().0, + "b" => ledger.new_allocated_account().0, + "c" => ledger.new_allocated_account().0, + "ROOT" => ledger.new_allocated_account().0, + }; + + // We will set all their manifests to be simple/trivial, except the AAB manifest + // which will be set to call "assert access rule" with each of the keys. + // The transaction should only pass if the AAB key is used. + for (key_name_to_assert, key_to_assert) in keys.iter() { + let mut builder = TestTransaction::new_v2_builder(ledger.next_transaction_nonce()); + + let aaa = builder.add_simple_subintent([], [keys["aaa"].signature_proof()]); + let aaba = builder.add_simple_subintent([], [keys["aaba"].signature_proof()]); + let aab = builder.add_tweaked_simple_subintent( + [aaba], + [keys["aab"].signature_proof()], + |builder| { + builder.call_method( + assert_access_rule_component_address, + "assert_access_rule", + (rule!(require(key_to_assert.signature_proof())),), + ) + }, + ); + let aac = builder.add_simple_subintent([], [keys["aac"].signature_proof()]); + let aa = builder.add_simple_subintent([aaa, aab, aac], [keys["aa"].signature_proof()]); + let ab = builder.add_simple_subintent([], [keys["ab"].signature_proof()]); + let a = builder.add_simple_subintent([aa, ab], [keys["a"].signature_proof()]); + let ba = builder.add_simple_subintent([], [keys["ba"].signature_proof()]); + let b = builder.add_simple_subintent([ba], [keys["b"].signature_proof()]); + let c = builder.add_simple_subintent([], [keys["c"].signature_proof()]); + let transaction = + builder.finish_with_simple_root_intent([a, b, c], [keys["ROOT"].signature_proof()]); + + let receipt = ledger.execute_test_transaction(transaction); + + // ASSERT + if *key_name_to_assert == "aab" { + receipt.expect_commit_success(); + } else { + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::SystemError(SystemError::AssertAccessRuleFailed) + ) + }); + } + } +} + #[test] fn should_be_able_to_use_separate_auth_in_subintent() { // Arrange @@ -72,6 +154,60 @@ fn should_be_able_to_use_separate_auth_in_subintent() { receipt.expect_commit_success(); } +#[test] +fn subintent_processor_uses_transaction_processor_global_caller() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + + let assert_access_rule_component_address = { + let package_address = ledger.publish_package_simple(PackageLoader::get("role_assignment")); + + let manifest = ManifestBuilder::new() + .lock_fee_from_faucet() + .call_function(package_address, "AssertAccessRule", "new", manifest_args!()) + .build(); + + let receipt = ledger.execute_manifest(manifest, []); + receipt.expect_commit_success(); + + receipt.expect_commit(true).new_component_addresses()[0] + }; + + // Act + let mut builder = TestTransaction::new_v2_builder(ledger.next_transaction_nonce()); + + let transaction_processor = BlueprintId { + package_address: TRANSACTION_PROCESSOR_PACKAGE, + blueprint_name: TRANSACTION_PROCESSOR_BLUEPRINT.to_string(), + }; + + let child = builder.add_subintent( + ManifestBuilder::new_subintent_v2() + .call_method( + assert_access_rule_component_address, + "assert_access_rule", + (rule!(require(global_caller(transaction_processor))),), + ) + .yield_to_parent(()) + .build(), + [], + ); + + let transaction = builder.finish_with_root_intent( + ManifestBuilder::new_v2() + .use_child("child", child) + .lock_fee_from_faucet() + .yield_to_child("child", ()) + .build(), + [], + ); + + let receipt = ledger.execute_test_transaction(transaction); + + // Assert + receipt.expect_commit_success(); +} + #[derive(Debug, Eq, PartialEq, ManifestSbor)] pub struct ManifestTransactionProcessorRunInput { pub manifest_encoded_instructions: Vec, diff --git a/radix-engine-tests/tests/system/subintent_verify_parent.rs b/radix-engine-tests/tests/system/subintent_verify_parent.rs index c5518c61d0f..44510fb2797 100644 --- a/radix-engine-tests/tests/system/subintent_verify_parent.rs +++ b/radix-engine-tests/tests/system/subintent_verify_parent.rs @@ -1,12 +1,4 @@ -use radix_common::constants::XRD; -use radix_common::crypto::HasPublicKeyHash; -use radix_engine::errors::{IntentError, RuntimeError, SystemError}; -use radix_engine_interface::macros::dec; -use radix_engine_interface::prelude::{require, require_amount, AccessRule}; -use radix_engine_interface::rule; -use radix_transactions::builder::ManifestBuilder; -use radix_transactions::model::TestTransaction; -use scrypto_test::ledger_simulator::LedgerSimulatorBuilder; +use crate::prelude::*; #[test] fn should_not_be_able_to_use_subintent_when_verify_parent_access_rule_not_met() { @@ -198,3 +190,67 @@ fn should_be_able_to_use_subintent_when_verify_parent_access_rule_is_met_on_seco // Assert receipt.expect_commit_success(); } + +#[test] +fn verify_parent_should_only_work_against_proofs_in_parent_intent() { + // Arrange + let mut ledger = LedgerSimulatorBuilder::new().build(); + + // We will create a complex transaction with lots of intents. + // Naming convention: subintent XXX will have children XXXa, XXXb, XXXc, etc. + // Each intent will be signed by its own key. + + let keys = indexmap! { + "aaa" => ledger.new_allocated_account().0, + "aaba" => ledger.new_allocated_account().0, + "aab" => ledger.new_allocated_account().0, + "aac" => ledger.new_allocated_account().0, + "aa" => ledger.new_allocated_account().0, + "ab" => ledger.new_allocated_account().0, + "a" => ledger.new_allocated_account().0, + "ba" => ledger.new_allocated_account().0, + "b" => ledger.new_allocated_account().0, + "c" => ledger.new_allocated_account().0, + "ROOT" => ledger.new_allocated_account().0, + }; + + // We will set all their manifests to be simple/trivial, except the AAB manifest + // which will be set to call "verify parent" with each of the keys. + // The transaction should only pass if the AA key is used (its parent). + for (key_name_to_assert, key_to_assert) in keys.iter() { + let mut builder = TestTransaction::new_v2_builder(ledger.next_transaction_nonce()); + + let aaa = builder.add_simple_subintent([], [keys["aaa"].signature_proof()]); + let aaba = builder.add_simple_subintent([], [keys["aaba"].signature_proof()]); + let aab = builder.add_tweaked_simple_subintent( + [aaba], + [keys["aab"].signature_proof()], + |builder| builder.verify_parent(rule!(require(key_to_assert.signature_proof()))), + ); + let aac = builder.add_simple_subintent([], [keys["aac"].signature_proof()]); + let aa = builder.add_simple_subintent([aaa, aab, aac], [keys["aa"].signature_proof()]); + let ab = builder.add_simple_subintent([], [keys["ab"].signature_proof()]); + let a = builder.add_simple_subintent([aa, ab], [keys["a"].signature_proof()]); + let ba = builder.add_simple_subintent([], [keys["ba"].signature_proof()]); + let b = builder.add_simple_subintent([ba], [keys["b"].signature_proof()]); + let c = builder.add_simple_subintent([], [keys["c"].signature_proof()]); + let transaction = + builder.finish_with_simple_root_intent([a, b, c], [keys["ROOT"].signature_proof()]); + + let receipt = ledger.execute_test_transaction(transaction); + + // ASSERT + if *key_name_to_assert == "aa" { + receipt.expect_commit_success(); + } else { + receipt.expect_specific_failure(|e| { + matches!( + e, + RuntimeError::SystemError(SystemError::IntentError( + IntentError::VerifyParentFailed + )) + ) + }); + } + } +} diff --git a/radix-engine-tests/tests/system_folder.rs b/radix-engine-tests/tests/system_folder.rs index 679a0c5d2ac..f9da4fc8742 100644 --- a/radix-engine-tests/tests/system_folder.rs +++ b/radix-engine-tests/tests/system_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod system; + +mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-tests/tests/vm_folder.rs b/radix-engine-tests/tests/vm_folder.rs index 0664f38901b..7cea481f86b 100644 --- a/radix-engine-tests/tests/vm_folder.rs +++ b/radix-engine-tests/tests/vm_folder.rs @@ -7,3 +7,7 @@ // * Have this X_folder.rs file which gets loaded by the test loader // * Use a mod definition to point to `X/mod.rs` where tests are defined mod vm; + +pub mod prelude { + pub use radix_engine_tests::prelude::*; +} diff --git a/radix-engine-toolkit-common/Cargo.toml b/radix-engine-toolkit-common/Cargo.toml index 6913a6539b5..91c859931d1 100644 --- a/radix-engine-toolkit-common/Cargo.toml +++ b/radix-engine-toolkit-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-engine-toolkit-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "Types and functions added to this repository to be shared between the radix-engine-toolkit-common and consumers of the engine, from the Radix DLT project." readme = "README.md" diff --git a/radix-engine/Cargo.toml b/radix-engine/Cargo.toml index b79cf02339a..bdb40a1e83b 100644 --- a/radix-engine/Cargo.toml +++ b/radix-engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-engine" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "Reference implementation of Radix Engine, from the Radix DLT project." readme = "README.md" diff --git a/radix-engine/README.md b/radix-engine/README.md new file mode 100644 index 00000000000..5f056ac7a8f --- /dev/null +++ b/radix-engine/README.md @@ -0,0 +1,3 @@ +# `radix-engine` + +See https://radix-engine-docs.radixdlt.com/index.html \ No newline at end of file diff --git a/radix-engine/src/blueprints/resource/auth_zone/auth_zone_substates.rs b/radix-engine/src/blueprints/resource/auth_zone/auth_zone_substates.rs index 727d5cf01c5..d6f954e77bc 100644 --- a/radix-engine/src/blueprints/resource/auth_zone/auth_zone_substates.rs +++ b/radix-engine/src/blueprints/resource/auth_zone/auth_zone_substates.rs @@ -5,30 +5,42 @@ use radix_engine_interface::blueprints::resource::*; pub struct AuthZone { pub proofs: Vec, - // Virtualized resources, note that one cannot create proofs with virtual resources but only be used for AuthZone checks + pub simulate_all_proofs_under_resources: BTreeSet, + pub implicit_non_fungible_proofs: BTreeSet, + + pub direct_caller_package_address: Option, + pub global_caller: Option<(GlobalCaller, Reference)>, + + pub parent: Option, +} + +#[derive(ScryptoSbor)] +#[sbor(type_name = "AuthZone")] +/// This is just the same as `AuthZone`, but with old field names. +/// This allows us to have a fixed genesis schema for the resource package. +pub struct GenesisSchemaAuthZone { + pub proofs: Vec, pub virtual_resources: BTreeSet, pub virtual_non_fungibles: BTreeSet, - pub local_caller_package_address: Option, pub global_caller: Option<(GlobalCaller, Reference)>, - pub parent: Option, } impl AuthZone { pub fn new( proofs: Vec, - virtual_resources: BTreeSet, - virtual_non_fungibles: BTreeSet, - local_caller_package_address: Option, + simulate_all_proofs_under_resources: BTreeSet, + implicit_non_fungible_proofs: BTreeSet, + direct_caller_package_address: Option, global_caller: Option<(GlobalCaller, Reference)>, parent: Option, ) -> Self { Self { proofs, - virtual_resources, - virtual_non_fungibles, - local_caller_package_address, + simulate_all_proofs_under_resources, + implicit_non_fungible_proofs, + direct_caller_package_address, global_caller, parent, } @@ -38,34 +50,34 @@ impl AuthZone { &self.proofs } - pub fn virtual_resources(&self) -> &BTreeSet { - &self.virtual_resources + pub fn simulate_all_proofs_under_resources(&self) -> &BTreeSet { + &self.simulate_all_proofs_under_resources } - pub fn virtual_non_fungibles(&self) -> &BTreeSet { - &self.virtual_non_fungibles + pub fn implicit_non_fungible_proofs(&self) -> &BTreeSet { + &self.implicit_non_fungible_proofs } - pub fn local_virtual_non_fungibles(&self) -> BTreeSet { - let mut virtual_proofs = BTreeSet::new(); + pub fn local_implicit_non_fungible_proofs(&self) -> BTreeSet { + let mut local_implicit_non_fungible_proofs = BTreeSet::new(); // Local Caller package address - if let Some(local_package_address) = self.local_caller_package_address { + if let Some(local_package_address) = self.direct_caller_package_address { let non_fungible_global_id = NonFungibleGlobalId::package_of_direct_caller_badge(local_package_address); - virtual_proofs.insert(non_fungible_global_id); + local_implicit_non_fungible_proofs.insert(non_fungible_global_id); } // Global Caller if let Some((global_caller, _global_caller_reference)) = &self.global_caller { - if !global_caller.is_frame_owned() { + if !global_caller.is_actually_frame_owned() { let non_fungible_global_id = NonFungibleGlobalId::global_caller_badge(global_caller.clone()); - virtual_proofs.insert(non_fungible_global_id); + local_implicit_non_fungible_proofs.insert(non_fungible_global_id); } } - virtual_proofs + local_implicit_non_fungible_proofs } pub fn push(&mut self, proof: Proof) { @@ -77,9 +89,9 @@ impl AuthZone { } pub fn remove_signature_proofs(&mut self) { - self.virtual_resources + self.simulate_all_proofs_under_resources .retain(|x| x != &SECP256K1_SIGNATURE_RESOURCE && x != &ED25519_SIGNATURE_RESOURCE); - self.virtual_non_fungibles.retain(|x| { + self.implicit_non_fungible_proofs.retain(|x| { x.resource_address() != SECP256K1_SIGNATURE_RESOURCE && x.resource_address() != ED25519_SIGNATURE_RESOURCE }); diff --git a/radix-engine/src/blueprints/resource/package.rs b/radix-engine/src/blueprints/resource/package.rs index 680faf54fe5..7105d9e04b1 100644 --- a/radix-engine/src/blueprints/resource/package.rs +++ b/radix-engine/src/blueprints/resource/package.rs @@ -798,7 +798,7 @@ impl ResourceNativePackage { let mut fields = Vec::new(); fields.push(FieldSchema::static_field( - aggregator.add_child_type_and_descendents::(), + aggregator.add_child_type_and_descendents::(), )); let mut functions = index_map_new(); diff --git a/radix-engine/src/system/actor.rs b/radix-engine/src/system/actor.rs index 8c593c423e2..93f3c7c9d38 100644 --- a/radix-engine/src/system/actor.rs +++ b/radix-engine/src/system/actor.rs @@ -69,12 +69,21 @@ pub struct BlueprintHookActor { #[derive(Debug, Clone, ScryptoSbor, PartialEq, Eq)] pub enum Actor { + /// In System V1, there was an explicit call to initialize the transaction processor. + /// This call has to have an actor making the call, which is the Root. + /// + /// From V2 onwards, we don't have an explicit function call to initialize the transaction + /// processor - but we still temporarily set a CallFrameInit with a `Root` actor. + /// This is used to set up the initial AuthZone in [`MultiThreadIntentProcessor::init`]. + /// + /// [`MultiThreadIntentProcessor::init`]: crate::system::transaction::multithread_intent_processor::MultiThreadIntentProcessor::init Root, Method(MethodActor), Function(FunctionActor), BlueprintHook(BlueprintHookActor), } +// This is only used by `kernel_create_kernel_for_testing` in the testing framework. impl Default for Actor { fn default() -> Self { Self::Root diff --git a/radix-engine/src/system/system_callback.rs b/radix-engine/src/system/system_callback.rs index 5297e52a7fe..bffeec49640 100644 --- a/radix-engine/src/system/system_callback.rs +++ b/radix-engine/src/system/system_callback.rs @@ -246,6 +246,13 @@ impl SystemVersion { true } + pub fn should_inject_transaction_processor_proofs_in_call_function(&self) -> bool { + match self { + SystemVersion::V1 => true, + SystemVersion::V2 | SystemVersion::V3 => false, + } + } + pub fn should_charge_for_transaction_intent(&self) -> bool { match self { SystemVersion::V1 => false, diff --git a/radix-engine/src/system/system_modules/auth/auth_module.rs b/radix-engine/src/system/system_modules/auth/auth_module.rs index 4b8088037f2..88d306dc0ec 100644 --- a/radix-engine/src/system/system_modules/auth/auth_module.rs +++ b/radix-engine/src/system/system_modules/auth/auth_module.rs @@ -44,9 +44,10 @@ pub struct Unauthorized { #[derive(Debug, Clone)] pub struct AuthModule { - /// Special-case the initial transaction processor function call and - /// add virtual resources to the transaction processor call frame - pub generate_transaction_processor_auth_zone: Option, + /// SystemV1 only - we special-case the initial transaction processor + /// function call and add virtual resources to the transaction processor + /// call frame + pub v1_transaction_processor_proofs_for_injection: Option, } pub enum AuthorizationCheckResult { @@ -72,17 +73,22 @@ pub enum ResolvedPermission { impl AuthModule { pub fn new() -> Self { Self { - generate_transaction_processor_auth_zone: None, + v1_transaction_processor_proofs_for_injection: None, } } pub fn new_with_transaction_processor_auth_zone(auth_zone_init: AuthZoneInit) -> Self { Self { - generate_transaction_processor_auth_zone: Some(auth_zone_init), + v1_transaction_processor_proofs_for_injection: Some(auth_zone_init), } } - fn on_call_function_auth_zone_params( + // In SystemV1, the transaction processor is initiated via a call_function, and we + // used this to inject the signature proofs and resource simulation. + // + // In SystemV2 and later, we initialize the auth zone directly, so no longer have + // need to do this check. + fn system_v1_resolve_injectable_transaction_processor_proofs( system: &mut SystemService, blueprint_id: &BlueprintId, ) -> Result<(BTreeSet, BTreeSet), RuntimeError> { @@ -93,7 +99,9 @@ impl AuthModule { let is_root_thread = system.kernel_get_current_stack_id_uncosted() == 0; if is_root_call_frame && is_root_thread { let auth_module = &system.kernel_get_system().modules.auth; - if let Some(auth_zone_init) = &auth_module.generate_transaction_processor_auth_zone { + if let Some(auth_zone_init) = &auth_module.v1_transaction_processor_proofs_for_injection + { + // This is an extra sanity check / defense in depth which I believe isn't strictly needed. let is_transaction_processor_blueprint = blueprint_id .package_address .eq(&TRANSACTION_PROCESSOR_PACKAGE) @@ -119,9 +127,25 @@ impl AuthModule { ) -> Result { // Create AuthZone let auth_zone = { - let (virtual_resources, virtual_non_fungibles) = - Self::on_call_function_auth_zone_params(system, blueprint_id)?; - Self::create_auth_zone(system, None, virtual_resources, virtual_non_fungibles)? + if system + .system() + .versioned_system_logic + .should_inject_transaction_processor_proofs_in_call_function() + { + let (simulate_all_proofs_under_resources, implicit_non_fungible_proofs) = + Self::system_v1_resolve_injectable_transaction_processor_proofs( + system, + blueprint_id, + )?; + Self::create_auth_zone( + system, + None, + simulate_all_proofs_under_resources, + implicit_non_fungible_proofs, + )? + } else { + Self::create_auth_zone(system, None, Default::default(), Default::default())? + } }; // Check authorization @@ -203,36 +227,45 @@ impl AuthModule { pub fn on_call_fn_mock( system: &mut SystemService, receiver: Option<(&NodeId, bool)>, - virtual_resources: BTreeSet, - virtual_non_fungibles: BTreeSet, + simulate_all_proofs_under_resources: BTreeSet, + implicit_non_fungible_proofs: BTreeSet, ) -> Result { - Self::create_auth_zone(system, receiver, virtual_resources, virtual_non_fungibles) + Self::create_auth_zone( + system, + receiver, + simulate_all_proofs_under_resources, + implicit_non_fungible_proofs, + ) } fn copy_global_caller( system: &mut SystemService, - node_id: &NodeId, + direct_caller_auth_zone_id: &NodeId, ) -> Result<(Option<(GlobalCaller, Reference)>, Option), RuntimeError> { - let handle = system.kernel_open_substate( - node_id, + let direct_caller_auth_zone_handle = system.kernel_open_substate( + direct_caller_auth_zone_id, MAIN_BASE_PARTITION, &AuthZoneField::AuthZone.into(), LockFlags::read_only(), SystemLockData::default(), )?; - let auth_zone = system - .kernel_read_substate(handle)? + let direct_caller_auth_zone = system + .kernel_read_substate(direct_caller_auth_zone_handle)? .as_typed::>() .unwrap(); - Ok((auth_zone.into_payload().global_caller, Some(handle))) + + Ok(( + direct_caller_auth_zone.into_payload().global_caller, + Some(direct_caller_auth_zone_handle), + )) } pub(crate) fn create_auth_zone( system: &mut SystemService, receiver: Option<(&NodeId, bool)>, - virtual_resources: BTreeSet, - virtual_non_fungibles: BTreeSet, + simulate_all_proofs_under_resources: BTreeSet, + implicit_non_fungible_proofs: BTreeSet, ) -> Result { let (auth_zone, parent_lock_handle) = { let is_global_context_change = if let Some((receiver, direct_access)) = receiver { @@ -242,58 +275,104 @@ impl AuthModule { true }; - let current_actor = system.current_actor(); - let local_package_address = current_actor.package_address(); + let direct_caller = system.current_actor(); + let direct_caller_package_address = direct_caller.package_address(); // Retrieve global caller property of next auth zone - let (global_caller, parent_lock_handle) = match current_actor { + let (global_caller, parent_lock_handle) = match direct_caller { Actor::Root | Actor::BlueprintHook(..) => (None, None), - Actor::Method(current_method_actor) => { - let node_visibility = - system.kernel_get_node_visibility_uncosted(¤t_method_actor.node_id); - let current_ref_origin = node_visibility - .reference_origin(current_method_actor.node_id) + Actor::Method(direct_caller_method_actor) => { + let direct_caller_ancestor_visibility_origin = system + .kernel_get_node_visibility_uncosted(&direct_caller_method_actor.node_id) + .reference_origin(direct_caller_method_actor.node_id) .unwrap(); - let self_auth_zone = current_method_actor.auth_zone; - match (current_ref_origin, is_global_context_change) { - // Actor is part of the global component state tree AND next actor is a global context change - (ReferenceOrigin::Global(address), true) => { - (Some((address.into(), Reference(self_auth_zone))), None) + let direct_caller_auth_zone = direct_caller_method_actor.auth_zone; + + // The `direct_caller_ancestor_visibility_origin` is rather indirect, but it is intended to + // capture the concept: "Who is the direct caller's ancestor for the purpose of auth?" + // + // In particular: + // * If the direct caller is a global object, then it has ReferenceOrigin::Global + // * If the direct caller was loaded from a substate belonging to a global object, + // then it gets a Borrowed visibility, which transforms into a ReferenceOrigin::Global. + // This also works transitively. + // * If the direct caller was made visible by being passed to the call frame, (i.e. it didn't + // arise from track), then it is `ReferenceOrigin::FrameOwned` + // + // At some point we should refactor this to make this all much more explicit. + match ( + direct_caller_ancestor_visibility_origin, + is_global_context_change, + ) { + // Direct caller's ancestor is global AND this call is a global context change + (ReferenceOrigin::Global(global_root_address), true) => { + let global_caller_address = global_root_address.into(); + let global_caller_leaf_auth_zone_reference = + Reference(direct_caller_auth_zone); + ( + Some(( + global_caller_address, + global_caller_leaf_auth_zone_reference, + )), + None, + ) } - // Actor is part of the global component state tree AND next actor is NOT a global context change + // Direct caller's ancestor is global AND this call is NOT a global context change (ReferenceOrigin::Global(..), false) => { - Self::copy_global_caller(system, &self_auth_zone)? + Self::copy_global_caller(system, &direct_caller_auth_zone)? } - // Actor is a direct access reference + // Direct caller's ancestor was directly accessed (ReferenceOrigin::DirectlyAccessed, _) => (None, None), - // Actor is a non-global reference + // Direct caller's ancestor was borrowed from an internal referance in a substate (ReferenceOrigin::SubstateNonGlobalReference(..), _) => (None, None), - // Actor is a frame-owned object + // Direct caller's ancestor was passed into the call frame (ReferenceOrigin::FrameOwned, _) => { - // In the past frame-owned objects were inheriting the AuthZone of the caller. - // It was a critical issue, which could allow called components to eg. - // withdraw resources from the signing account. - // To prevent this we use TRANSACTION_TRACKER NodeId as a marker, that we are dealing with a frame-owned object. - // It is checked later on when virtual proofs for AuthZone are verified. - // Approach with such marker allows to keep backward compatibility with substate database. + // In the past, all frame-owned direct callers copied their global caller to their callee. + // This was a mistake, as it could allow frame-owned objects to use proofs from e.g. + // the transaction processor. + // + // A fix needed to be backwards-compatible (without changing the size of substates, which would + // affect the fee costs), and whilst the auth zone reference could be fixed by using a `Reference` + // to `self_auth_zone`, the global caller was harder. + // + // As a work-around, the `FRAME_OWNED_GLOBAL_MARKER = TRANSACTION_TRACKER` was used as a marker + // that the global caller was invalid and shouldn't be used. It is checked used to avoid adding + // a global caller implicit proof in this case. + let (caller, lock_handle) = - Self::copy_global_caller(system, &self_auth_zone)?; - ( - caller.map(|_| { - (FRAME_OWNED_GLOBAL_MARKER.into(), Reference(self_auth_zone)) - }), - lock_handle, - ) + Self::copy_global_caller(system, &direct_caller_auth_zone)?; + + // To avoid changing the size of the substate, we need to make sure that we replace Some + // with Some and None with None. + let global_caller = match caller { + Some(_) => { + let global_caller_address = FRAME_OWNED_GLOBAL_MARKER.into(); + // NOTE: This results in both the global caller stack and the parent stack being the same. + // This won't cause any critical issues, but should be reworked at some point. + let global_caller_leaf_auth_zone_reference = + Reference(direct_caller_auth_zone); + Some(( + global_caller_address, + global_caller_leaf_auth_zone_reference, + )) + } + None => None, + }; + + (global_caller, lock_handle) } } } Actor::Function(function_actor) => { - let self_auth_zone = function_actor.auth_zone; + let direct_caller_auth_zone = function_actor.auth_zone; let global_caller = function_actor.as_global_caller(); if is_global_context_change { - (Some((global_caller, Reference(self_auth_zone))), None) + ( + Some((global_caller, Reference(direct_caller_auth_zone))), + None, + ) } else { - Self::copy_global_caller(system, &self_auth_zone)? + Self::copy_global_caller(system, &direct_caller_auth_zone)? } } }; @@ -309,9 +388,9 @@ impl AuthModule { let auth_zone = AuthZone::new( vec![], - virtual_resources, - virtual_non_fungibles, - local_package_address, + simulate_all_proofs_under_resources, + implicit_non_fungible_proofs, + direct_caller_package_address, global_caller, auth_zone_parent, ); diff --git a/radix-engine/src/system/system_modules/auth/authorization.rs b/radix-engine/src/system/system_modules/auth/authorization.rs index af387165cb4..2d7c6cd459b 100644 --- a/radix-engine/src/system/system_modules/auth/authorization.rs +++ b/radix-engine/src/system/system_modules/auth/authorization.rs @@ -71,18 +71,20 @@ impl Authorization { handles.push(handle); { - let mut virtual_non_fungible_global_ids = BTreeSet::new(); - let virtual_resources = auth_zone.virtual_resources(); + let mut implicit_non_fungible_proofs = BTreeSet::new(); + let simulate_all_proofs_under_resources = + auth_zone.simulate_all_proofs_under_resources(); - virtual_non_fungible_global_ids.extend(auth_zone.virtual_non_fungibles().clone()); + implicit_non_fungible_proofs + .extend(auth_zone.implicit_non_fungible_proofs().clone()); let proofs = auth_zone.proofs(); // Check if check( proofs, - virtual_resources, - virtual_non_fungible_global_ids, + simulate_all_proofs_under_resources, + implicit_non_fungible_proofs, api, )? { pass = true; @@ -132,17 +134,21 @@ impl Authorization { .unwrap() .into_payload(); - // Check Local virtual non fungibles - let virtual_proofs = auth_zone.local_virtual_non_fungibles(); - if !virtual_proofs.is_empty() { - if check(&[], &btreeset!(), virtual_proofs, api)? { + // Check local implicit non fungible proofs + let local_implicit_non_fungible_proofs = auth_zone.local_implicit_non_fungible_proofs(); + if !local_implicit_non_fungible_proofs.is_empty() { + if check(&[], &btreeset!(), local_implicit_non_fungible_proofs, api)? { return Ok(true); } } // Check global caller's full auth zone - if let Some((_global_caller, global_caller_reference)) = &auth_zone.global_caller { - if Self::global_auth_zone_matches(api, &global_caller_reference.0, &check)? { + if let Some((_, global_caller_leaf_auth_zone_reference)) = &auth_zone.global_caller { + if Self::global_auth_zone_matches( + api, + &global_caller_leaf_auth_zone_reference.0, + &check, + )? { return Ok(true); } } diff --git a/radix-engine/src/system/system_modules/costing/costing_module.rs b/radix-engine/src/system/system_modules/costing/costing_module.rs index a7a77c45717..c5e0be27a7b 100644 --- a/radix-engine/src/system/system_modules/costing/costing_module.rs +++ b/radix-engine/src/system/system_modules/costing/costing_module.rs @@ -405,7 +405,9 @@ impl PrivilegedSystemModule for CostingModule { api: &mut impl SystemBasedKernelApi, invocation: &KernelInvocation, ) -> Result<(), RuntimeError> { - // Skip invocation costing for transaction processor + // This check only applies in SystemV1. + // In this case, there was a call from Root => Transaction Processor, which this check avoids charging for. + // From SystemV2 onwards, there is no explicit call, and the Root actor is simply overwritten. if api.kernel_get_system_state().current_call_frame.is_root() { return Ok(()); } @@ -495,7 +497,9 @@ impl> SystemModule for CostingMod }); } - // Skip invocation costing for transaction processor + // This check only applies in SystemV1. + // In this case, there was a call from Root => Transaction Processor, which this check avoids charging for. + // From SystemV2 onwards, there is no explicit call, and the Root actor is simply overwritten. if is_root { return Ok(()); } @@ -527,7 +531,9 @@ impl> SystemModule for CostingMod }); } - // Skip invocation costing for transaction processor + // This check only applies in SystemV1. + // In this case, there was a call from Root => Transaction Processor, which this check avoids charging for. + // From SystemV2 onwards, there is no explicit call, and the Root actor is simply overwritten. if api.system_state().current_call_frame.is_root() { return Ok(()); } diff --git a/radix-engine/src/system/transaction/multithread_intent_processor.rs b/radix-engine/src/system/transaction/multithread_intent_processor.rs index 296bc227351..00bf9de02ca 100644 --- a/radix-engine/src/system/transaction/multithread_intent_processor.rs +++ b/radix-engine/src/system/transaction/multithread_intent_processor.rs @@ -44,6 +44,9 @@ impl<'e> MultiThreadIntentProcessor<'e> { for (thread_id, intent) in executable.all_intents().enumerate() { api.kernel_switch_stack(thread_id)?; + // We create the auth zone for the transaction processor. + // This needs a `SystemService` to resolve the current actor (Root) to get the global caller (None), + // and also to create the node and substates. let mut system_service = SystemService::new(api); let simulate_every_proof_under_resources = intent .auth_zone_init diff --git a/radix-native-sdk/Cargo.toml b/radix-native-sdk/Cargo.toml index 5bc9c200492..e9416f30aa9 100644 --- a/radix-native-sdk/Cargo.toml +++ b/radix-native-sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-native-sdk" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library for developing native blueprints, from the Radix DLT project." readme = "README.md" diff --git a/radix-rust/Cargo.toml b/radix-rust/Cargo.toml index 1921e35b525..8c8943f1e7d 100644 --- a/radix-rust/Cargo.toml +++ b/radix-rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A very thin abstraction over Rust std library for convenience, from the Radix DLT project." readme = "README.md" diff --git a/radix-rust/src/macros.rs b/radix-rust/src/macros.rs index 841ebe7650e..9c40dc160d4 100644 --- a/radix-rust/src/macros.rs +++ b/radix-rust/src/macros.rs @@ -46,7 +46,7 @@ macro_rules! assert_matches { ) } }; - ($expression:expr, $pattern:pat $(if $condition:expr)? $(,)? => $code:expr) => { + ($expression:expr, $pattern:pat $(if $condition:expr)? => $code:expr $(,)?) => { match $expression { $pattern $(if $condition)? => $code, ref expression => panic!( @@ -67,7 +67,7 @@ macro_rules! assert_matches { ) } }; - ($expression:expr, $pattern:pat $(if $condition:expr)? $(,)? => $code:expr, $($arg:tt)+) => { + ($expression:expr, $pattern:pat $(if $condition:expr)? => $code:expr, $($arg:tt)+) => { match $expression { $pattern $(if $condition)? => $code, ref expression => panic!( diff --git a/radix-sbor-derive/Cargo.toml b/radix-sbor-derive/Cargo.toml index cacacea0bfe..1389c0135dc 100644 --- a/radix-sbor-derive/Cargo.toml +++ b/radix-sbor-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library of macros for deriving Scrypto SBOR and Manifest SBOR implementation, from the Radix DLT project." readme = "README.md" diff --git a/radix-substate-store-impls/Cargo.toml b/radix-substate-store-impls/Cargo.toml index 7085c24515b..e451c232001 100644 --- a/radix-substate-store-impls/Cargo.toml +++ b/radix-substate-store-impls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-substate-store-impls" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "Includes various substate store implementations, from the Radix DLT project." readme = "README.md" diff --git a/radix-substate-store-interface/Cargo.toml b/radix-substate-store-interface/Cargo.toml index e55bbf0683b..86d70dd8c1f 100644 --- a/radix-substate-store-interface/Cargo.toml +++ b/radix-substate-store-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-substate-store-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "The interface exposed by all substate stores, from the Radix DLT project." readme = "README.md" diff --git a/radix-substate-store-queries/Cargo.toml b/radix-substate-store-queries/Cargo.toml index f0bb09b21e9..54e252ea63b 100644 --- a/radix-substate-store-queries/Cargo.toml +++ b/radix-substate-store-queries/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-substate-store-queries" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library for interpreting raw substates with knowledge of higher layers, from the Radix DLT project." readme = "README.md" diff --git a/radix-transaction-scenarios/Cargo.toml b/radix-transaction-scenarios/Cargo.toml index cd00ca2ee98..e999e48cdc4 100644 --- a/radix-transaction-scenarios/Cargo.toml +++ b/radix-transaction-scenarios/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-transaction-scenarios" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A set of common transaction scenarios for testing and demonstration purpose, from the Radix DLT project." readme = "README.md" diff --git a/radix-transactions/Cargo.toml b/radix-transactions/Cargo.toml index 868d9386572..5393f9a2640 100644 --- a/radix-transactions/Cargo.toml +++ b/radix-transactions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "radix-transactions" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "Various Radix transaction models and the manifest compiler/decompiler, from the Radix DLT project." readme = "README.md" diff --git a/radix-transactions/src/builder/manifest_builder.rs b/radix-transactions/src/builder/manifest_builder.rs index 7cf253ff7c3..2c2e5844ac4 100644 --- a/radix-transactions/src/builder/manifest_builder.rs +++ b/radix-transactions/src/builder/manifest_builder.rs @@ -343,7 +343,9 @@ impl ManifestBuilder { self } - #[deprecated = "This should not be used apart from for test code purposefully constructing invalid manifests. Instead use the more-tailored instruction, or add_instruction_advanced."] + /// This should not be used apart from for test code purposefully constructing invalid manifests. + /// Instead use the more-tailored instruction, or add_instruction_advanced. + #[cfg(test)] pub fn add_raw_instruction_ignoring_all_side_effects( self, instruction: impl Into, diff --git a/radix-transactions/src/builder/transaction_builder.rs b/radix-transactions/src/builder/transaction_builder.rs index d76407db905..cc452774697 100644 --- a/radix-transactions/src/builder/transaction_builder.rs +++ b/radix-transactions/src/builder/transaction_builder.rs @@ -70,7 +70,7 @@ impl TransactionV1Builder { self } - pub fn sign(mut self, signer: &S) -> Self { + pub fn sign(mut self, signer: S) -> Self { let intent = self.transaction_intent(); let prepared = intent .prepare(PreparationSettings::latest_ref()) @@ -80,7 +80,7 @@ impl TransactionV1Builder { self } - pub fn multi_sign(mut self, signers: &[&S]) -> Self { + pub fn multi_sign(mut self, signers: impl IntoIterator) -> Self { let intent = self.transaction_intent(); let prepared = intent .prepare(PreparationSettings::latest_ref()) @@ -97,7 +97,7 @@ impl TransactionV1Builder { self } - pub fn notarize(mut self, signer: &S) -> Self { + pub fn notarize(mut self, signer: S) -> Self { let signed_intent = self.signed_transaction_intent(); let prepared = signed_intent .prepare(PreparationSettings::latest_ref()) @@ -182,7 +182,7 @@ pub type SignedPartialTransactionV2Builder = PartialTransactionV2Builder; /// In future, this may become a state-machine style builder, to catch more errors at compile time. #[derive(Default, Clone)] pub struct PartialTransactionV2Builder { - child_partial_transactions: IndexMap< + pub(crate) child_partial_transactions: IndexMap< String, ( SubintentHash, @@ -190,9 +190,9 @@ pub struct PartialTransactionV2Builder { TransactionObjectNames, ), >, - root_subintent_header: Option, - root_subintent_message: Option, - root_subintent_manifest: Option, + pub(crate) root_subintent_header: Option, + pub(crate) root_subintent_message: Option, + pub(crate) root_subintent_manifest: Option, // Cached once created root_subintent: Option<(SubintentV2, ManifestObjectNames)>, prepared_root_subintent: Option, @@ -342,14 +342,14 @@ impl PartialTransactionV2Builder { self.create_prepared_subintent().subintent_hash() } - pub fn sign(mut self, signer: &S) -> Self { + pub fn sign(mut self, signer: S) -> Self { let hash = self.subintent_hash(); self.root_subintent_signatures .push(IntentSignatureV1(signer.sign_with_public_key(&hash))); self } - pub fn multi_sign(mut self, signers: &[&S]) -> Self { + pub fn multi_sign(mut self, signers: impl IntoIterator) -> Self { let hash = self.subintent_hash(); for signer in signers { self.root_subintent_signatures @@ -581,7 +581,7 @@ pub struct TransactionV2Builder { // Note - these names are long, but agreed with Yulong that we would clarify // non_root_subintents from root_subintent / transaction_intent so this is // applying that logic to these field names - child_partial_transactions: IndexMap< + pub(crate) child_partial_transactions: IndexMap< String, ( SubintentHash, @@ -589,10 +589,10 @@ pub struct TransactionV2Builder { TransactionObjectNames, ), >, - transaction_header: Option, - transaction_intent_header: Option, - transaction_intent_message: Option, - transaction_intent_manifest: Option, + pub(crate) transaction_header: Option, + pub(crate) transaction_intent_header: Option, + pub(crate) transaction_intent_message: Option, + pub(crate) transaction_intent_manifest: Option, // Temporarily cached once created transaction_intent_and_non_root_subintent_signatures: Option<(TransactionIntentV2, NonRootSubintentSignaturesV2)>, @@ -796,14 +796,14 @@ impl TransactionV2Builder { self.create_prepared_intent().transaction_intent_hash() } - pub fn sign(mut self, signer: &S) -> Self { + pub fn sign(mut self, signer: S) -> Self { let hash = self.intent_hash(); self.transaction_intent_signatures .push(IntentSignatureV1(signer.sign_with_public_key(&hash))); self } - pub fn multi_sign(mut self, signers: &[&S]) -> Self { + pub fn multi_sign(mut self, signers: impl IntoIterator) -> Self { let hash = self.intent_hash(); for signer in signers { self.transaction_intent_signatures diff --git a/radix-transactions/src/errors.rs b/radix-transactions/src/errors.rs index 02677532fc9..681155d2d57 100644 --- a/radix-transactions/src/errors.rs +++ b/radix-transactions/src/errors.rs @@ -3,11 +3,9 @@ use sbor::*; #[derive(Debug, Clone, PartialEq, Eq)] pub enum HeaderValidationError { - UnknownVersion(u8), InvalidEpochRange, InvalidTimestampRange, InvalidNetwork, - InvalidCostUnitLimit, InvalidTip, NoValidEpochRangeAcrossAllIntents, NoValidTimestampRangeAcrossAllIntents, diff --git a/radix-transactions/src/manifest/any_instruction.rs b/radix-transactions/src/manifest/any_instruction.rs new file mode 100644 index 00000000000..247b6fe3e7a --- /dev/null +++ b/radix-transactions/src/manifest/any_instruction.rs @@ -0,0 +1,86 @@ +use crate::internal_prelude::*; +use decompiler::*; + +/// A type representing an enum of all possible instructions. +/// This can then be mapped into a specific instruction type. +pub type AnyInstruction = InstructionV2; + +/// A marker trait for an Instruction set, e.g. InstructionV1 +pub trait ManifestInstructionSet: TryFrom + Into + Clone { + fn decompile( + &self, + context: &mut DecompilationContext, + ) -> Result { + self.map_ref(context) + } + + fn effect(&self) -> ManifestInstructionEffect { + self.map_ref(EffectMapper) + } + + fn into_any(self) -> AnyInstruction { + self.map_self(IntoAnyMapper) + } + + fn try_convert(self) -> Result>::Error> + where + AnyInstruction: TryInto, + { + self.map_self(IntoThroughAnyMapper(PhantomData)) + } + + fn map_ref(&self, mapper: M) -> M::Output<'_>; + fn map_self(self, mapper: M) -> M::Output; +} + +/// This trait is intended to reduce the boilerplate of defining actions which can +/// be applied to all instructions in a set. +pub trait InstructionRefMapper { + type Output<'i>; + fn apply<'i>(self, instruction: &'i impl ManifestInstruction) -> Self::Output<'i>; +} + +impl<'a, 'b> InstructionRefMapper for &'b mut DecompilationContext<'a> { + type Output<'i> = Result; + + fn apply<'i>(self, instruction: &'i impl ManifestInstruction) -> Self::Output<'i> { + instruction.decompile(self) + } +} + +struct EffectMapper; +impl InstructionRefMapper for EffectMapper { + type Output<'i> = ManifestInstructionEffect<'i>; + + fn apply<'i>(self, instruction: &'i impl ManifestInstruction) -> Self::Output<'i> { + instruction.effect() + } +} + +pub trait OwnedInstructionMapper { + type Output; + fn apply(self, instruction: impl ManifestInstruction) -> Self::Output; +} + +struct IntoAnyMapper; +impl OwnedInstructionMapper for IntoAnyMapper { + type Output = AnyInstruction; + + fn apply(self, instruction: impl ManifestInstruction) -> AnyInstruction { + instruction.into_any() + } +} + +struct IntoThroughAnyMapper(PhantomData) +where + AnyInstruction: TryInto; +impl OwnedInstructionMapper for IntoThroughAnyMapper +where + AnyInstruction: TryInto, +{ + type Output = Result>::Error>; + + fn apply(self, instruction: impl ManifestInstruction) -> Self::Output { + >::try_into(instruction.into_any()) + } +} diff --git a/radix-transactions/src/manifest/generator.rs b/radix-transactions/src/manifest/generator.rs index c1d2fc85859..e8ac7443510 100644 --- a/radix-transactions/src/manifest/generator.rs +++ b/radix-transactions/src/manifest/generator.rs @@ -2262,14 +2262,13 @@ pub fn generator_error_diagnostics( GeneratorErrorKind::ManifestBuildError( ManifestBuildError::PreallocatedAddressesUnsupportedByManifestType, ) => { - let title = - format!("preallocated addresses are not supported in this manifest version"); + let title = format!("preallocated addresses are not supported in this manifest type"); (title, "unsupported instruction".to_string()) } GeneratorErrorKind::ManifestBuildError( ManifestBuildError::ChildSubintentsUnsupportedByManifestType, ) => { - let title = format!("child subintents are not supported in this manifest version"); + let title = format!("child subintents are not supported in this manifest type"); (title, "unsupported instruction".to_string()) } GeneratorErrorKind::HeaderInstructionMustComeFirst => { diff --git a/radix-transactions/src/manifest/manifest_instructions.rs b/radix-transactions/src/manifest/manifest_instructions.rs index 32a2f187a19..ca23e1d4328 100644 --- a/radix-transactions/src/manifest/manifest_instructions.rs +++ b/radix-transactions/src/manifest/manifest_instructions.rs @@ -12,20 +12,6 @@ use radix_engine_interface::object_modules::royalty::*; use ManifestInstructionEffect as Effect; -/// A type representing an enum of all possible instructions. -/// This can then be mapped into a specific instruction type. -pub type AnyInstruction = InstructionV2; - -/// A marker trait for an Instruction set, e.g. InstructionV1 -pub trait ManifestInstructionSet: TryFrom + Into + Clone { - fn decompile( - &self, - context: &mut DecompilationContext, - ) -> Result; - - fn effect(&self) -> Effect; -} - pub trait ManifestInstruction: Into { const IDENT: &'static str; const ID: u8; @@ -1341,6 +1327,14 @@ pub struct YieldToParent { pub args: ManifestValue, } +impl YieldToParent { + pub fn empty() -> Self { + Self { + args: ManifestValue::unit(), + } + } +} + impl ManifestInstruction for YieldToParent { const IDENT: &'static str = "YIELD_TO_PARENT"; const ID: u8 = INSTRUCTION_YIELD_TO_PARENT_DISCRIMINATOR; diff --git a/radix-transactions/src/manifest/manifest_traits.rs b/radix-transactions/src/manifest/manifest_traits.rs index 178617513d2..5613fe097e2 100644 --- a/radix-transactions/src/manifest/manifest_traits.rs +++ b/radix-transactions/src/manifest/manifest_traits.rs @@ -106,7 +106,7 @@ pub trait ReadableManifestBase { fn get_known_object_names_ref(&self) -> ManifestObjectNamesRef; } -/// An object-safe of ReadableManifest +/// An object-safe version of ReadableManifest pub trait ReadableManifest: ReadableManifestBase { fn iter_instruction_effects(&self) -> impl Iterator; fn iter_cloned_instructions(&self) -> impl Iterator; @@ -141,3 +141,74 @@ impl ReadableManifest for T { static NO_PREALLOCATED_ADDRESSES: [PreAllocatedAddress; 0] = []; static NO_CHILD_SUBINTENTS: [ChildSubintentSpecifier; 0] = []; + +pub struct EphemeralManifest<'a, I: ManifestInstructionSet> { + pub is_subintent: bool, + pub instructions: &'a [I], + pub blobs: &'a IndexMap>, + pub child_subintent_specifiers: Option<&'a IndexSet>, + pub known_object_names_ref: ManifestObjectNamesRef<'a>, +} + +impl<'a, I: ManifestInstructionSet> EphemeralManifest<'a, I> { + pub fn new_childless_transaction_manifest( + instructions: &'a [I], + blobs: &'a IndexMap>, + ) -> Self { + Self { + is_subintent: false, + instructions, + blobs, + child_subintent_specifiers: None, + known_object_names_ref: ManifestObjectNamesRef::Unknown, + } + } + + pub fn new( + instructions: &'a [I], + blobs: &'a IndexMap>, + child_subintent_specifiers: &'a IndexSet, + is_subintent: bool, + ) -> Self { + Self { + is_subintent, + instructions, + blobs, + child_subintent_specifiers: Some(child_subintent_specifiers), + known_object_names_ref: ManifestObjectNamesRef::Unknown, + } + } +} + +impl<'a, I: ManifestInstructionSet> ReadableManifestBase for EphemeralManifest<'a, I> { + fn is_subintent(&self) -> bool { + self.is_subintent + } + + fn get_blobs<'b>(&'b self) -> impl Iterator)> { + self.blobs.iter() + } + + fn get_known_object_names_ref(&self) -> ManifestObjectNamesRef { + self.known_object_names_ref + } + + fn get_child_subintent_hashes<'b>( + &'b self, + ) -> impl ExactSizeIterator { + let iterator: Box> = + match self.child_subintent_specifiers { + Some(specifiers) => Box::new(specifiers.iter()), + None => Box::new(NO_CHILD_SUBINTENTS.iter()), + }; + iterator + } +} + +impl<'a, I: ManifestInstructionSet> TypedReadableManifest for EphemeralManifest<'a, I> { + type Instruction = I; + + fn get_typed_instructions(&self) -> &[I] { + self.instructions + } +} diff --git a/radix-transactions/src/manifest/mod.rs b/radix-transactions/src/manifest/mod.rs index 932b1d981eb..aac60260e4c 100644 --- a/radix-transactions/src/manifest/mod.rs +++ b/radix-transactions/src/manifest/mod.rs @@ -1,3 +1,4 @@ +mod any_instruction; mod any_manifest; pub mod ast; pub mod blob_provider; @@ -19,6 +20,7 @@ mod static_manifest_interpreter; pub mod static_resource_movements; pub mod token; +pub use any_instruction::*; pub use any_manifest::*; pub use blob_provider::*; pub use compiler::*; diff --git a/radix-transactions/src/manifest/parser.rs b/radix-transactions/src/manifest/parser.rs index 1918c701e53..e8beb51880c 100644 --- a/radix-transactions/src/manifest/parser.rs +++ b/radix-transactions/src/manifest/parser.rs @@ -1013,7 +1013,18 @@ impl Parser { open: Token, close: Token, ) -> Result, ParserError> { - self.advance_exact(open)?; + self.parse_values_any_with_open_close_spans(open, close) + .map(|(values, _, _)| values) + } + + /// Parse a comma-separated value list, enclosed by a pair of marks. + /// Return values and opening and closing span + fn parse_values_any_with_open_close_spans( + &mut self, + open: Token, + close: Token, + ) -> Result<(Vec, Span, Span), ParserError> { + let open_token = self.advance_exact(open)?; let mut values = Vec::new(); while self.peek()?.token != close { values.push(self.parse_value()?); @@ -1021,25 +1032,27 @@ impl Parser { self.advance_exact(Token::Comma)?; } } - self.advance_exact(close)?; - Ok(values) + let close_token = self.advance_exact(close)?; + Ok((values, open_token.span, close_token.span)) } fn parse_values_one(&mut self) -> Result { - let values = self.parse_values_any(Token::OpenParenthesis, Token::CloseParenthesis)?; - if values.len() != 1 { - Err(ParserError { + let (values, open_span, close_span) = self.parse_values_any_with_open_close_spans( + Token::OpenParenthesis, + Token::CloseParenthesis, + )?; + match values.len() { + 1 => Ok(values[0].clone()), + _ => Err(ParserError { error_kind: ParserErrorKind::InvalidNumberOfValues { actual: values.len(), expected: 1, }, span: Span { - start: values[0].span.start, - end: values[values.len() - 1].span.end, + start: open_span.end, + end: close_span.start, }, - }) - } else { - Ok(values[0].clone()) + }), } } @@ -1466,6 +1479,26 @@ mod tests { span: span!(start = (8, 0, 8), end = (20, 0, 20)), } ); + parse_value_error!( + r#"Address()"#, + ParserError { + error_kind: ParserErrorKind::InvalidNumberOfValues { + actual: 0, + expected: 1, + }, + span: span!(start = (8, 0, 8), end = (8, 0, 8)), + } + ); + parse_value_error!( + r#"Address( )"#, + ParserError { + error_kind: ParserErrorKind::InvalidNumberOfValues { + actual: 0, + expected: 1, + }, + span: span!(start = (8, 0, 8), end = (11, 0, 11)), + } + ); } #[test] diff --git a/radix-transactions/src/model/mod.rs b/radix-transactions/src/model/mod.rs index f7d3cde4f7b..fa9533d8cea 100644 --- a/radix-transactions/src/model/mod.rs +++ b/radix-transactions/src/model/mod.rs @@ -238,7 +238,7 @@ Enum<3u8>( assert_eq!( validated, Err(TransactionValidationError::PrepareError( - PrepareError::Other(format!("Unknown transaction payload discriminator byte: 4")) + PrepareError::UnexpectedTransactionDiscriminator { actual: Some(4) } )) ) } diff --git a/radix-transactions/src/model/preparation/decoder.rs b/radix-transactions/src/model/preparation/decoder.rs index 58f587df724..97d04bab6b4 100644 --- a/radix-transactions/src/model/preparation/decoder.rs +++ b/radix-transactions/src/model/preparation/decoder.rs @@ -4,10 +4,10 @@ use sbor::*; #[derive(Debug, Clone, Eq, PartialEq)] pub enum ValueType { Blob, - Message, Subintent, - ChildIntentConstraint, - IntentSignatures, + ChildSubintentSpecifier, + SubintentSignatureBatches, + // Too many signatures is captured at validation time } #[derive(Debug, Clone, Eq, PartialEq)] @@ -22,7 +22,9 @@ pub enum PrepareError { max: usize, }, LengthOverflow, - Other(String), + UnexpectedTransactionDiscriminator { + actual: Option, + }, } impl From for PrepareError { diff --git a/radix-transactions/src/model/test_transaction.rs b/radix-transactions/src/model/test_transaction.rs index 6684dc6e30c..2938ce892ff 100644 --- a/radix-transactions/src/model/test_transaction.rs +++ b/radix-transactions/src/model/test_transaction.rs @@ -22,6 +22,39 @@ impl TestTransactionV2Builder { } } + /// Yields to each child exactly once with empty arguments. + pub fn add_simple_subintent( + &mut self, + children: impl IntoIterator, + proofs: impl IntoIterator, + ) -> SubintentHash { + let mut manifest_builder = ManifestBuilder::new_subintent_v2(); + for (child_index, child_hash) in children.into_iter().enumerate() { + let child_name = format!("child_{child_index}"); + manifest_builder = manifest_builder.use_child(&child_name, child_hash); + manifest_builder = manifest_builder.yield_to_child(child_name, ()); + } + let manifest = manifest_builder.yield_to_parent(()).build(); + self.add_subintent(manifest, proofs) + } + + pub fn add_tweaked_simple_subintent( + &mut self, + children: impl IntoIterator, + proofs: impl IntoIterator, + addition: impl FnOnce(SubintentManifestV2Builder) -> SubintentManifestV2Builder, + ) -> SubintentHash { + let mut manifest_builder = ManifestBuilder::new_subintent_v2(); + for (child_index, child_hash) in children.into_iter().enumerate() { + let child_name = format!("child_{child_index}"); + manifest_builder = manifest_builder.use_child(&child_name, child_hash); + manifest_builder = manifest_builder.yield_to_child(child_name, ()); + } + manifest_builder = addition(manifest_builder); + let manifest = manifest_builder.yield_to_parent(()).build(); + self.add_subintent(manifest, proofs) + } + pub fn add_subintent( &mut self, manifest: SubintentManifestV2, @@ -34,6 +67,24 @@ impl TestTransactionV2Builder { SubintentHash(hash) } + /// Uses the faucet and yields to each child exactly once with empty arguments. + pub fn finish_with_simple_root_intent( + self, + children: impl IntoIterator, + proofs: impl IntoIterator, + ) -> TestTransaction { + let mut manifest_builder = ManifestBuilder::new_v2(); + manifest_builder = manifest_builder.lock_fee_from_faucet(); + for (child_index, child_hash) in children.into_iter().enumerate() { + let child_name = format!("child_{child_index}"); + // In the manifest builder, we allow USE_CHILD later than in a written manifest + manifest_builder = manifest_builder.use_child(&child_name, child_hash); + manifest_builder = manifest_builder.yield_to_child(child_name, ()); + } + let manifest = manifest_builder.build(); + self.finish_with_root_intent(manifest, proofs) + } + pub fn finish_with_root_intent( self, manifest: TransactionManifestV2, diff --git a/radix-transactions/src/model/user_transaction.rs b/radix-transactions/src/model/user_transaction.rs index 8e94ddb29a9..76195a1bf08 100644 --- a/radix-transactions/src/model/user_transaction.rs +++ b/radix-transactions/src/model/user_transaction.rs @@ -248,9 +248,9 @@ impl PreparedTransaction for PreparedUserTransaction { ) -> Result { let offset = decoder.get_offset(); let slice = decoder.get_input_slice(); - let discriminator_byte = slice.get(offset + 1).ok_or(PrepareError::Other( - "Could not read transaction payload discriminator byte".to_string(), - ))?; + let discriminator_byte = slice + .get(offset + 1) + .ok_or(PrepareError::UnexpectedTransactionDiscriminator { actual: None })?; let prepared = match TransactionDiscriminator::from_repr(*discriminator_byte) { Some(TransactionDiscriminator::V1Notarized) => PreparedUserTransaction::V1( @@ -260,9 +260,9 @@ impl PreparedTransaction for PreparedUserTransaction { PreparedNotarizedTransactionV2::prepare_from_transaction_enum(decoder)?, ), _ => { - return Err(PrepareError::Other(format!( - "Unknown transaction payload discriminator byte: {discriminator_byte}" - ))) + return Err(PrepareError::UnexpectedTransactionDiscriminator { + actual: Some(*discriminator_byte), + }) } }; diff --git a/radix-transactions/src/model/v1/blobs.rs b/radix-transactions/src/model/v1/blobs.rs index 54cb3059559..6954fa6f344 100644 --- a/radix-transactions/src/model/v1/blobs.rs +++ b/radix-transactions/src/model/v1/blobs.rs @@ -11,6 +11,12 @@ pub struct BlobsV1 { pub blobs: Vec, } +impl BlobsV1 { + pub fn none() -> Self { + Self { blobs: Vec::new() } + } +} + impl From>> for BlobsV1 { fn from(blobs: IndexMap>) -> Self { let blobs = blobs diff --git a/radix-transactions/src/model/v1/instruction_v1.rs b/radix-transactions/src/model/v1/instruction_v1.rs index ad74ee91433..e363d0baa14 100644 --- a/radix-transactions/src/model/v1/instruction_v1.rs +++ b/radix-transactions/src/model/v1/instruction_v1.rs @@ -1,86 +1,137 @@ use crate::internal_prelude::*; impl> From for InstructionV1 { - fn from(value: T) -> Self { - value.into_enum() + fn from(instruction: T) -> Self { + instruction.into_enum() + } +} + +impl From for AnyInstruction { + fn from(any_v1_instruction: InstructionV1) -> Self { + any_v1_instruction.into_any() } } impl ManifestInstructionSet for InstructionV1 { - fn decompile( - &self, - context: &mut decompiler::DecompilationContext, - ) -> Result { + fn map_ref(&self, mapper: M) -> M::Output<'_> { match self { - InstructionV1::TakeFromWorktop(x) => x.decompile(context), - InstructionV1::TakeNonFungiblesFromWorktop(x) => x.decompile(context), - InstructionV1::TakeAllFromWorktop(x) => x.decompile(context), - InstructionV1::ReturnToWorktop(x) => x.decompile(context), - InstructionV1::BurnResource(x) => x.decompile(context), - InstructionV1::AssertWorktopContainsAny(x) => x.decompile(context), - InstructionV1::AssertWorktopContains(x) => x.decompile(context), - InstructionV1::AssertWorktopContainsNonFungibles(x) => x.decompile(context), - InstructionV1::CreateProofFromBucketOfAmount(x) => x.decompile(context), - InstructionV1::CreateProofFromBucketOfNonFungibles(x) => x.decompile(context), - InstructionV1::CreateProofFromBucketOfAll(x) => x.decompile(context), - InstructionV1::CreateProofFromAuthZoneOfAmount(x) => x.decompile(context), - InstructionV1::CreateProofFromAuthZoneOfNonFungibles(x) => x.decompile(context), - InstructionV1::CreateProofFromAuthZoneOfAll(x) => x.decompile(context), - InstructionV1::CloneProof(x) => x.decompile(context), - InstructionV1::DropProof(x) => x.decompile(context), - InstructionV1::PushToAuthZone(x) => x.decompile(context), - InstructionV1::PopFromAuthZone(x) => x.decompile(context), - InstructionV1::DropAuthZoneProofs(x) => x.decompile(context), - InstructionV1::DropAuthZoneRegularProofs(x) => x.decompile(context), - InstructionV1::DropAuthZoneSignatureProofs(x) => x.decompile(context), - InstructionV1::DropNamedProofs(x) => x.decompile(context), - InstructionV1::DropAllProofs(x) => x.decompile(context), - InstructionV1::CallFunction(x) => x.decompile(context), - InstructionV1::CallMethod(x) => x.decompile(context), - InstructionV1::CallRoyaltyMethod(x) => x.decompile(context), - InstructionV1::CallMetadataMethod(x) => x.decompile(context), - InstructionV1::CallRoleAssignmentMethod(x) => x.decompile(context), - InstructionV1::CallDirectVaultMethod(x) => x.decompile(context), - InstructionV1::AllocateGlobalAddress(x) => x.decompile(context), + InstructionV1::TakeFromWorktop(x) => mapper.apply(x), + InstructionV1::TakeNonFungiblesFromWorktop(x) => mapper.apply(x), + InstructionV1::TakeAllFromWorktop(x) => mapper.apply(x), + InstructionV1::ReturnToWorktop(x) => mapper.apply(x), + InstructionV1::BurnResource(x) => mapper.apply(x), + InstructionV1::AssertWorktopContainsAny(x) => mapper.apply(x), + InstructionV1::AssertWorktopContains(x) => mapper.apply(x), + InstructionV1::AssertWorktopContainsNonFungibles(x) => mapper.apply(x), + InstructionV1::CreateProofFromBucketOfAmount(x) => mapper.apply(x), + InstructionV1::CreateProofFromBucketOfNonFungibles(x) => mapper.apply(x), + InstructionV1::CreateProofFromBucketOfAll(x) => mapper.apply(x), + InstructionV1::CreateProofFromAuthZoneOfAmount(x) => mapper.apply(x), + InstructionV1::CreateProofFromAuthZoneOfNonFungibles(x) => mapper.apply(x), + InstructionV1::CreateProofFromAuthZoneOfAll(x) => mapper.apply(x), + InstructionV1::CloneProof(x) => mapper.apply(x), + InstructionV1::DropProof(x) => mapper.apply(x), + InstructionV1::PushToAuthZone(x) => mapper.apply(x), + InstructionV1::PopFromAuthZone(x) => mapper.apply(x), + InstructionV1::DropAuthZoneProofs(x) => mapper.apply(x), + InstructionV1::DropAuthZoneRegularProofs(x) => mapper.apply(x), + InstructionV1::DropAuthZoneSignatureProofs(x) => mapper.apply(x), + InstructionV1::DropNamedProofs(x) => mapper.apply(x), + InstructionV1::DropAllProofs(x) => mapper.apply(x), + InstructionV1::CallFunction(x) => mapper.apply(x), + InstructionV1::CallMethod(x) => mapper.apply(x), + InstructionV1::CallRoyaltyMethod(x) => mapper.apply(x), + InstructionV1::CallMetadataMethod(x) => mapper.apply(x), + InstructionV1::CallRoleAssignmentMethod(x) => mapper.apply(x), + InstructionV1::CallDirectVaultMethod(x) => mapper.apply(x), + InstructionV1::AllocateGlobalAddress(x) => mapper.apply(x), } } - fn effect(&self) -> ManifestInstructionEffect { + fn map_self(self, mapper: M) -> M::Output { match self { - InstructionV1::TakeFromWorktop(x) => x.effect(), - InstructionV1::TakeNonFungiblesFromWorktop(x) => x.effect(), - InstructionV1::TakeAllFromWorktop(x) => x.effect(), - InstructionV1::ReturnToWorktop(x) => x.effect(), - InstructionV1::BurnResource(x) => x.effect(), - InstructionV1::AssertWorktopContainsAny(x) => x.effect(), - InstructionV1::AssertWorktopContains(x) => x.effect(), - InstructionV1::AssertWorktopContainsNonFungibles(x) => x.effect(), - InstructionV1::CreateProofFromBucketOfAmount(x) => x.effect(), - InstructionV1::CreateProofFromBucketOfNonFungibles(x) => x.effect(), - InstructionV1::CreateProofFromBucketOfAll(x) => x.effect(), - InstructionV1::CreateProofFromAuthZoneOfAmount(x) => x.effect(), - InstructionV1::CreateProofFromAuthZoneOfNonFungibles(x) => x.effect(), - InstructionV1::CreateProofFromAuthZoneOfAll(x) => x.effect(), - InstructionV1::CloneProof(x) => x.effect(), - InstructionV1::DropProof(x) => x.effect(), - InstructionV1::PushToAuthZone(x) => x.effect(), - InstructionV1::PopFromAuthZone(x) => x.effect(), - InstructionV1::DropAuthZoneProofs(x) => x.effect(), - InstructionV1::DropAuthZoneRegularProofs(x) => x.effect(), - InstructionV1::DropAuthZoneSignatureProofs(x) => x.effect(), - InstructionV1::DropNamedProofs(x) => x.effect(), - InstructionV1::DropAllProofs(x) => x.effect(), - InstructionV1::CallFunction(x) => x.effect(), - InstructionV1::CallMethod(x) => x.effect(), - InstructionV1::CallRoyaltyMethod(x) => x.effect(), - InstructionV1::CallMetadataMethod(x) => x.effect(), - InstructionV1::CallRoleAssignmentMethod(x) => x.effect(), - InstructionV1::CallDirectVaultMethod(x) => x.effect(), - InstructionV1::AllocateGlobalAddress(x) => x.effect(), + InstructionV1::TakeFromWorktop(x) => mapper.apply(x), + InstructionV1::TakeNonFungiblesFromWorktop(x) => mapper.apply(x), + InstructionV1::TakeAllFromWorktop(x) => mapper.apply(x), + InstructionV1::ReturnToWorktop(x) => mapper.apply(x), + InstructionV1::BurnResource(x) => mapper.apply(x), + InstructionV1::AssertWorktopContainsAny(x) => mapper.apply(x), + InstructionV1::AssertWorktopContains(x) => mapper.apply(x), + InstructionV1::AssertWorktopContainsNonFungibles(x) => mapper.apply(x), + InstructionV1::CreateProofFromBucketOfAmount(x) => mapper.apply(x), + InstructionV1::CreateProofFromBucketOfNonFungibles(x) => mapper.apply(x), + InstructionV1::CreateProofFromBucketOfAll(x) => mapper.apply(x), + InstructionV1::CreateProofFromAuthZoneOfAmount(x) => mapper.apply(x), + InstructionV1::CreateProofFromAuthZoneOfNonFungibles(x) => mapper.apply(x), + InstructionV1::CreateProofFromAuthZoneOfAll(x) => mapper.apply(x), + InstructionV1::CloneProof(x) => mapper.apply(x), + InstructionV1::DropProof(x) => mapper.apply(x), + InstructionV1::PushToAuthZone(x) => mapper.apply(x), + InstructionV1::PopFromAuthZone(x) => mapper.apply(x), + InstructionV1::DropAuthZoneProofs(x) => mapper.apply(x), + InstructionV1::DropAuthZoneRegularProofs(x) => mapper.apply(x), + InstructionV1::DropAuthZoneSignatureProofs(x) => mapper.apply(x), + InstructionV1::DropNamedProofs(x) => mapper.apply(x), + InstructionV1::DropAllProofs(x) => mapper.apply(x), + InstructionV1::CallFunction(x) => mapper.apply(x), + InstructionV1::CallMethod(x) => mapper.apply(x), + InstructionV1::CallRoyaltyMethod(x) => mapper.apply(x), + InstructionV1::CallMetadataMethod(x) => mapper.apply(x), + InstructionV1::CallRoleAssignmentMethod(x) => mapper.apply(x), + InstructionV1::CallDirectVaultMethod(x) => mapper.apply(x), + InstructionV1::AllocateGlobalAddress(x) => mapper.apply(x), } } } +impl TryFrom for InstructionV1 { + type Error = (); + + fn try_from(value: AnyInstruction) -> Result { + let mapped = match value { + AnyInstruction::TakeFromWorktop(x) => x.into(), + AnyInstruction::TakeNonFungiblesFromWorktop(x) => x.into(), + AnyInstruction::TakeAllFromWorktop(x) => x.into(), + AnyInstruction::ReturnToWorktop(x) => x.into(), + AnyInstruction::BurnResource(x) => x.into(), + AnyInstruction::AssertWorktopContainsAny(x) => x.into(), + AnyInstruction::AssertWorktopContains(x) => x.into(), + AnyInstruction::AssertWorktopContainsNonFungibles(x) => x.into(), + AnyInstruction::AssertWorktopResourcesOnly(_) => return Err(()), + AnyInstruction::AssertWorktopResourcesInclude(_) => return Err(()), + AnyInstruction::AssertNextCallReturnsOnly(_) => return Err(()), + AnyInstruction::AssertNextCallReturnsInclude(_) => return Err(()), + AnyInstruction::AssertBucketContents(_) => return Err(()), + AnyInstruction::CreateProofFromBucketOfAmount(x) => x.into(), + AnyInstruction::CreateProofFromBucketOfNonFungibles(x) => x.into(), + AnyInstruction::CreateProofFromBucketOfAll(x) => x.into(), + AnyInstruction::CreateProofFromAuthZoneOfAmount(x) => x.into(), + AnyInstruction::CreateProofFromAuthZoneOfNonFungibles(x) => x.into(), + AnyInstruction::CreateProofFromAuthZoneOfAll(x) => x.into(), + AnyInstruction::CloneProof(x) => x.into(), + AnyInstruction::DropProof(x) => x.into(), + AnyInstruction::PushToAuthZone(x) => x.into(), + AnyInstruction::PopFromAuthZone(x) => x.into(), + AnyInstruction::DropAuthZoneProofs(x) => x.into(), + AnyInstruction::DropAuthZoneRegularProofs(x) => x.into(), + AnyInstruction::DropAuthZoneSignatureProofs(x) => x.into(), + AnyInstruction::DropNamedProofs(x) => x.into(), + AnyInstruction::DropAllProofs(x) => x.into(), + AnyInstruction::CallFunction(x) => x.into(), + AnyInstruction::CallMethod(x) => x.into(), + AnyInstruction::CallRoyaltyMethod(x) => x.into(), + AnyInstruction::CallMetadataMethod(x) => x.into(), + AnyInstruction::CallRoleAssignmentMethod(x) => x.into(), + AnyInstruction::CallDirectVaultMethod(x) => x.into(), + AnyInstruction::AllocateGlobalAddress(x) => x.into(), + AnyInstruction::YieldToParent(_) => return Err(()), + AnyInstruction::YieldToChild(_) => return Err(()), + AnyInstruction::VerifyParent(_) => return Err(()), + }; + Ok(mapped) + } +} + #[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoDescribe, ScryptoSborAssertion)] #[sbor(impl_variant_traits)] #[sbor_assert(fixed("FILE:instruction_v1_schema.txt"))] diff --git a/radix-transactions/src/model/v2/child_subintent_hashes_v2.rs b/radix-transactions/src/model/v2/child_subintent_hashes_v2.rs index 9cdd5fbbfc0..50b96c407f8 100644 --- a/radix-transactions/src/model/v2/child_subintent_hashes_v2.rs +++ b/radix-transactions/src/model/v2/child_subintent_hashes_v2.rs @@ -9,7 +9,7 @@ pub struct ChildSubintentSpecifiersV2 { } impl TransactionPartialPrepare for ChildSubintentSpecifiersV2 { - type Prepared = PreparedChildSubintentSpecifiersV2V2; + type Prepared = PreparedChildSubintentSpecifiersV2; } /// A new-type of a [`SubintentHash`], representing that the subintent is claimed @@ -35,6 +35,12 @@ impl ChildSubintentSpecifier { } } +impl From for ChildSubintentSpecifier { + fn from(hash: SubintentHash) -> Self { + Self { hash } + } +} + /// A new-type representing the index of a referenced intent. /// The first few of these will be the children of the given intent. /// @@ -73,20 +79,20 @@ impl From for ManifestNamedIntentIndex { //======== #[derive(Debug, Clone, Eq, PartialEq)] -pub struct PreparedChildSubintentSpecifiersV2V2 { +pub struct PreparedChildSubintentSpecifiersV2 { pub children: IndexSet, pub summary: Summary, } -impl_has_summary!(PreparedChildSubintentSpecifiersV2V2); +impl_has_summary!(PreparedChildSubintentSpecifiersV2); -impl TransactionPreparableFromValueBody for PreparedChildSubintentSpecifiersV2V2 { +impl TransactionPreparableFromValueBody for PreparedChildSubintentSpecifiersV2 { fn prepare_from_value_body(decoder: &mut TransactionDecoder) -> Result { let max_child_subintents_per_intent = decoder.settings().max_child_subintents_per_intent; let (hashes, summary) = ConcatenatedDigest::prepare_from_sbor_array_value_body::>( decoder, - ValueType::ChildIntentConstraint, + ValueType::ChildSubintentSpecifier, max_child_subintents_per_intent, )?; diff --git a/radix-transactions/src/model/v2/instruction_v2.rs b/radix-transactions/src/model/v2/instruction_v2.rs index dd606c42584..d0cb545e569 100644 --- a/radix-transactions/src/model/v2/instruction_v2.rs +++ b/radix-transactions/src/model/v2/instruction_v2.rs @@ -1,5 +1,4 @@ use crate::internal_prelude::*; -use decompiler::*; impl> From for InstructionV2 { fn from(value: T) -> Self { @@ -8,181 +7,93 @@ impl> From for } impl ManifestInstructionSet for InstructionV2 { - fn decompile( - &self, - context: &mut DecompilationContext, - ) -> Result { + fn map_ref(&self, mapper: M) -> M::Output<'_> { match self { - InstructionV2::TakeFromWorktop(x) => x.decompile(context), - InstructionV2::TakeNonFungiblesFromWorktop(x) => x.decompile(context), - InstructionV2::TakeAllFromWorktop(x) => x.decompile(context), - InstructionV2::ReturnToWorktop(x) => x.decompile(context), - InstructionV2::BurnResource(x) => x.decompile(context), - InstructionV2::AssertWorktopContainsAny(x) => x.decompile(context), - InstructionV2::AssertWorktopContains(x) => x.decompile(context), - InstructionV2::AssertWorktopContainsNonFungibles(x) => x.decompile(context), - InstructionV2::AssertWorktopResourcesOnly(x) => x.decompile(context), - InstructionV2::AssertWorktopResourcesInclude(x) => x.decompile(context), - InstructionV2::AssertNextCallReturnsOnly(x) => x.decompile(context), - InstructionV2::AssertNextCallReturnsInclude(x) => x.decompile(context), - InstructionV2::AssertBucketContents(x) => x.decompile(context), - InstructionV2::CreateProofFromBucketOfAmount(x) => x.decompile(context), - InstructionV2::CreateProofFromBucketOfNonFungibles(x) => x.decompile(context), - InstructionV2::CreateProofFromBucketOfAll(x) => x.decompile(context), - InstructionV2::CreateProofFromAuthZoneOfAmount(x) => x.decompile(context), - InstructionV2::CreateProofFromAuthZoneOfNonFungibles(x) => x.decompile(context), - InstructionV2::CreateProofFromAuthZoneOfAll(x) => x.decompile(context), - InstructionV2::CloneProof(x) => x.decompile(context), - InstructionV2::DropProof(x) => x.decompile(context), - InstructionV2::PushToAuthZone(x) => x.decompile(context), - InstructionV2::PopFromAuthZone(x) => x.decompile(context), - InstructionV2::DropAuthZoneProofs(x) => x.decompile(context), - InstructionV2::DropAuthZoneRegularProofs(x) => x.decompile(context), - InstructionV2::DropAuthZoneSignatureProofs(x) => x.decompile(context), - InstructionV2::DropNamedProofs(x) => x.decompile(context), - InstructionV2::DropAllProofs(x) => x.decompile(context), - InstructionV2::CallFunction(x) => x.decompile(context), - InstructionV2::CallMethod(x) => x.decompile(context), - InstructionV2::CallRoyaltyMethod(x) => x.decompile(context), - InstructionV2::CallMetadataMethod(x) => x.decompile(context), - InstructionV2::CallRoleAssignmentMethod(x) => x.decompile(context), - InstructionV2::CallDirectVaultMethod(x) => x.decompile(context), - InstructionV2::AllocateGlobalAddress(x) => x.decompile(context), - InstructionV2::YieldToParent(x) => x.decompile(context), - InstructionV2::YieldToChild(x) => x.decompile(context), - InstructionV2::VerifyParent(x) => x.decompile(context), + InstructionV2::TakeFromWorktop(x) => mapper.apply(x), + InstructionV2::TakeNonFungiblesFromWorktop(x) => mapper.apply(x), + InstructionV2::TakeAllFromWorktop(x) => mapper.apply(x), + InstructionV2::ReturnToWorktop(x) => mapper.apply(x), + InstructionV2::BurnResource(x) => mapper.apply(x), + InstructionV2::AssertWorktopContainsAny(x) => mapper.apply(x), + InstructionV2::AssertWorktopContains(x) => mapper.apply(x), + InstructionV2::AssertWorktopContainsNonFungibles(x) => mapper.apply(x), + InstructionV2::AssertWorktopResourcesOnly(x) => mapper.apply(x), + InstructionV2::AssertWorktopResourcesInclude(x) => mapper.apply(x), + InstructionV2::AssertNextCallReturnsOnly(x) => mapper.apply(x), + InstructionV2::AssertNextCallReturnsInclude(x) => mapper.apply(x), + InstructionV2::AssertBucketContents(x) => mapper.apply(x), + InstructionV2::CreateProofFromBucketOfAmount(x) => mapper.apply(x), + InstructionV2::CreateProofFromBucketOfNonFungibles(x) => mapper.apply(x), + InstructionV2::CreateProofFromBucketOfAll(x) => mapper.apply(x), + InstructionV2::CreateProofFromAuthZoneOfAmount(x) => mapper.apply(x), + InstructionV2::CreateProofFromAuthZoneOfNonFungibles(x) => mapper.apply(x), + InstructionV2::CreateProofFromAuthZoneOfAll(x) => mapper.apply(x), + InstructionV2::CloneProof(x) => mapper.apply(x), + InstructionV2::DropProof(x) => mapper.apply(x), + InstructionV2::PushToAuthZone(x) => mapper.apply(x), + InstructionV2::PopFromAuthZone(x) => mapper.apply(x), + InstructionV2::DropAuthZoneProofs(x) => mapper.apply(x), + InstructionV2::DropAuthZoneRegularProofs(x) => mapper.apply(x), + InstructionV2::DropAuthZoneSignatureProofs(x) => mapper.apply(x), + InstructionV2::DropNamedProofs(x) => mapper.apply(x), + InstructionV2::DropAllProofs(x) => mapper.apply(x), + InstructionV2::CallFunction(x) => mapper.apply(x), + InstructionV2::CallMethod(x) => mapper.apply(x), + InstructionV2::CallRoyaltyMethod(x) => mapper.apply(x), + InstructionV2::CallMetadataMethod(x) => mapper.apply(x), + InstructionV2::CallRoleAssignmentMethod(x) => mapper.apply(x), + InstructionV2::CallDirectVaultMethod(x) => mapper.apply(x), + InstructionV2::AllocateGlobalAddress(x) => mapper.apply(x), + InstructionV2::YieldToParent(x) => mapper.apply(x), + InstructionV2::YieldToChild(x) => mapper.apply(x), + InstructionV2::VerifyParent(x) => mapper.apply(x), } } - fn effect(&self) -> ManifestInstructionEffect { + fn map_self(self, mapper: M) -> M::Output { match self { - InstructionV2::TakeFromWorktop(x) => x.effect(), - InstructionV2::TakeNonFungiblesFromWorktop(x) => x.effect(), - InstructionV2::TakeAllFromWorktop(x) => x.effect(), - InstructionV2::ReturnToWorktop(x) => x.effect(), - InstructionV2::BurnResource(x) => x.effect(), - InstructionV2::AssertWorktopContainsAny(x) => x.effect(), - InstructionV2::AssertWorktopContains(x) => x.effect(), - InstructionV2::AssertWorktopContainsNonFungibles(x) => x.effect(), - InstructionV2::AssertWorktopResourcesOnly(x) => x.effect(), - InstructionV2::AssertWorktopResourcesInclude(x) => x.effect(), - InstructionV2::AssertNextCallReturnsOnly(x) => x.effect(), - InstructionV2::AssertNextCallReturnsInclude(x) => x.effect(), - InstructionV2::AssertBucketContents(x) => x.effect(), - InstructionV2::CreateProofFromBucketOfAmount(x) => x.effect(), - InstructionV2::CreateProofFromBucketOfNonFungibles(x) => x.effect(), - InstructionV2::CreateProofFromBucketOfAll(x) => x.effect(), - InstructionV2::CreateProofFromAuthZoneOfAmount(x) => x.effect(), - InstructionV2::CreateProofFromAuthZoneOfNonFungibles(x) => x.effect(), - InstructionV2::CreateProofFromAuthZoneOfAll(x) => x.effect(), - InstructionV2::CloneProof(x) => x.effect(), - InstructionV2::DropProof(x) => x.effect(), - InstructionV2::PushToAuthZone(x) => x.effect(), - InstructionV2::PopFromAuthZone(x) => x.effect(), - InstructionV2::DropAuthZoneProofs(x) => x.effect(), - InstructionV2::DropAuthZoneRegularProofs(x) => x.effect(), - InstructionV2::DropAuthZoneSignatureProofs(x) => x.effect(), - InstructionV2::DropNamedProofs(x) => x.effect(), - InstructionV2::DropAllProofs(x) => x.effect(), - InstructionV2::CallFunction(x) => x.effect(), - InstructionV2::CallMethod(x) => x.effect(), - InstructionV2::CallRoyaltyMethod(x) => x.effect(), - InstructionV2::CallMetadataMethod(x) => x.effect(), - InstructionV2::CallRoleAssignmentMethod(x) => x.effect(), - InstructionV2::CallDirectVaultMethod(x) => x.effect(), - InstructionV2::AllocateGlobalAddress(x) => x.effect(), - InstructionV2::YieldToParent(x) => x.effect(), - InstructionV2::YieldToChild(x) => x.effect(), - InstructionV2::VerifyParent(x) => x.effect(), + InstructionV2::TakeFromWorktop(x) => mapper.apply(x), + InstructionV2::TakeNonFungiblesFromWorktop(x) => mapper.apply(x), + InstructionV2::TakeAllFromWorktop(x) => mapper.apply(x), + InstructionV2::ReturnToWorktop(x) => mapper.apply(x), + InstructionV2::BurnResource(x) => mapper.apply(x), + InstructionV2::AssertWorktopContainsAny(x) => mapper.apply(x), + InstructionV2::AssertWorktopContains(x) => mapper.apply(x), + InstructionV2::AssertWorktopContainsNonFungibles(x) => mapper.apply(x), + InstructionV2::AssertWorktopResourcesOnly(x) => mapper.apply(x), + InstructionV2::AssertWorktopResourcesInclude(x) => mapper.apply(x), + InstructionV2::AssertNextCallReturnsOnly(x) => mapper.apply(x), + InstructionV2::AssertNextCallReturnsInclude(x) => mapper.apply(x), + InstructionV2::AssertBucketContents(x) => mapper.apply(x), + InstructionV2::CreateProofFromBucketOfAmount(x) => mapper.apply(x), + InstructionV2::CreateProofFromBucketOfNonFungibles(x) => mapper.apply(x), + InstructionV2::CreateProofFromBucketOfAll(x) => mapper.apply(x), + InstructionV2::CreateProofFromAuthZoneOfAmount(x) => mapper.apply(x), + InstructionV2::CreateProofFromAuthZoneOfNonFungibles(x) => mapper.apply(x), + InstructionV2::CreateProofFromAuthZoneOfAll(x) => mapper.apply(x), + InstructionV2::CloneProof(x) => mapper.apply(x), + InstructionV2::DropProof(x) => mapper.apply(x), + InstructionV2::PushToAuthZone(x) => mapper.apply(x), + InstructionV2::PopFromAuthZone(x) => mapper.apply(x), + InstructionV2::DropAuthZoneProofs(x) => mapper.apply(x), + InstructionV2::DropAuthZoneRegularProofs(x) => mapper.apply(x), + InstructionV2::DropAuthZoneSignatureProofs(x) => mapper.apply(x), + InstructionV2::DropNamedProofs(x) => mapper.apply(x), + InstructionV2::DropAllProofs(x) => mapper.apply(x), + InstructionV2::CallFunction(x) => mapper.apply(x), + InstructionV2::CallMethod(x) => mapper.apply(x), + InstructionV2::CallRoyaltyMethod(x) => mapper.apply(x), + InstructionV2::CallMetadataMethod(x) => mapper.apply(x), + InstructionV2::CallRoleAssignmentMethod(x) => mapper.apply(x), + InstructionV2::CallDirectVaultMethod(x) => mapper.apply(x), + InstructionV2::AllocateGlobalAddress(x) => mapper.apply(x), + InstructionV2::YieldToParent(x) => mapper.apply(x), + InstructionV2::YieldToChild(x) => mapper.apply(x), + InstructionV2::VerifyParent(x) => mapper.apply(x), } } } -impl From for InstructionV2 { - fn from(value: InstructionV1) -> Self { - match value { - InstructionV1::TakeFromWorktop(x) => x.into(), - InstructionV1::TakeNonFungiblesFromWorktop(x) => x.into(), - InstructionV1::TakeAllFromWorktop(x) => x.into(), - InstructionV1::ReturnToWorktop(x) => x.into(), - InstructionV1::BurnResource(x) => x.into(), - InstructionV1::AssertWorktopContainsAny(x) => x.into(), - InstructionV1::AssertWorktopContains(x) => x.into(), - InstructionV1::AssertWorktopContainsNonFungibles(x) => x.into(), - InstructionV1::CreateProofFromBucketOfAmount(x) => x.into(), - InstructionV1::CreateProofFromBucketOfNonFungibles(x) => x.into(), - InstructionV1::CreateProofFromBucketOfAll(x) => x.into(), - InstructionV1::CreateProofFromAuthZoneOfAmount(x) => x.into(), - InstructionV1::CreateProofFromAuthZoneOfNonFungibles(x) => x.into(), - InstructionV1::CreateProofFromAuthZoneOfAll(x) => x.into(), - InstructionV1::CloneProof(x) => x.into(), - InstructionV1::DropProof(x) => x.into(), - InstructionV1::PushToAuthZone(x) => x.into(), - InstructionV1::PopFromAuthZone(x) => x.into(), - InstructionV1::DropAuthZoneProofs(x) => x.into(), - InstructionV1::DropAuthZoneRegularProofs(x) => x.into(), - InstructionV1::DropAuthZoneSignatureProofs(x) => x.into(), - InstructionV1::DropNamedProofs(x) => x.into(), - InstructionV1::DropAllProofs(x) => x.into(), - InstructionV1::CallFunction(x) => x.into(), - InstructionV1::CallMethod(x) => x.into(), - InstructionV1::CallRoyaltyMethod(x) => x.into(), - InstructionV1::CallMetadataMethod(x) => x.into(), - InstructionV1::CallRoleAssignmentMethod(x) => x.into(), - InstructionV1::CallDirectVaultMethod(x) => x.into(), - InstructionV1::AllocateGlobalAddress(x) => x.into(), - } - } -} - -impl TryFrom for InstructionV1 { - type Error = (); - - fn try_from(value: InstructionV2) -> Result { - let mapped = match value { - InstructionV2::TakeFromWorktop(x) => x.into(), - InstructionV2::TakeNonFungiblesFromWorktop(x) => x.into(), - InstructionV2::TakeAllFromWorktop(x) => x.into(), - InstructionV2::ReturnToWorktop(x) => x.into(), - InstructionV2::BurnResource(x) => x.into(), - InstructionV2::AssertWorktopContainsAny(x) => x.into(), - InstructionV2::AssertWorktopContains(x) => x.into(), - InstructionV2::AssertWorktopContainsNonFungibles(x) => x.into(), - InstructionV2::AssertWorktopResourcesOnly(_) => return Err(()), - InstructionV2::AssertWorktopResourcesInclude(_) => return Err(()), - InstructionV2::AssertNextCallReturnsOnly(_) => return Err(()), - InstructionV2::AssertNextCallReturnsInclude(_) => return Err(()), - InstructionV2::AssertBucketContents(_) => return Err(()), - InstructionV2::CreateProofFromBucketOfAmount(x) => x.into(), - InstructionV2::CreateProofFromBucketOfNonFungibles(x) => x.into(), - InstructionV2::CreateProofFromBucketOfAll(x) => x.into(), - InstructionV2::CreateProofFromAuthZoneOfAmount(x) => x.into(), - InstructionV2::CreateProofFromAuthZoneOfNonFungibles(x) => x.into(), - InstructionV2::CreateProofFromAuthZoneOfAll(x) => x.into(), - InstructionV2::CloneProof(x) => x.into(), - InstructionV2::DropProof(x) => x.into(), - InstructionV2::PushToAuthZone(x) => x.into(), - InstructionV2::PopFromAuthZone(x) => x.into(), - InstructionV2::DropAuthZoneProofs(x) => x.into(), - InstructionV2::DropAuthZoneRegularProofs(x) => x.into(), - InstructionV2::DropAuthZoneSignatureProofs(x) => x.into(), - InstructionV2::DropNamedProofs(x) => x.into(), - InstructionV2::DropAllProofs(x) => x.into(), - InstructionV2::CallFunction(x) => x.into(), - InstructionV2::CallMethod(x) => x.into(), - InstructionV2::CallRoyaltyMethod(x) => x.into(), - InstructionV2::CallMetadataMethod(x) => x.into(), - InstructionV2::CallRoleAssignmentMethod(x) => x.into(), - InstructionV2::CallDirectVaultMethod(x) => x.into(), - InstructionV2::AllocateGlobalAddress(x) => x.into(), - InstructionV2::YieldToParent(_) => return Err(()), - InstructionV2::YieldToChild(_) => return Err(()), - InstructionV2::VerifyParent(_) => return Err(()), - }; - Ok(mapped) - } -} - #[derive(Debug, Clone, PartialEq, Eq, ManifestSbor, ScryptoDescribe, ScryptoSborAssertion)] #[sbor(impl_variant_traits)] #[sbor_assert( diff --git a/radix-transactions/src/model/v2/intent_core_v2.rs b/radix-transactions/src/model/v2/intent_core_v2.rs index cfdcd42089b..4548460229b 100644 --- a/radix-transactions/src/model/v2/intent_core_v2.rs +++ b/radix-transactions/src/model/v2/intent_core_v2.rs @@ -27,7 +27,7 @@ pub struct PreparedIntentCoreV2 { pub header: PreparedIntentHeaderV2, pub blobs: PreparedBlobsV1, pub message: PreparedMessageV2, - pub children: PreparedChildSubintentSpecifiersV2V2, + pub children: PreparedChildSubintentSpecifiersV2, pub instructions: PreparedInstructionsV2, pub summary: Summary, } diff --git a/radix-transactions/src/model/v2/intent_signatures_v2.rs b/radix-transactions/src/model/v2/intent_signatures_v2.rs index 41657211b50..bebcafd2ccd 100644 --- a/radix-transactions/src/model/v2/intent_signatures_v2.rs +++ b/radix-transactions/src/model/v2/intent_signatures_v2.rs @@ -7,6 +7,18 @@ pub struct IntentSignaturesV2 { pub signatures: Vec, } +impl IntentSignaturesV2 { + pub fn none() -> Self { + Self { + signatures: Vec::new(), + } + } + + pub fn new(signatures: Vec) -> Self { + Self { signatures } + } +} + impl TransactionPartialPrepare for IntentSignaturesV2 { type Prepared = PreparedIntentSignaturesV2; } @@ -34,7 +46,7 @@ impl TransactionPreparableFromValueBody for PreparedNonRootSubintentSignaturesV2 Vec, >( decoder, - ValueType::IntentSignatures, + ValueType::SubintentSignatureBatches, max_subintents_per_transaction, )?; diff --git a/radix-transactions/src/model/v2/transaction_manifest_v2.rs b/radix-transactions/src/model/v2/transaction_manifest_v2.rs index 0619063e871..43b738e102c 100644 --- a/radix-transactions/src/model/v2/transaction_manifest_v2.rs +++ b/radix-transactions/src/model/v2/transaction_manifest_v2.rs @@ -84,6 +84,15 @@ impl BuildableManifest for TransactionManifestV2 { impl BuildableManifestSupportingChildren for TransactionManifestV2 {} impl TransactionManifestV2 { + pub fn empty() -> Self { + Self { + instructions: Default::default(), + blobs: Default::default(), + children: Default::default(), + object_names: ManifestObjectNames::Unknown, + } + } + pub fn from_intent_core(intent: &IntentCoreV2) -> Self { Self { instructions: intent.instructions.to_vec(), @@ -93,6 +102,18 @@ impl TransactionManifestV2 { } } + pub fn to_intent_core(self, header: IntentHeaderV2, message: MessageV2) -> IntentCoreV2 { + IntentCoreV2 { + header, + blobs: self.blobs.into(), + message, + instructions: self.instructions.into(), + children: ChildSubintentSpecifiersV2 { + children: self.children, + }, + } + } + pub fn for_intent(self) -> (InstructionsV2, BlobsV1, ChildSubintentSpecifiersV2) { ( self.instructions.into(), diff --git a/radix-transactions/src/model/v2/validated_notarized_transaction_v2.rs b/radix-transactions/src/model/v2/validated_notarized_transaction_v2.rs index ca4b2311727..8718196e152 100644 --- a/radix-transactions/src/model/v2/validated_notarized_transaction_v2.rs +++ b/radix-transactions/src/model/v2/validated_notarized_transaction_v2.rs @@ -19,7 +19,7 @@ pub struct ValidatedSignedPartialTransactionV2 { pub non_root_subintents_info: Vec, } -pub struct ValidatedPartialTransactionTreeV2 { +pub struct ValidatedTransactionTreeV2 { pub overall_validity_range: OverallValidityRangeV2, pub total_signature_validations: usize, pub root_intent_info: ValidatedIntentInformationV2, diff --git a/radix-transactions/src/signing/signer.rs b/radix-transactions/src/signing/signer.rs index 3dfd8cac581..4de7a28c7b4 100644 --- a/radix-transactions/src/signing/signer.rs +++ b/radix-transactions/src/signing/signer.rs @@ -33,6 +33,20 @@ pub trait Signer { fn sign_with_public_key(&self, message_hash: &impl IsHash) -> SignatureWithPublicKeyV1; } +impl<'a, S: Signer> Signer for &'a S { + fn public_key(&self) -> PublicKey { + (*self).public_key() + } + + fn sign_without_public_key(&self, message_hash: &impl IsHash) -> SignatureV1 { + (*self).sign_without_public_key(message_hash) + } + + fn sign_with_public_key(&self, message_hash: &impl IsHash) -> SignatureWithPublicKeyV1 { + (*self).sign_with_public_key(message_hash) + } +} + impl Signer for Secp256k1PrivateKey { fn sign_without_public_key(&self, message_hash: &impl IsHash) -> SignatureV1 { self.sign(message_hash).into() diff --git a/radix-transactions/src/validation/mod.rs b/radix-transactions/src/validation/mod.rs index 02c4dd852af..4732e32318f 100644 --- a/radix-transactions/src/validation/mod.rs +++ b/radix-transactions/src/validation/mod.rs @@ -1,9 +1,19 @@ mod id_allocator; mod id_validator; mod signature_validator; +mod transaction_structure_validator; +mod transaction_validation_configuration; mod transaction_validator; +mod transaction_validator_v1; +mod transaction_validator_v2; +#[cfg(test)] +mod validation_test_helpers; pub use id_allocator::*; pub use id_validator::*; pub use signature_validator::*; +pub use transaction_structure_validator::*; +pub use transaction_validation_configuration::*; pub use transaction_validator::*; +#[cfg(test)] +pub(crate) use validation_test_helpers::*; diff --git a/radix-transactions/src/validation/signature_validator.rs b/radix-transactions/src/validation/signature_validator.rs index c6e3f3de659..6699784841b 100644 --- a/radix-transactions/src/validation/signature_validator.rs +++ b/radix-transactions/src/validation/signature_validator.rs @@ -32,3 +32,665 @@ pub fn verify(signed_hash: &Hash, public_key: &PublicKey, signature: &SignatureV _ => false, } } + +pub trait SignedIntentTreeStructure { + type IntentTree: IntentTreeStructure; + fn root_signatures(&self) -> PendingIntentSignatureValidations; + fn non_root_subintent_signatures( + &self, + ) -> impl ExactSizeIterator; + fn intent_tree(&self) -> &Self::IntentTree; + fn transaction_version(&self) -> TransactionVersion; + + fn construct_pending_signature_validations<'a>( + &'a self, + config: &'a TransactionValidationConfig, + ) -> Result, TransactionValidationError> { + let mut pending_signatures = AllPendingSignatureValidations::new_with_root( + self.transaction_version(), + config, + self.intent_tree().root().intent_hash(), + self.root_signatures(), + )?; + + let non_root_subintents = self.intent_tree().non_root_subintents(); + let non_root_subintent_signatures = self.non_root_subintent_signatures(); + if non_root_subintents.len() != non_root_subintent_signatures.len() { + return Err( + SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches + .located(TransactionValidationErrorLocation::AcrossTransaction), + ); + } + for (index, (subintent, signatures)) in non_root_subintents + .zip(non_root_subintent_signatures) + .enumerate() + { + pending_signatures.add_non_root( + SubintentIndex(index), + subintent.subintent_hash(), + signatures.for_subintent(subintent.subintent_hash()), + )?; + } + + Ok(pending_signatures) + } +} + +#[must_use] +pub struct AllPendingSignatureValidations<'a> { + transaction_version: TransactionVersion, + config: &'a TransactionValidationConfig, + root: ( + PendingIntentSignatureValidations<'a>, + TransactionValidationErrorLocation, + ), + non_roots: Vec<( + PendingIntentSignatureValidations<'a>, + TransactionValidationErrorLocation, + )>, + total_signature_validations: usize, +} + +pub enum PendingSubintentSignatureValidations<'a> { + Subintent { + intent_signatures: &'a [IntentSignatureV1], + }, + PreviewSubintent { + intent_public_keys: &'a [PublicKey], + }, +} + +impl<'a> PendingSubintentSignatureValidations<'a> { + fn for_subintent(self, signed_hash: SubintentHash) -> PendingIntentSignatureValidations<'a> { + match self { + PendingSubintentSignatureValidations::Subintent { intent_signatures } => { + PendingIntentSignatureValidations::Subintent { + intent_signatures, + signed_hash, + } + } + PendingSubintentSignatureValidations::PreviewSubintent { intent_public_keys } => { + PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys } + } + } + } +} + +/// This can assume that the signature counts are within checked limits, +/// so calculations cannot overflow. +pub enum PendingIntentSignatureValidations<'a> { + TransactionIntent { + notary_is_signatory: bool, + notary_public_key: PublicKey, + notary_signature: SignatureV1, + notarized_hash: SignedTransactionIntentHash, + intent_signatures: &'a [IntentSignatureV1], + signed_hash: TransactionIntentHash, + }, + PreviewTransactionIntent { + notary_is_signatory: bool, + notary_public_key: PublicKey, + intent_public_keys: &'a [PublicKey], + }, + Subintent { + intent_signatures: &'a [IntentSignatureV1], + signed_hash: SubintentHash, + }, + PreviewSubintent { + intent_public_keys: &'a [PublicKey], + }, +} + +impl<'a> AllPendingSignatureValidations<'a> { + pub(crate) fn new_with_root( + transaction_version: TransactionVersion, + config: &'a TransactionValidationConfig, + root_intent_hash: IntentHash, + signatures: PendingIntentSignatureValidations<'a>, + ) -> Result { + let intent_signature_validations = signatures.intent_signature_validations(); + let error_location = TransactionValidationErrorLocation::for_root(root_intent_hash); + if intent_signature_validations > config.max_signer_signatures_per_intent { + return Err(TransactionValidationError::SignatureValidationError( + error_location, + SignatureValidationError::TooManySignatures { + total: intent_signature_validations, + limit: config.max_signer_signatures_per_intent, + }, + )); + } + let notary_signature_validations = signatures.notary_signature_validations(); + + Ok(Self { + transaction_version, + config, + root: (signatures, error_location), + non_roots: Default::default(), + total_signature_validations: intent_signature_validations + + notary_signature_validations, + }) + } + + fn add_non_root( + &mut self, + subintent_index: SubintentIndex, + subintent_hash: SubintentHash, + signatures: PendingIntentSignatureValidations<'a>, + ) -> Result<(), TransactionValidationError> { + let intent_signature_validations = signatures.intent_signature_validations(); + let error_location = + TransactionValidationErrorLocation::NonRootSubintent(subintent_index, subintent_hash); + if intent_signature_validations > self.config.max_signer_signatures_per_intent { + return Err(TransactionValidationError::SignatureValidationError( + error_location, + SignatureValidationError::TooManySignatures { + total: intent_signature_validations, + limit: self.config.max_signer_signatures_per_intent, + }, + )); + } + + self.non_roots.push((signatures, error_location)); + self.total_signature_validations += intent_signature_validations; + Ok(()) + } + + pub(crate) fn validate_all( + self, + ) -> Result { + if self.total_signature_validations > self.config.max_total_signature_validations { + return Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::AcrossTransaction, + SignatureValidationError::TooManySignatures { + total: self.total_signature_validations, + limit: self.config.max_total_signature_validations, + }, + )); + } + let config = self.config; + let transaction_version = self.transaction_version; + let root_signer_keys = Self::validate_signatures(self.root.0, config, transaction_version) + .map_err(|err| { + TransactionValidationError::SignatureValidationError(self.root.1, err) + })?; + + let non_root_signer_keys = self + .non_roots + .into_iter() + .map(|non_root| { + Self::validate_signatures(non_root.0, config, transaction_version).map_err(|err| { + TransactionValidationError::SignatureValidationError(non_root.1, err) + }) + }) + .collect::>()?; + + Ok(SignatureValidationSummary { + root_signer_keys, + non_root_signer_keys, + total_signature_validations: self.total_signature_validations, + }) + } + + fn validate_signatures( + signatures: PendingIntentSignatureValidations, + config: &TransactionValidationConfig, + transaction_version: TransactionVersion, + ) -> Result, SignatureValidationError> { + let public_keys = match signatures { + PendingIntentSignatureValidations::TransactionIntent { + notary_is_signatory, + notary_public_key, + notary_signature, + notarized_hash, + intent_signatures, + signed_hash, + } => { + let mut intent_public_keys: IndexSet = Default::default(); + for signature in intent_signatures { + let public_key = verify_and_recover(signed_hash.as_hash(), &signature.0) + .ok_or(SignatureValidationError::InvalidIntentSignature)?; + + if !intent_public_keys.insert(public_key) { + return Err(SignatureValidationError::DuplicateSigner); + } + } + + if !verify( + notarized_hash.as_hash(), + ¬ary_public_key, + ¬ary_signature, + ) { + return Err(SignatureValidationError::InvalidNotarySignature); + } + + if notary_is_signatory { + if !intent_public_keys.insert(notary_public_key) + && !config.allow_notary_to_duplicate_signer(transaction_version) + { + return Err( + SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner, + ); + } + } + + intent_public_keys + } + PendingIntentSignatureValidations::PreviewTransactionIntent { + notary_is_signatory, + notary_public_key, + intent_public_keys, + } => { + let mut checked_intent_public_keys: IndexSet = Default::default(); + for key in intent_public_keys { + if !checked_intent_public_keys.insert(key.clone()) { + return Err(SignatureValidationError::DuplicateSigner); + } + } + if notary_is_signatory { + if !checked_intent_public_keys.insert(notary_public_key) + && !config.allow_notary_to_duplicate_signer(transaction_version) + { + return Err( + SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner, + ); + } + } + checked_intent_public_keys + } + PendingIntentSignatureValidations::Subintent { + intent_signatures, + signed_hash, + } => { + let mut intent_public_keys: IndexSet = Default::default(); + for signature in intent_signatures { + let public_key = verify_and_recover(signed_hash.as_hash(), &signature.0) + .ok_or(SignatureValidationError::InvalidIntentSignature)?; + + if !intent_public_keys.insert(public_key) { + return Err(SignatureValidationError::DuplicateSigner); + } + } + intent_public_keys + } + PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys } => { + let mut checked_intent_public_keys: IndexSet = Default::default(); + for key in intent_public_keys { + if !checked_intent_public_keys.insert(key.clone()) { + return Err(SignatureValidationError::DuplicateSigner); + } + } + checked_intent_public_keys + } + }; + + Ok(public_keys) + } +} + +pub(crate) struct SignatureValidationSummary { + pub root_signer_keys: IndexSet, + pub non_root_signer_keys: Vec>, + pub total_signature_validations: usize, +} + +impl<'a> PendingIntentSignatureValidations<'a> { + fn intent_signature_validations(&self) -> usize { + match self { + PendingIntentSignatureValidations::TransactionIntent { + intent_signatures, .. + } => intent_signatures.len(), + PendingIntentSignatureValidations::PreviewTransactionIntent { + intent_public_keys, + .. + } => intent_public_keys.len(), + PendingIntentSignatureValidations::Subintent { + intent_signatures, .. + } => intent_signatures.len(), + PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys } => { + intent_public_keys.len() + } + } + } + + fn notary_signature_validations(&self) -> usize { + match self { + PendingIntentSignatureValidations::TransactionIntent { .. } + | PendingIntentSignatureValidations::PreviewTransactionIntent { .. } => 1, + PendingIntentSignatureValidations::Subintent { .. } + | PendingIntentSignatureValidations::PreviewSubintent { .. } => 0, + } + } +} + +#[cfg(test)] +mod tests { + use crate::internal_prelude::*; + + #[test] + fn test_demonstrate_behaviour_with_notary_duplicating_signer() { + // Arrange + let network = NetworkDefinition::simulator(); + let notary = Secp256k1PrivateKey::from_u64(1).unwrap(); + + let babylon_validator = TransactionValidator::new_with_static_config( + TransactionValidationConfig::babylon(), + network.id, + ); + let latest_validator = TransactionValidator::new_with_static_config( + TransactionValidationConfig::latest(), + network.id, + ); + + let transaction_v1 = TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: network.id, + start_epoch_inclusive: Epoch::of(1), + end_epoch_exclusive: Epoch::of(10), + nonce: 0, + notary_public_key: notary.public_key().into(), + notary_is_signatory: true, + tip_percentage: 0, + }) + .manifest(ManifestBuilder::new().drop_auth_zone_proofs().build()) + .sign(¬ary) + .notarize(¬ary) + .build(); + + let transaction_v2 = TransactionV2Builder::new_with_test_defaults() + .notary_is_signatory(true) + .notary_public_key(notary.public_key()) + .manifest(ManifestBuilder::new_v2().drop_auth_zone_proofs().build()) + .sign(¬ary) + .notarize(¬ary) + .build_minimal_no_validate(); + + // Act & Assert - Transaction V1 permits using notary as signatory and also having it sign + // It was deemed that we didn't want to start failing V1 transactions for this at Cuttlefish + // as we didn't want existing integrations to break. + assert!(transaction_v1 + .prepare_and_validate(&babylon_validator) + .is_ok()); + assert!(transaction_v1 + .prepare_and_validate(&latest_validator) + .is_ok()); + + // Act & Assert - Transaction V2 does not permit duplicating a notary is signatory as a signatory + assert_matches!( + transaction_v2.prepare_and_validate(&babylon_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::TransactionTypeNotSupported + )) + ); + assert_matches!( + transaction_v2.prepare_and_validate(&latest_validator), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner + )), + ); + } + + struct FakeSigner<'a, S: Signer> { + signer: &'a S, + } + + impl<'a, S: Signer> FakeSigner<'a, S> { + fn new(signer: &'a S) -> Self { + Self { signer } + } + } + + impl<'a, S: Signer> Signer for FakeSigner<'a, S> { + fn public_key(&self) -> PublicKey { + self.signer.public_key().into() + } + + fn sign_without_public_key(&self, message_hash: &impl IsHash) -> SignatureV1 { + let mut signature = self.signer.sign_without_public_key(message_hash); + match &mut signature { + SignatureV1::Secp256k1(inner_signature) => { + inner_signature.0[5] += 1; + } + SignatureV1::Ed25519(inner_signature) => { + inner_signature.0[5] += 1; + } + } + signature + } + + fn sign_with_public_key(&self, message_hash: &impl IsHash) -> SignatureWithPublicKeyV1 { + let mut signature = self.signer.sign_with_public_key(message_hash); + match &mut signature { + SignatureWithPublicKeyV1::Secp256k1 { signature } => { + signature.0[5] += 1; + } + SignatureWithPublicKeyV1::Ed25519 { + signature, + public_key: _, + } => { + signature.0[5] += 1; + } + } + signature + } + } + + #[test] + fn test_invalid_signatures() { + let network = NetworkDefinition::simulator(); + + let validator = TransactionValidator::new_with_static_config( + TransactionValidationConfig::latest(), + network.id, + ); + + let versions_to_test = [TransactionVersion::V1, TransactionVersion::V2]; + + fn validate_transaction( + validator: &TransactionValidator, + version: TransactionVersion, + signer: &impl Signer, + notary: &impl Signer, + ) -> Result, TransactionValidationError> { + let signer_keys = match version { + TransactionVersion::V1 => { + unsigned_v1_builder(notary.public_key().into()) + .sign(signer) + .notarize(notary) + .build() + .prepare_and_validate(validator)? + .signer_keys + } + TransactionVersion::V2 => { + TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .notary_public_key(notary.public_key()) + .sign(signer) + .notarize(notary) + .build_minimal_no_validate() + .prepare_and_validate(validator)? + .transaction_intent_info + .signer_keys + } + }; + Ok(signer_keys) + } + + { + // Test Secp256k1 + let notary = Secp256k1PrivateKey::from_u64(1).unwrap(); + let signer = Secp256k1PrivateKey::from_u64(13).unwrap(); + for version in versions_to_test { + assert_matches!( + validate_transaction(&validator, version, &signer, ¬ary), + Ok(signer_keys) => { + assert_eq!(signer_keys.len(), 1); + assert_eq!(signer_keys[0], signer.public_key().into()); + } + ); + match validate_transaction(&validator, version, &FakeSigner::new(&signer), ¬ary) + { + // Coincidentally, between V1 and V2 we hit both cases below + Ok(signer_keys) => { + // NOTE: Because we recover our Secp256k1 public keys, by mutating the signature + // we might have stumbled on a valid signature for a different key - but that's okay. + // As long as we can't fake the signature of a particular key, that's okay. + assert_eq!(signer_keys.len(), 1); + assert_ne!(signer_keys[0], signer.public_key().into()); + } + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::InvalidIntentSignature, + )) => {} + other_result => { + panic!("Unexpected result: {other_result:?}"); + } + } + assert_matches!( + validate_transaction(&validator, version, &signer, &FakeSigner::new(¬ary)), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::InvalidNotarySignature + )) + ); + } + } + + { + // Test Ed25519 + let notary = Ed25519PrivateKey::from_u64(1).unwrap(); + let signer = Ed25519PrivateKey::from_u64(13).unwrap(); + for version in versions_to_test { + assert_matches!( + validate_transaction(&validator, version, &signer, ¬ary), + Ok(signer_keys) => { + assert_eq!(signer_keys.len(), 1); + assert_eq!(signer_keys[0], signer.public_key().into()); + } + ); + assert_matches!( + validate_transaction(&validator, version, &FakeSigner::new(&signer), ¬ary), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::InvalidIntentSignature + )) + ); + assert_matches!( + validate_transaction(&validator, version, &signer, &FakeSigner::new(¬ary)), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::InvalidNotarySignature + )) + ); + } + } + } + + #[test] + fn too_many_signatures_should_be_rejected() { + fn validate_transaction( + root_signature_count: usize, + signature_counts: Vec, + ) -> Result { + TransactionV2Builder::new_with_test_defaults() + .add_children( + signature_counts + .iter() + .enumerate() + .map(|(i, signature_count)| { + create_leaf_partial_transaction(i as u64, *signature_count) + }), + ) + .add_manifest_calling_each_child_once() + .multi_sign( + (0..root_signature_count) + .into_iter() + .map(|i| Secp256k1PrivateKey::from_u64((100 + i) as u64).unwrap()), + ) + .default_notarize_and_validate() + } + + assert_matches!(validate_transaction(1, vec![10]), Ok(_)); + assert_matches!( + validate_transaction(1, vec![10, 20]), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(1), _), + SignatureValidationError::TooManySignatures { + total: 20, + limit: 16, + }, + )) + ); + assert_matches!( + validate_transaction(17, vec![0, 3]), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::TooManySignatures { + total: 17, + limit: 16, + }, + )) + ); + assert_matches!( + validate_transaction(1, vec![10, 10, 10, 10, 10, 10, 10]), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::AcrossTransaction, + SignatureValidationError::TooManySignatures { + total: 72, // 70 from subintent, 1 from transaction intent, 1 from notarization + limit: 64 + }, + )) + ); + } + + #[test] + fn test_incorrect_number_of_subintent_signature_batches() { + // CASE 1: Too fee signatures + let validator = TransactionValidator::new_for_latest_simulator(); + + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_children(vec![PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .build()]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate(); + + // Remove one signature batch + let removed_signature_batch = transaction + .signed_transaction_intent + .non_root_subintent_signatures + .by_subintent + .pop() + .unwrap(); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::AcrossTransaction, + SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches + )) + ); + + // CASE 2: Too many signature batches + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate(); + + // Add an extra signature batch + transaction + .signed_transaction_intent + .non_root_subintent_signatures + .by_subintent + .push(removed_signature_batch); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::AcrossTransaction, + SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches + )) + ); + } +} diff --git a/radix-transactions/src/validation/transaction_structure_validator.rs b/radix-transactions/src/validation/transaction_structure_validator.rs new file mode 100644 index 00000000000..5b4c62cbfa4 --- /dev/null +++ b/radix-transactions/src/validation/transaction_structure_validator.rs @@ -0,0 +1,444 @@ +use crate::internal_prelude::*; +use core::ops::ControlFlow; + +pub trait IntentStructure { + fn intent_hash(&self) -> IntentHash; + fn children(&self) -> impl ExactSizeIterator; + + /// Should perform all the validation of the intent, except the relationship to other intents. + fn validate_intent( + &self, + validator: &TransactionValidator, + aggregation: &mut AcrossIntentAggregation, + ) -> Result; +} + +pub trait IntentTreeStructure { + type RootIntentStructure: IntentStructure; + type SubintentStructure: IntentStructure + HasSubintentHash; + fn root(&self) -> &Self::RootIntentStructure; + fn non_root_subintents<'a>( + &'a self, + ) -> impl ExactSizeIterator; +} + +pub struct ValidatedIntentTreeInformation { + pub intent_relationships: IntentRelationships, + pub overall_validity_range: OverallValidityRangeV2, + pub root_yield_summary: ManifestYieldSummary, +} + +impl TransactionValidator { + pub fn validate_intents_and_structure( + &self, + intent_tree: &impl IntentTreeStructure, + ) -> Result { + let intent_relationships = self.validate_intent_relationships(intent_tree)?; + + let non_root_subintent_details = &intent_relationships.non_root_subintents; + let mut aggregation = AcrossIntentAggregation::start(); + let mut yield_summaries: IndexMap = + index_map_with_capacity(intent_tree.non_root_subintents().len() + 1); + + let root_yield_summary = { + let root_intent_hash = intent_tree.root().intent_hash(); + let yield_summary = intent_tree + .root() + .validate_intent(self, &mut aggregation) + .map_err(|err| { + TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::for_root(root_intent_hash), + err, + ) + })?; + yield_summaries.insert(root_intent_hash, yield_summary.clone()); + yield_summary + }; + + for (index, subintent) in intent_tree.non_root_subintents().enumerate() { + let subintent_hash = subintent.subintent_hash(); + let yield_summary = + subintent + .validate_intent(self, &mut aggregation) + .map_err(|err| { + TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent( + SubintentIndex(index), + subintent_hash, + ), + err, + ) + })?; + yield_summaries.insert(subintent_hash.into(), yield_summary); + } + + let overall_validity_range = aggregation.finalize(&self.config)?; + + for (child_hash, child_details) in non_root_subintent_details { + let child_intent_hash = IntentHash::Subintent(*child_hash); + // This checks that the YIELD_TO_PARENTs in a subintent match the YIELD_TO_CHILDS in the parent. + // The instruction validation has already checked that the subintents end with a YIELD_TO_PARENT. + let parent_yield_summary = yield_summaries.get(&child_details.parent).unwrap(); + let parent_yield_child_calls = + *parent_yield_summary.child_yields.get(child_hash).unwrap(); + let child_yield_summary = yield_summaries.get(&child_intent_hash).unwrap(); + let child_yield_parent_calls = child_yield_summary.parent_yields; + if parent_yield_child_calls != child_yield_parent_calls { + return Err( + SubintentStructureError::MismatchingYieldChildAndYieldParentCountsForSubintent + .for_subintent(child_details.index, *child_hash), + ); + } + } + + Ok(ValidatedIntentTreeInformation { + intent_relationships, + overall_validity_range, + root_yield_summary, + }) + } + + /// The root intent can be either: + /// * If validating a full transaction: a transaction intent + /// * If validating a partial transaction: a root subintent + fn validate_intent_relationships( + &self, + intent_tree: &impl IntentTreeStructure, + ) -> Result { + let mut root_intent_details = RootIntentRelationshipDetails::default(); + let mut non_root_subintent_details = + IndexMap::::default(); + + // STEP 1 + // ------ + // * We establish that the non-root subintents are unique + // * We create an index from the SubintentHash to SubintentIndex + for (index, subintent) in intent_tree.non_root_subintents().enumerate() { + let subintent_hash = subintent.subintent_hash(); + let index = SubintentIndex(index); + let details = SubintentRelationshipDetails::default_for(index); + if let Some(_) = non_root_subintent_details.insert(subintent_hash, details) { + return Err(SubintentStructureError::DuplicateSubintent + .for_subintent(index, subintent_hash)); + } + } + + // STEP 2 + // ------ + // We establish, for each parent intent, that each of its children: + // * Exist as subintents in the transaction tree + // * Has no other parents + // + // We also: + // * Save the unique parent on each subintent which is a child + // * Save the children of an intent into its intent details + // + // After this step, we know that each subintent has at most one parent. + // We determine that every subintent has exactly one parent in step 4. + + // STEP 2A - Handle children of the root intent + { + let parent_hash = intent_tree.root().intent_hash(); + let intent_details = &mut root_intent_details; + for child_hash in intent_tree.root().children() { + let child_subintent_details = non_root_subintent_details + .get_mut(&child_hash) + .ok_or_else(|| { + SubintentStructureError::ChildSubintentNotIncludedInTransaction(child_hash) + .for_unindexed() + })?; + if child_subintent_details.parent == PLACEHOLDER_PARENT { + child_subintent_details.parent = parent_hash; + } else { + return Err(SubintentStructureError::SubintentHasMultipleParents + .for_subintent(child_subintent_details.index, child_hash)); + } + intent_details.children.push(child_subintent_details.index); + } + } + + // STEP 2B - Handle the children of each subintent + for subintent in intent_tree.non_root_subintents() { + let subintent_hash = subintent.subintent_hash(); + let parent_hash: IntentHash = subintent_hash.into(); + let children = subintent.children(); + let mut children_details = Vec::with_capacity(children.len()); + for child_hash in children { + let child_subintent_details = non_root_subintent_details + .get_mut(&child_hash) + .ok_or_else(|| { + SubintentStructureError::ChildSubintentNotIncludedInTransaction(child_hash) + .for_unindexed() + })?; + if child_subintent_details.parent == PLACEHOLDER_PARENT { + child_subintent_details.parent = parent_hash; + } else { + return Err(SubintentStructureError::SubintentHasMultipleParents + .for_subintent(child_subintent_details.index, child_hash)); + } + children_details.push(child_subintent_details.index); + } + non_root_subintent_details + .get_mut(&subintent_hash) + .unwrap() + .children = children_details; + } + + // STEP 3 + // ------ + // We traverse the child relationships from the root, and mark a depth. + // We error if any exceed the maximum depth. + // + // The iteration count is guaranteed to be bounded by the number of subintents because: + // * Each subintent has at most one parent from step 2. + // * Each parent -> child relationship is traversed at most once in the iteration. + // Quick proof by contradiction: + // - Assume not. Then some parent A is visited more than once. + // - Take the earliest such A in the iteration. + // - On both of its visits, A can only have been visited from its parent B. + // - But then B must have been visited more than once, contradicting the minimality of A. + let mut work_list = vec![]; + for index in root_intent_details.children.iter() { + work_list.push((*index, 1)); + } + + let max_depth = if intent_tree.root().intent_hash().is_for_subintent() { + self.config.max_subintent_depth - 1 + } else { + self.config.max_subintent_depth + }; + + loop { + let Some((index, depth)) = work_list.pop() else { + break; + }; + if depth > max_depth { + let (hash, _) = non_root_subintent_details.get_index(index.0).unwrap(); + return Err( + SubintentStructureError::SubintentExceedsMaxDepth.for_subintent(index, *hash) + ); + } + let (_, subintent_details) = non_root_subintent_details.get_index_mut(index.0).unwrap(); + subintent_details.depth = depth; + for index in subintent_details.children.iter() { + work_list.push((*index, depth + 1)); + } + } + + // STEP 4 + // ------ + // We check that every subintent has a marked "depth from root". + // + // Combined with step 2 and step 3, we now have that: + // * Every subintent has a unique parent. + // * Every subintent is reachable from the root. + // + // Therefore there is a unique path from every subintent to the root, which implies + // the subintents form a tree. + for (hash, details) in non_root_subintent_details.iter() { + if details.depth == 0 { + return Err( + SubintentStructureError::SubintentIsNotReachableFromTheTransactionIntent + .for_subintent(details.index, *hash), + ); + } + } + + Ok(IntentRelationships { + root_intent: root_intent_details, + non_root_subintents: non_root_subintent_details, + }) + } +} + +// This type is public so it can be used by the toolkit. +#[must_use] +pub struct AcrossIntentAggregation { + total_reference_count: usize, + overall_start_epoch_inclusive: Epoch, + overall_end_epoch_exclusive: Epoch, + overall_start_timestamp_inclusive: Option, + overall_end_timestamp_exclusive: Option, +} + +impl AcrossIntentAggregation { + pub fn start() -> Self { + Self { + total_reference_count: 0, + overall_start_epoch_inclusive: Epoch::zero(), + overall_end_epoch_exclusive: Epoch::of(u64::MAX), + overall_start_timestamp_inclusive: None, + overall_end_timestamp_exclusive: None, + } + } + + pub fn finalize( + self, + config: &TransactionValidationConfig, + ) -> Result { + if self.total_reference_count > config.max_total_references { + return Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::AcrossTransaction, + IntentValidationError::TooManyReferences { + total: self.total_reference_count, + limit: config.max_total_references, + }, + )); + } + Ok(OverallValidityRangeV2 { + epoch_range: EpochRange { + start_epoch_inclusive: self.overall_start_epoch_inclusive, + end_epoch_exclusive: self.overall_end_epoch_exclusive, + }, + proposer_timestamp_range: ProposerTimestampRange { + start_timestamp_inclusive: self.overall_start_timestamp_inclusive, + end_timestamp_exclusive: self.overall_end_timestamp_exclusive, + }, + }) + } + + pub fn record_reference_count( + &mut self, + count: usize, + config: &TransactionValidationConfig, + ) -> Result<(), IntentValidationError> { + if count > config.max_references_per_intent { + return Err(IntentValidationError::TooManyReferences { + total: count, + limit: config.max_references_per_intent, + }); + } + self.total_reference_count = self.total_reference_count.saturating_add(count); + Ok(()) + } + + pub fn update_headers( + &mut self, + start_epoch_inclusive: Epoch, + end_epoch_exclusive: Epoch, + start_timestamp_inclusive: Option<&Instant>, + end_timestamp_exclusive: Option<&Instant>, + ) -> Result<(), HeaderValidationError> { + if start_epoch_inclusive > self.overall_start_epoch_inclusive { + self.overall_start_epoch_inclusive = start_epoch_inclusive; + } + if end_epoch_exclusive < self.overall_end_epoch_exclusive { + self.overall_end_epoch_exclusive = end_epoch_exclusive; + } + if self.overall_start_epoch_inclusive >= self.overall_end_epoch_exclusive { + return Err(HeaderValidationError::NoValidEpochRangeAcrossAllIntents); + } + if let Some(start_timestamp_inclusive) = start_timestamp_inclusive { + if self.overall_start_timestamp_inclusive.is_none() + || self + .overall_start_timestamp_inclusive + .as_ref() + .is_some_and(|t| start_timestamp_inclusive > t) + { + self.overall_start_timestamp_inclusive = Some(*start_timestamp_inclusive); + } + } + if let Some(end_timestamp_exclusive) = end_timestamp_exclusive { + if self.overall_end_timestamp_exclusive.is_none() + || self + .overall_end_timestamp_exclusive + .as_ref() + .is_some_and(|t| end_timestamp_exclusive < t) + { + self.overall_end_timestamp_exclusive = Some(*end_timestamp_exclusive); + } + } + match ( + self.overall_start_timestamp_inclusive.as_ref(), + self.overall_end_timestamp_exclusive.as_ref(), + ) { + (Some(start_inclusive), Some(end_exclusive)) => { + if start_inclusive >= end_exclusive { + return Err(HeaderValidationError::NoValidTimestampRangeAcrossAllIntents); + } + } + _ => {} + } + Ok(()) + } +} + +pub struct IntentRelationships { + pub root_intent: RootIntentRelationshipDetails, + pub non_root_subintents: IndexMap, +} + +#[derive(Default)] +pub struct RootIntentRelationshipDetails { + pub children: Vec, +} + +pub struct SubintentRelationshipDetails { + pub index: SubintentIndex, + pub parent: IntentHash, + pub depth: usize, + pub children: Vec, +} + +impl SubintentRelationshipDetails { + fn default_for(index: SubintentIndex) -> Self { + Self { + index, + parent: PLACEHOLDER_PARENT, + depth: Default::default(), + children: Default::default(), + } + } +} + +const PLACEHOLDER_PARENT: IntentHash = + IntentHash::Transaction(TransactionIntentHash(Hash([0u8; Hash::LENGTH]))); + +// This type is public so it can be used by the toolkit. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ManifestYieldSummary { + pub parent_yields: usize, + pub child_yields: IndexMap, +} + +impl ManifestYieldSummary { + pub fn new_with_children(children: impl Iterator) -> Self { + Self { + parent_yields: 0, + child_yields: children.map(|child| (child, 0)).collect(), + } + } +} + +impl ManifestInterpretationVisitor for ManifestYieldSummary { + type Output = ManifestValidationError; + + fn on_end_instruction(&mut self, details: OnEndInstruction) -> ControlFlow { + // Safe from overflow due to checking max instruction count + match details.effect { + ManifestInstructionEffect::Invocation { + kind: InvocationKind::YieldToParent, + .. + } => { + self.parent_yields += 1; + } + ManifestInstructionEffect::Invocation { + kind: + InvocationKind::YieldToChild { + child_index: ManifestNamedIntent(index), + }, + .. + } => { + let index = index as usize; + + // This should exist because we are handling this after the instruction, + // so the interpreter should have errored with ChildIntentNotRegistered + // if the child yield was invalid. + let (_, count) = self.child_yields.get_index_mut(index).unwrap(); + *count += 1; + } + _ => {} + } + ControlFlow::Continue(()) + } +} diff --git a/radix-transactions/src/validation/transaction_validation_configuration.rs b/radix-transactions/src/validation/transaction_validation_configuration.rs new file mode 100644 index 00000000000..c73bade84c9 --- /dev/null +++ b/radix-transactions/src/validation/transaction_validation_configuration.rs @@ -0,0 +1,146 @@ +use crate::internal_prelude::*; +use radix_substate_store_interface::interface::{SubstateDatabase, SubstateDatabaseExtensions}; + +define_single_versioned! { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] + pub TransactionValidationConfigurationSubstate(TransactionValidationConfigurationVersions) => TransactionValidationConfig = TransactionValidationConfigV1, + outer_attributes: [ + #[derive(ScryptoSborAssertion)] + #[sbor_assert(backwards_compatible( + cuttlefish = "FILE:transaction_validation_configuration_substate_cuttlefish_schema.bin", + ))] + ] +} + +impl TransactionValidationConfig { + pub fn load(database: &impl SubstateDatabase) -> Self { + database + .get_substate::( + TRANSACTION_TRACKER, + BOOT_LOADER_PARTITION, + BootLoaderField::TransactionValidationConfiguration, + ) + .map(|s| s.fully_update_and_into_latest_version()) + .unwrap_or_else(|| Self::babylon()) + } + + pub(crate) fn allow_notary_to_duplicate_signer(&self, version: TransactionVersion) -> bool { + match version { + TransactionVersion::V1 => self.v1_transactions_allow_notary_to_duplicate_signer, + TransactionVersion::V2 => false, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TransactionVersion { + V1, + V2, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] +pub struct TransactionValidationConfigV1 { + /// Signer signatures only, not including notary signature + pub max_signer_signatures_per_intent: usize, + pub max_references_per_intent: usize, + pub min_tip_percentage: u16, + pub max_tip_percentage: u16, + pub max_epoch_range: u64, + pub max_instructions: usize, + pub message_validation: MessageValidationConfig, + pub v1_transactions_allow_notary_to_duplicate_signer: bool, + pub preparation_settings: PreparationSettingsV1, + pub manifest_validation: ManifestValidationRuleset, + // V2 settings + pub v2_transactions_allowed: bool, + pub min_tip_basis_points: u32, + pub max_tip_basis_points: u32, + /// A setting of N here allows a total depth of N + 1 if you + /// include the root transaction intent. + pub max_subintent_depth: usize, + pub max_total_signature_validations: usize, + pub max_total_references: usize, +} + +impl TransactionValidationConfig { + pub const fn latest() -> Self { + Self::cuttlefish() + } + + pub const fn babylon() -> Self { + Self { + max_signer_signatures_per_intent: 16, + max_references_per_intent: usize::MAX, + min_tip_percentage: 0, + max_tip_percentage: u16::MAX, + max_instructions: usize::MAX, + // ~30 days given 5 minute epochs + max_epoch_range: 12 * 24 * 30, + v1_transactions_allow_notary_to_duplicate_signer: true, + manifest_validation: ManifestValidationRuleset::BabylonBasicValidator, + message_validation: MessageValidationConfig::babylon(), + preparation_settings: PreparationSettings::babylon(), + // V2-only settings + v2_transactions_allowed: true, + max_subintent_depth: 0, + min_tip_basis_points: 0, + max_tip_basis_points: 0, + max_total_signature_validations: usize::MAX, + max_total_references: usize::MAX, + } + } + + pub const fn cuttlefish() -> Self { + Self { + max_references_per_intent: 512, + v2_transactions_allowed: true, + max_subintent_depth: 3, + min_tip_basis_points: 0, + max_instructions: 1000, + manifest_validation: ManifestValidationRuleset::Interpreter( + InterpreterValidationRulesetSpecifier::Cuttlefish, + ), + // Tip of 100 times the cost of a transaction + max_tip_basis_points: 100 * 10000, + preparation_settings: PreparationSettings::cuttlefish(), + max_total_signature_validations: 64, + max_total_references: 512, + ..Self::babylon() + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] +pub enum ManifestValidationRuleset { + BabylonBasicValidator, + Interpreter(InterpreterValidationRulesetSpecifier), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] +pub struct MessageValidationConfig { + pub max_plaintext_message_length: usize, + pub max_encrypted_message_length: usize, + pub max_mime_type_length: usize, + pub max_decryptors: usize, +} + +impl MessageValidationConfig { + pub const fn latest() -> Self { + Self::babylon() + } + + pub const fn babylon() -> Self { + Self { + max_plaintext_message_length: 2048, + max_mime_type_length: 128, + max_encrypted_message_length: 2048 + 12 + 16, // Account for IV and MAC - see AesGcmPayload + max_decryptors: 20, + } + } +} + +impl Default for MessageValidationConfig { + fn default() -> Self { + Self::latest() + } +} diff --git a/radix-transactions/src/validation/transaction_validator.rs b/radix-transactions/src/validation/transaction_validator.rs index b43ea22a4ab..70d80e72648 100644 --- a/radix-transactions/src/validation/transaction_validator.rs +++ b/radix-transactions/src/validation/transaction_validator.rs @@ -1,155 +1,11 @@ -use core::ops::ControlFlow; - -use radix_substate_store_interface::interface::{SubstateDatabase, SubstateDatabaseExtensions}; +use radix_substate_store_interface::interface::SubstateDatabase; use crate::internal_prelude::*; -pub trait TransactionPreparer { - fn preparation_settings(&self) -> &PreparationSettings; -} - -define_single_versioned! { - #[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] - pub TransactionValidationConfigurationSubstate(TransactionValidationConfigurationVersions) => TransactionValidationConfig = TransactionValidationConfigV1, - outer_attributes: [ - #[derive(ScryptoSborAssertion)] - #[sbor_assert(backwards_compatible( - cuttlefish = "FILE:transaction_validation_configuration_substate_cuttlefish_schema.bin", - ))] - ] -} - -impl TransactionValidationConfig { - pub fn load(database: &impl SubstateDatabase) -> Self { - database - .get_substate::( - TRANSACTION_TRACKER, - BOOT_LOADER_PARTITION, - BootLoaderField::TransactionValidationConfiguration, - ) - .map(|s| s.fully_update_and_into_latest_version()) - .unwrap_or_else(|| Self::babylon()) - } - - fn allow_notary_to_duplicate_signer(&self, version: TransactionVersion) -> bool { - match version { - TransactionVersion::V1 => self.v1_transactions_allow_notary_to_duplicate_signer, - TransactionVersion::V2 => false, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] -pub struct TransactionValidationConfigV1 { - /// Signer signatures only, not including notary signature - pub max_signer_signatures_per_intent: usize, - pub max_references_per_intent: usize, - pub min_tip_percentage: u16, - pub max_tip_percentage: u16, - pub max_epoch_range: u64, - pub max_instructions: usize, - pub message_validation: MessageValidationConfig, - pub v1_transactions_allow_notary_to_duplicate_signer: bool, - pub preparation_settings: PreparationSettingsV1, - pub manifest_validation: ManifestValidationRuleset, - // V2 settings - pub v2_transactions_allowed: bool, - pub min_tip_basis_points: u32, - pub max_tip_basis_points: u32, - /// A setting of N here allows a total depth of N + 1 if you - /// include the root transaction intent. - pub max_subintent_depth: usize, - pub max_total_signature_validations: usize, - pub max_total_references: usize, -} - -impl TransactionValidationConfig { - pub const fn latest() -> Self { - Self::cuttlefish() - } - - pub const fn babylon() -> Self { - Self { - max_signer_signatures_per_intent: 16, - max_references_per_intent: usize::MAX, - min_tip_percentage: 0, - max_tip_percentage: u16::MAX, - max_instructions: usize::MAX, - // ~30 days given 5 minute epochs - max_epoch_range: 12 * 24 * 30, - v1_transactions_allow_notary_to_duplicate_signer: true, - manifest_validation: ManifestValidationRuleset::BabylonBasicValidator, - message_validation: MessageValidationConfig::babylon(), - preparation_settings: PreparationSettings::babylon(), - // V2-only settings - v2_transactions_allowed: true, - max_subintent_depth: 0, - min_tip_basis_points: 0, - max_tip_basis_points: 0, - max_total_signature_validations: usize::MAX, - max_total_references: usize::MAX, - } - } - - pub const fn cuttlefish() -> Self { - Self { - max_references_per_intent: 512, - v2_transactions_allowed: true, - max_subintent_depth: 3, - min_tip_basis_points: 0, - max_instructions: 1000, - manifest_validation: ManifestValidationRuleset::Interpreter( - InterpreterValidationRulesetSpecifier::Cuttlefish, - ), - // Tip of 100 times the cost of a transaction - max_tip_basis_points: 100 * 10000, - preparation_settings: PreparationSettings::cuttlefish(), - max_total_signature_validations: 64, - max_total_references: 512, - ..Self::babylon() - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] -pub enum ManifestValidationRuleset { - BabylonBasicValidator, - Interpreter(InterpreterValidationRulesetSpecifier), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Sbor)] -pub struct MessageValidationConfig { - pub max_plaintext_message_length: usize, - pub max_encrypted_message_length: usize, - pub max_mime_type_length: usize, - pub max_decryptors: usize, -} - -impl MessageValidationConfig { - pub const fn latest() -> Self { - Self::babylon() - } - - pub const fn babylon() -> Self { - Self { - max_plaintext_message_length: 2048, - max_mime_type_length: 128, - max_encrypted_message_length: 2048 + 12 + 16, // Account for IV and MAC - see AesGcmPayload - max_decryptors: 20, - } - } -} - -impl Default for MessageValidationConfig { - fn default() -> Self { - Self::latest() - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TransactionValidator { - config: TransactionValidationConfig, - required_network_id: Option, + pub(super) config: TransactionValidationConfig, + pub(super) required_network_id: Option, } impl TransactionValidator { @@ -204,2413 +60,8 @@ impl TransactionValidator { pub fn preparation_settings(&self) -> &PreparationSettings { &self.config.preparation_settings } - - #[allow(deprecated)] - pub fn validate_notarized_v1( - &self, - transaction: PreparedNotarizedTransactionV1, - ) -> Result { - let transaction_intent = &transaction.signed_intent.intent; - - let signatures = AllPendingSignatureValidations::new_with_root( - TransactionVersion::V1, - &self.config, - transaction_intent.transaction_intent_hash().into(), - PendingIntentSignatureValidations::TransactionIntent { - notary_is_signatory: transaction_intent.header.inner.notary_is_signatory, - notary_public_key: transaction_intent.header.inner.notary_public_key, - notary_signature: transaction.notary_signature.inner.0, - notarized_hash: transaction.signed_transaction_intent_hash(), - intent_signatures: transaction - .signed_intent - .intent_signatures - .inner - .signatures - .as_slice(), - signed_hash: transaction_intent.transaction_intent_hash(), - }, - )?; - - let aggregation = self - .validate_intent_v1(&transaction.signed_intent.intent) - .map_err(|err| { - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::RootTransactionIntent( - transaction_intent.transaction_intent_hash(), - ), - err, - ) - })?; - let _ = aggregation.finalize(&self.config)?; // Don't use the overall validity range in V1 - - let encoded_instructions = - manifest_encode(&transaction.signed_intent.intent.instructions.inner.0)?; - - let SignatureValidationSummary { - root_signer_keys: signer_keys, - non_root_signer_keys: _, // Not used in V1 - total_signature_validations: num_of_signature_validations, - } = signatures.validate_all()?; - - Ok(ValidatedNotarizedTransactionV1 { - prepared: transaction, - encoded_instructions, - signer_keys, - num_of_signature_validations, - }) - } - - #[allow(deprecated)] - pub fn validate_preview_intent_v1( - &self, - preview_intent: PreviewIntentV1, - ) -> Result { - let fake_intent_hash = SimulatedTransactionIntentNullification.transaction_intent_hash(); - let intent = preview_intent.intent.prepare(self.preparation_settings())?; - - let aggregation = self.validate_intent_v1(&intent).map_err(|err| { - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(fake_intent_hash), - err, - ) - })?; - aggregation.finalize(&self.config)?; - - let encoded_instructions = manifest_encode(&intent.instructions.inner.0)?; - - Ok(ValidatedPreviewIntent { - intent, - encoded_instructions, - signer_public_keys: preview_intent.signer_public_keys, - flags: preview_intent.flags, - }) - } - - // This method is public so it can be used by the toolkit. - #[allow(deprecated)] - pub fn validate_intent_v1( - &self, - intent: &PreparedIntentV1, - ) -> Result { - let mut aggregation = AcrossIntentAggregation::start(); - self.validate_header_v1(&intent.header.inner)?; - self.validate_message_v1(&intent.message.inner)?; - aggregation.record_reference_count(intent.instructions.references.len(), &self.config)?; - self.validate_instructions_v1(&intent.instructions.inner.0, &intent.blobs.blobs_by_hash)?; - - Ok(aggregation) - } - - pub fn validate_instructions_v1( - &self, - instructions: &[InstructionV1], - blobs: &IndexMap>, - ) -> Result<(), IntentValidationError> { - if instructions.len() > self.config.max_instructions { - return Err(ManifestValidationError::TooManyInstructions.into()); - } - impl<'a> ReadableManifestBase for (&'a [InstructionV1], &'a IndexMap>) { - fn is_subintent(&self) -> bool { - false - } - - fn get_blobs<'b>(&'b self) -> impl Iterator)> { - self.1.iter() - } - - fn get_known_object_names_ref(&self) -> ManifestObjectNamesRef { - ManifestObjectNamesRef::Unknown - } - } - impl<'a> TypedReadableManifest for (&'a [InstructionV1], &'a IndexMap>) { - type Instruction = InstructionV1; - - fn get_typed_instructions(&self) -> &[Self::Instruction] { - self.0 - } - } - - match self.config.manifest_validation { - ManifestValidationRuleset::BabylonBasicValidator => self - .validate_instructions_basic_v1(instructions) - .map_err(|err| err.into()), - ManifestValidationRuleset::Interpreter(specifier) => StaticManifestInterpreter::new( - ValidationRuleset::for_specifier(specifier), - &(instructions, blobs), - ) - .validate() - .map_err(|err| err.into()), - } - } - - pub fn validate_instructions_basic_v1( - &self, - instructions: &[InstructionV1], - ) -> Result<(), ManifestBasicValidatorError> { - let mut id_validator = BasicManifestValidator::new(); - for instruction in instructions { - match instruction.effect() { - ManifestInstructionEffect::CreateBucket { .. } => { - let _ = id_validator.new_bucket(); - } - ManifestInstructionEffect::CreateProof { source_amount, .. } => { - let _ = id_validator.new_proof(source_amount.proof_kind())?; - } - ManifestInstructionEffect::ConsumeBucket { - consumed_bucket: bucket, - .. - } => { - id_validator.drop_bucket(&bucket)?; - } - ManifestInstructionEffect::ConsumeProof { - consumed_proof: proof, - .. - } => { - id_validator.drop_proof(&proof)?; - } - ManifestInstructionEffect::CloneProof { cloned_proof, .. } => { - let _ = id_validator.clone_proof(&cloned_proof)?; - } - ManifestInstructionEffect::DropManyProofs { - drop_all_named_proofs, - .. - } => { - if drop_all_named_proofs { - id_validator.drop_all_named_proofs()?; - } - } - ManifestInstructionEffect::Invocation { args, .. } => { - id_validator.process_call_data(args)?; - } - ManifestInstructionEffect::CreateAddressAndReservation { .. } => { - let _ = id_validator.new_address_reservation(); - id_validator.new_named_address(); - } - ManifestInstructionEffect::ResourceAssertion { .. } => {} - ManifestInstructionEffect::Verification { .. } => { - unreachable!("No InstructionV1 returns this effect"); - } - } - } - Ok(()) - } - - pub fn validate_header_v1( - &self, - header: &TransactionHeaderV1, - ) -> Result<(), HeaderValidationError> { - // network - if let Some(required_network_id) = self.required_network_id { - if header.network_id != required_network_id { - return Err(HeaderValidationError::InvalidNetwork); - } - } - - // epoch - if header.end_epoch_exclusive <= header.start_epoch_inclusive { - return Err(HeaderValidationError::InvalidEpochRange); - } - let max_end_epoch = header - .start_epoch_inclusive - .after(self.config.max_epoch_range) - .ok_or(HeaderValidationError::InvalidEpochRange)?; - if header.end_epoch_exclusive > max_end_epoch { - return Err(HeaderValidationError::InvalidEpochRange); - } - - // tip percentage - if header.tip_percentage < self.config.min_tip_percentage - || header.tip_percentage > self.config.max_tip_percentage - { - return Err(HeaderValidationError::InvalidTip); - } - - Ok(()) - } - - pub fn validate_message_v1(&self, message: &MessageV1) -> Result<(), InvalidMessageError> { - let validation = &self.config.message_validation; - match message { - MessageV1::None => {} - MessageV1::Plaintext(plaintext_message) => { - let PlaintextMessageV1 { mime_type, message } = plaintext_message; - if mime_type.len() > validation.max_mime_type_length { - return Err(InvalidMessageError::MimeTypeTooLong { - actual: mime_type.len(), - permitted: validation.max_mime_type_length, - }); - } - if message.len() > validation.max_plaintext_message_length { - return Err(InvalidMessageError::PlaintextMessageTooLong { - actual: message.len(), - permitted: validation.max_plaintext_message_length, - }); - } - } - MessageV1::Encrypted(encrypted_message) => { - let EncryptedMessageV1 { - encrypted, - decryptors_by_curve, - } = encrypted_message; - if encrypted.0.len() > validation.max_encrypted_message_length { - return Err(InvalidMessageError::EncryptedMessageTooLong { - actual: encrypted.0.len(), - permitted: validation.max_encrypted_message_length, - }); - } - if decryptors_by_curve.len() == 0 { - return Err(InvalidMessageError::NoDecryptors); - } - let mut total_decryptors = 0; - for (curve_type, decryptors) in decryptors_by_curve.iter() { - if decryptors.curve_type() != *curve_type { - return Err(InvalidMessageError::MismatchingDecryptorCurves { - actual: decryptors.curve_type(), - expected: *curve_type, - }); - } - if decryptors.number_of_decryptors() == 0 { - return Err(InvalidMessageError::NoDecryptorsForCurveType { - curve_type: decryptors.curve_type(), - }); - } - // Can't overflow because decryptor count << size of a transaction < 1MB < usize, - total_decryptors += decryptors.number_of_decryptors(); - } - if total_decryptors > validation.max_decryptors { - return Err(InvalidMessageError::TooManyDecryptors { - actual: total_decryptors, - permitted: validation.max_decryptors, - }); - } - } - } - Ok(()) - } - - pub fn validate_notarized_v2( - &self, - prepared: PreparedNotarizedTransactionV2, - ) -> Result { - if !self.config.v2_transactions_allowed { - return Err(TransactionValidationError::TransactionVersionNotPermitted( - 2, - )); - } - - let transaction_intent = &prepared.signed_intent.transaction_intent; - let non_root_subintents = &transaction_intent.non_root_subintents; - - self.validate_transaction_header_v2(&transaction_intent.transaction_header.inner) - .map_err(|err| { - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::RootTransactionIntent( - transaction_intent.transaction_intent_hash(), - ), - IntentValidationError::HeaderValidationError(err), - ) - })?; - - let mut signatures = AllPendingSignatureValidations::new_with_root( - TransactionVersion::V2, - &self.config, - transaction_intent.transaction_intent_hash().into(), - PendingIntentSignatureValidations::TransactionIntent { - notary_is_signatory: transaction_intent - .transaction_header - .inner - .notary_is_signatory, - notary_public_key: transaction_intent - .transaction_header - .inner - .notary_public_key, - notary_signature: prepared.notary_signature.inner.0, - notarized_hash: prepared.signed_transaction_intent_hash(), - intent_signatures: prepared - .signed_intent - .transaction_intent_signatures - .inner - .signatures - .as_slice(), - signed_hash: transaction_intent.transaction_intent_hash(), - }, - )?; - signatures.add_non_root_subintents_v2( - non_root_subintents, - &prepared.signed_intent.non_root_subintent_signatures, - )?; - - let ValidatedPartialTransactionTreeV2 { - overall_validity_range, - total_signature_validations, - root_intent_info, - root_yield_to_parent_count: _, // Checked to be 0 in the manifest validator. - non_root_subintents_info, - } = self.validate_transaction_subtree_v2( - &transaction_intent.root_intent_core, - transaction_intent.transaction_intent_hash().into(), - non_root_subintents, - signatures, - )?; - - Ok(ValidatedNotarizedTransactionV2 { - prepared, - overall_validity_range, - total_signature_validations, - transaction_intent_info: root_intent_info, - non_root_subintents_info, - }) - } - - pub fn validate_preview_transaction_v2( - &self, - prepared: PreparedPreviewTransactionV2, - ) -> Result { - if !self.config.v2_transactions_allowed { - return Err(TransactionValidationError::TransactionVersionNotPermitted( - 2, - )); - } - - let transaction_intent = &prepared.transaction_intent; - let non_root_subintents = &transaction_intent.non_root_subintents; - - self.validate_transaction_header_v2(&transaction_intent.transaction_header.inner) - .map_err(|err| { - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::RootTransactionIntent( - transaction_intent.transaction_intent_hash(), - ), - IntentValidationError::HeaderValidationError(err), - ) - })?; - - let mut signatures = AllPendingSignatureValidations::new_with_root( - TransactionVersion::V2, - &self.config, - transaction_intent.transaction_intent_hash().into(), - PendingIntentSignatureValidations::PreviewTransactionIntent { - notary_is_signatory: transaction_intent - .transaction_header - .inner - .notary_is_signatory, - notary_public_key: transaction_intent - .transaction_header - .inner - .notary_public_key, - intent_public_keys: prepared.root_subintent_signatures.inner.as_slice(), - }, - )?; - signatures.add_non_root_preview_subintents_v2( - non_root_subintents, - &prepared.non_root_subintent_signatures.inner, - )?; - - let ValidatedPartialTransactionTreeV2 { - overall_validity_range, - total_signature_validations: total_expected_signature_validations, - root_intent_info, - root_yield_to_parent_count: _, // Checked to be 0 in the manifest validator. - non_root_subintents_info, - } = self.validate_transaction_subtree_v2( - &transaction_intent.root_intent_core, - transaction_intent.transaction_intent_hash().into(), - non_root_subintents, - signatures, - )?; - - Ok(ValidatedPreviewTransactionV2 { - prepared, - overall_validity_range, - total_expected_signature_validations, - transaction_intent_info: root_intent_info, - non_root_subintents_info, - }) - } - - // This method is public so it can be used by the toolkit. - pub fn validate_transaction_header_v2( - &self, - header: &TransactionHeaderV2, - ) -> Result<(), HeaderValidationError> { - if header.tip_basis_points < self.config.min_tip_basis_points - || header.tip_basis_points > self.config.max_tip_basis_points - { - return Err(HeaderValidationError::InvalidTip); - } - - Ok(()) - } - - pub fn validate_signed_partial_transaction_v2( - &self, - prepared: PreparedSignedPartialTransactionV2, - ) -> Result { - if !self.config.v2_transactions_allowed { - return Err(TransactionValidationError::TransactionVersionNotPermitted( - 2, - )); - } - - let root_subintent = &prepared.partial_transaction.root_subintent; - let root_intent_hash: IntentHash = root_subintent.subintent_hash().into(); - let non_root_subintents = &prepared.partial_transaction.non_root_subintents; - - let mut signatures = AllPendingSignatureValidations::new_with_root( - TransactionVersion::V2, - &self.config, - root_intent_hash, - PendingIntentSignatureValidations::Subintent { - intent_signatures: prepared - .root_subintent_signatures - .inner - .signatures - .as_slice(), - signed_hash: root_subintent.subintent_hash(), - }, - )?; - signatures.add_non_root_subintents_v2( - non_root_subintents, - &prepared.non_root_subintent_signatures, - )?; - - let ValidatedPartialTransactionTreeV2 { - overall_validity_range, - root_intent_info, - root_yield_to_parent_count, - non_root_subintents_info, - total_signature_validations, - } = self.validate_transaction_subtree_v2( - &root_subintent.intent_core, - root_subintent.subintent_hash().into(), - non_root_subintents, - signatures, - )?; - - Ok(ValidatedSignedPartialTransactionV2 { - prepared, - total_signature_validations, - overall_validity_range, - root_subintent_info: root_intent_info, - root_subintent_yield_to_parent_count: root_yield_to_parent_count, - non_root_subintents_info, - }) - } - - pub fn validate_transaction_subtree_v2( - &self, - root_intent_core: &PreparedIntentCoreV2, - root_intent_hash: IntentHash, - non_root_subintents: &PreparedNonRootSubintentsV2, - signatures: AllPendingSignatureValidations, - ) -> Result { - let non_root_subintents = non_root_subintents.subintents.as_slice(); - - let intent_relationships = self.validate_intent_relationships_v2( - root_intent_hash, - root_intent_core, - non_root_subintents, - )?; - let (overall_validity_range, root_yield_summary) = self - .validate_v2_intent_cores_and_subintent_connection_counts( - root_intent_hash, - root_intent_core, - non_root_subintents, - &intent_relationships.non_root_subintents, - )?; - - let SignatureValidationSummary { - root_signer_keys, - non_root_signer_keys, - total_signature_validations, - } = signatures.validate_all()?; - - let root_intent_info = ValidatedIntentInformationV2 { - encoded_instructions: manifest_encode(&root_intent_core.instructions.inner.0)?.into(), - children_subintent_indices: intent_relationships.root_intent.children, - signer_keys: root_signer_keys, - }; - let non_root_subintents_info = non_root_subintents - .iter() - .zip(non_root_signer_keys) - .zip(intent_relationships.non_root_subintents.into_values()) - .map( - |((subintent, signer_keys), info)| -> Result<_, TransactionValidationError> { - Ok(ValidatedIntentInformationV2 { - encoded_instructions: manifest_encode( - &subintent.intent_core.instructions.inner.0, - )? - .into(), - signer_keys, - children_subintent_indices: info.children, - }) - }, - ) - .collect::>()?; - - Ok(ValidatedPartialTransactionTreeV2 { - overall_validity_range, - root_intent_info, - root_yield_to_parent_count: root_yield_summary.parent_yields, - non_root_subintents_info, - total_signature_validations, - }) - } - - /// Can be used for both partial and complete trees. - /// - /// This should be run after `validate_intent_relationships_v2`, which creates - /// the subintent details map for you which describes the relationship between - /// different intents. - fn validate_v2_intent_cores_and_subintent_connection_counts( - &self, - root_intent_hash: IntentHash, - root_intent_core: &PreparedIntentCoreV2, - non_root_subintents: &[PreparedSubintentV2], - non_root_subintent_details: &IndexMap, - ) -> Result<(OverallValidityRangeV2, ManifestYieldSummary), TransactionValidationError> { - let mut aggregation = AcrossIntentAggregation::start(); - let mut yield_summaries: IndexMap = - index_map_with_capacity(non_root_subintents.len() + 1); - let root_yield_summary = { - let yield_summary = self - .validate_v2_intent_core( - root_intent_core, - &mut aggregation, - root_intent_hash.is_for_subintent(), - ) - .map_err(|err| { - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::for_root(root_intent_hash), - err, - ) - })?; - yield_summaries.insert(root_intent_hash, yield_summary.clone()); - yield_summary - }; - for (index, subintent) in non_root_subintents.iter().enumerate() { - let subintent_hash = subintent.subintent_hash(); - let yield_summary = self - .validate_v2_intent_core(&subintent.intent_core, &mut aggregation, true) - .map_err(|err| { - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::NonRootSubintent( - SubintentIndex(index), - subintent_hash, - ), - err, - ) - })?; - yield_summaries.insert(subintent_hash.into(), yield_summary); - } - - let overall_validity_range = aggregation.finalize(&self.config)?; - - for (child_hash, child_details) in non_root_subintent_details { - let child_intent_hash = IntentHash::Subintent(*child_hash); - // This checks that the YIELD_TO_PARENTs in a subintent match the YIELD_TO_CHILDS in the parent. - // The instruction validation has already checked that the subintents end with a YIELD_TO_PARENT. - let parent_yield_summary = yield_summaries.get(&child_details.parent).unwrap(); - let parent_yield_child_calls = - *parent_yield_summary.child_yields.get(child_hash).unwrap(); - let child_yield_summary = yield_summaries.get(&child_intent_hash).unwrap(); - let child_yield_parent_calls = child_yield_summary.parent_yields; - if parent_yield_child_calls != child_yield_parent_calls { - return Err( - SubintentStructureError::MismatchingYieldChildAndYieldParentCountsForSubintent - .for_subintent(child_details.index, *child_hash), - ); - } - } - - Ok((overall_validity_range, root_yield_summary)) - } - - // This method is public so it can be used by the toolkit. - pub fn validate_v2_intent_core( - &self, - intent_core: &PreparedIntentCoreV2, - aggregation: &mut AcrossIntentAggregation, - is_subintent: bool, - ) -> Result { - self.validate_intent_header_v2(&intent_core.header.inner, aggregation)?; - self.validate_message_v2(&intent_core.message.inner)?; - aggregation - .record_reference_count(intent_core.instructions.references.len(), &self.config)?; - let yield_summary = self.validate_manifest_v2( - &intent_core.instructions.inner.0, - &intent_core.blobs.blobs_by_hash, - &intent_core.children.children, - is_subintent, - )?; - Ok(yield_summary) - } - - // This method is public so it can be used by the toolkit. - pub fn validate_intent_header_v2( - &self, - header: &IntentHeaderV2, - aggregation: &mut AcrossIntentAggregation, - ) -> Result<(), HeaderValidationError> { - // Network - if let Some(required_network_id) = self.required_network_id { - if header.network_id != required_network_id { - return Err(HeaderValidationError::InvalidNetwork); - } - } - - // Epoch - if header.end_epoch_exclusive <= header.start_epoch_inclusive { - return Err(HeaderValidationError::InvalidEpochRange); - } - let max_end_epoch = header - .start_epoch_inclusive - .after(self.config.max_epoch_range) - .ok_or(HeaderValidationError::InvalidEpochRange)?; - if header.end_epoch_exclusive > max_end_epoch { - return Err(HeaderValidationError::InvalidEpochRange); - } - - match ( - header.min_proposer_timestamp_inclusive.as_ref(), - header.max_proposer_timestamp_exclusive.as_ref(), - ) { - (Some(min_timestamp_inclusive), Some(max_timestamp_exclusive)) => { - if min_timestamp_inclusive >= max_timestamp_exclusive { - return Err(HeaderValidationError::InvalidTimestampRange); - } - } - _ => {} - }; - - aggregation.update_headers( - header.start_epoch_inclusive, - header.end_epoch_exclusive, - header.min_proposer_timestamp_inclusive.as_ref(), - header.max_proposer_timestamp_exclusive.as_ref(), - )?; - - Ok(()) - } - - // This method is public so it can be used by the toolkit. - pub fn validate_message_v2(&self, message: &MessageV2) -> Result<(), InvalidMessageError> { - let validation = &self.config.message_validation; - match message { - MessageV2::None => {} - MessageV2::Plaintext(plaintext_message) => { - let PlaintextMessageV1 { mime_type, message } = plaintext_message; - if mime_type.len() > validation.max_mime_type_length { - return Err(InvalidMessageError::MimeTypeTooLong { - actual: mime_type.len(), - permitted: validation.max_mime_type_length, - }); - } - if message.len() > validation.max_plaintext_message_length { - return Err(InvalidMessageError::PlaintextMessageTooLong { - actual: message.len(), - permitted: validation.max_plaintext_message_length, - }); - } - } - MessageV2::Encrypted(encrypted_message) => { - let EncryptedMessageV2 { - encrypted, - decryptors_by_curve, - } = encrypted_message; - if encrypted.0.len() > validation.max_encrypted_message_length { - return Err(InvalidMessageError::EncryptedMessageTooLong { - actual: encrypted.0.len(), - permitted: validation.max_encrypted_message_length, - }); - } - if decryptors_by_curve.len() == 0 { - return Err(InvalidMessageError::NoDecryptors); - } - let mut total_decryptors = 0; - for (curve_type, decryptors) in decryptors_by_curve.iter() { - if decryptors.curve_type() != *curve_type { - return Err(InvalidMessageError::MismatchingDecryptorCurves { - actual: decryptors.curve_type(), - expected: *curve_type, - }); - } - if decryptors.number_of_decryptors() == 0 { - return Err(InvalidMessageError::NoDecryptorsForCurveType { - curve_type: decryptors.curve_type(), - }); - } - // Can't overflow because decryptor count << size of a transaction < 1MB < usize, - total_decryptors += decryptors.number_of_decryptors(); - } - if total_decryptors > validation.max_decryptors { - return Err(InvalidMessageError::TooManyDecryptors { - actual: total_decryptors, - permitted: validation.max_decryptors, - }); - } - } - } - Ok(()) - } - - // This method is public so it can be used by the toolkit. - /// The `is_subintent` property indicates whether it should be treated as a subintent. - /// A subintent is able to `YIELD_TO_PARENT` and is required to end with a `YIELD_TO_PARENT`. - pub fn validate_manifest_v2( - &self, - instructions: &[InstructionV2], - blobs: &IndexMap>, - children: &IndexSet, - is_subintent: bool, - ) -> Result { - if instructions.len() > self.config.max_instructions { - return Err(ManifestValidationError::TooManyInstructions); - } - impl<'a> ReadableManifestBase - for ( - &'a [InstructionV2], - &'a IndexMap>, - &'a IndexSet, - bool, - ) - { - fn is_subintent(&self) -> bool { - self.3 - } - - fn get_blobs<'b>(&'b self) -> impl Iterator)> { - self.1.iter() - } - - fn get_known_object_names_ref(&self) -> ManifestObjectNamesRef { - ManifestObjectNamesRef::Unknown - } - - fn get_child_subintent_hashes<'b>( - &'b self, - ) -> impl ExactSizeIterator { - self.2.iter() - } - } - impl<'a> TypedReadableManifest - for ( - &'a [InstructionV2], - &'a IndexMap>, - &'a IndexSet, - bool, - ) - { - type Instruction = InstructionV2; - - fn get_typed_instructions(&self) -> &[Self::Instruction] { - self.0 - } - } - let mut yield_summary = ManifestYieldSummary { - parent_yields: 0, - child_yields: children.iter().map(|child| (child.hash, 0)).collect(), - }; - StaticManifestInterpreter::new( - ValidationRuleset::cuttlefish(), - &(instructions, blobs, children, is_subintent), - ) - .validate_and_apply_visitor(&mut yield_summary)?; - Ok(yield_summary) - } - - /// The root intent can be either: - /// * If validating a full transaction: a transaction intent - /// * If validating a partial transaction: a root subintent - fn validate_intent_relationships_v2( - &self, - root_intent_hash: IntentHash, - root_intent_core: &PreparedIntentCoreV2, - non_root_subintents: &[PreparedSubintentV2], - ) -> Result { - let mut root_intent_details = RootIntentRelationshipDetails::default(); - let mut non_root_subintent_details = - IndexMap::::default(); - - // STEP 1 - // ------ - // * We establish that the subintents are unique - // * We create an index from the SubintentHash to SubintentIndex - for (index, subintent) in non_root_subintents.iter().enumerate() { - let subintent_hash = subintent.subintent_hash(); - let index = SubintentIndex(index); - let details = SubintentRelationshipDetails::default_for(index); - if let Some(_) = non_root_subintent_details.insert(subintent_hash, details) { - return Err(SubintentStructureError::DuplicateSubintent - .for_subintent(index, subintent_hash)); - } - } - - // STEP 2 - // ------ - // We establish, for each parent intent, that each of its children: - // * Exist as subintents in the transaction - // * Only is the child of that parent intent and no other - // - // We also: - // * Save the unique parent on each subintent which is a child - // * Save the children of an intent into its intent details - - // STEP 2A - Handle children of the transaction intent - { - let parent_hash = root_intent_hash; - let intent_details = &mut root_intent_details; - for child_subintent_hash in root_intent_core.children.children.iter() { - let child_hash = child_subintent_hash.hash; - let child_subintent_details = non_root_subintent_details - .get_mut(&child_hash) - .ok_or_else(|| { - SubintentStructureError::ChildSubintentNotIncludedInTransaction(child_hash) - .for_unindexed() - })?; - if child_subintent_details.parent == PLACEHOLDER_PARENT { - child_subintent_details.parent = parent_hash; - } else { - return Err(SubintentStructureError::SubintentHasMultipleParents - .for_subintent(child_subintent_details.index, child_hash)); - } - intent_details.children.push(child_subintent_details.index); - } - } - - // STEP 2B - Handle the children of each subintent - for subintent in non_root_subintents.iter() { - let subintent_hash = subintent.subintent_hash(); - let parent_hash: IntentHash = subintent_hash.into(); - let children = &subintent.intent_core.children.children; - let mut children_details = Vec::with_capacity(children.len()); - for child_subintent in children.iter() { - let child_hash = child_subintent.hash; - let child_subintent_details = non_root_subintent_details - .get_mut(&child_hash) - .ok_or_else(|| { - SubintentStructureError::ChildSubintentNotIncludedInTransaction(child_hash) - .for_unindexed() - })?; - if child_subintent_details.parent == PLACEHOLDER_PARENT { - child_subintent_details.parent = parent_hash; - } else { - return Err(SubintentStructureError::SubintentHasMultipleParents - .for_subintent(child_subintent_details.index, child_hash)); - } - children_details.push(child_subintent_details.index); - } - non_root_subintent_details - .get_mut(&subintent_hash) - .unwrap() - .children = children_details; - } - - // STEP 3 - // ------ - // We traverse the child relationships from the root, and mark a depth. - // We error if any exceed the maximum depth. - // - // As each child has at most one parent, we can guarantee the work is bounded - // by the total number of subintents. - let mut work_list = vec![]; - for index in root_intent_details.children.iter() { - work_list.push((*index, 1)); - } - - let max_depth = if root_intent_hash.is_for_subintent() { - self.config.max_subintent_depth - 1 - } else { - self.config.max_subintent_depth - }; - - loop { - let Some((index, depth)) = work_list.pop() else { - break; - }; - if depth > max_depth { - let (hash, _) = non_root_subintent_details.get_index(index.0).unwrap(); - return Err( - SubintentStructureError::SubintentExceedsMaxDepth.for_subintent(index, *hash) - ); - } - let (_, subintent_details) = non_root_subintent_details.get_index_mut(index.0).unwrap(); - subintent_details.depth = depth; - for index in subintent_details.children.iter() { - work_list.push((*index, depth + 1)); - } - } - - // STEP 4 - // ------ - // We check that every subintent has a marked "depth from root". - // - // Combined with step 2 and step 3, we now have that: - // * Every subintent has a unique parent. - // * Every subintent is reachable from the root. - // - // Therefore there is a unique path from every subintent to the root - // So we have confirmed the subintents form a tree. - for (hash, details) in non_root_subintent_details.iter() { - if details.depth == 0 { - return Err( - SubintentStructureError::SubintentIsNotReachableFromTheTransactionIntent - .for_subintent(details.index, *hash), - ); - } - } - - Ok(IntentRelationships { - root_intent: root_intent_details, - non_root_subintents: non_root_subintent_details, - }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum TransactionVersion { - V1, - V2, -} - -// This type is public so it can be used by the toolkit. -#[must_use] -pub struct AcrossIntentAggregation { - total_reference_count: usize, - overall_start_epoch_inclusive: Epoch, - overall_end_epoch_exclusive: Epoch, - overall_start_timestamp_inclusive: Option, - overall_end_timestamp_exclusive: Option, -} - -impl AcrossIntentAggregation { - fn start() -> Self { - Self { - total_reference_count: 0, - overall_start_epoch_inclusive: Epoch::zero(), - overall_end_epoch_exclusive: Epoch::of(u64::MAX), - overall_start_timestamp_inclusive: None, - overall_end_timestamp_exclusive: None, - } - } - - fn finalize( - self, - config: &TransactionValidationConfig, - ) -> Result { - if self.total_reference_count > config.max_total_references { - return Err(TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::AcrossTransaction, - IntentValidationError::TooManyReferences { - total: self.total_reference_count, - limit: config.max_total_references, - }, - )); - } - Ok(OverallValidityRangeV2 { - epoch_range: EpochRange { - start_epoch_inclusive: self.overall_start_epoch_inclusive, - end_epoch_exclusive: self.overall_end_epoch_exclusive, - }, - proposer_timestamp_range: ProposerTimestampRange { - start_timestamp_inclusive: self.overall_start_timestamp_inclusive, - end_timestamp_exclusive: self.overall_end_timestamp_exclusive, - }, - }) - } - - fn record_reference_count( - &mut self, - count: usize, - config: &TransactionValidationConfig, - ) -> Result<(), IntentValidationError> { - if count > config.max_references_per_intent { - return Err(IntentValidationError::TooManyReferences { - total: count, - limit: config.max_references_per_intent, - }); - } - self.total_reference_count = self.total_reference_count.saturating_add(count); - Ok(()) - } - - fn update_headers( - &mut self, - start_epoch_inclusive: Epoch, - end_epoch_exclusive: Epoch, - start_timestamp_inclusive: Option<&Instant>, - end_timestamp_exclusive: Option<&Instant>, - ) -> Result<(), HeaderValidationError> { - if start_epoch_inclusive > self.overall_start_epoch_inclusive { - self.overall_start_epoch_inclusive = start_epoch_inclusive; - } - if end_epoch_exclusive < self.overall_end_epoch_exclusive { - self.overall_end_epoch_exclusive = end_epoch_exclusive; - } - if self.overall_start_epoch_inclusive >= self.overall_end_epoch_exclusive { - return Err(HeaderValidationError::NoValidEpochRangeAcrossAllIntents); - } - if let Some(start_timestamp_inclusive) = start_timestamp_inclusive { - if self.overall_start_timestamp_inclusive.is_none() - || self - .overall_start_timestamp_inclusive - .as_ref() - .is_some_and(|t| start_timestamp_inclusive > t) - { - self.overall_start_timestamp_inclusive = Some(*start_timestamp_inclusive); - } - } - if let Some(end_timestamp_exclusive) = end_timestamp_exclusive { - if self.overall_end_timestamp_exclusive.is_none() - || self - .overall_end_timestamp_exclusive - .as_ref() - .is_some_and(|t| end_timestamp_exclusive < t) - { - self.overall_end_timestamp_exclusive = Some(*end_timestamp_exclusive); - } - } - match ( - self.overall_start_timestamp_inclusive.as_ref(), - self.overall_end_timestamp_exclusive.as_ref(), - ) { - (Some(start_inclusive), Some(end_exclusive)) => { - if start_inclusive >= end_exclusive { - return Err(HeaderValidationError::NoValidTimestampRangeAcrossAllIntents); - } - } - _ => {} - } - Ok(()) - } -} - -pub struct AllPendingSignatureValidations<'a> { - transaction_version: TransactionVersion, - config: &'a TransactionValidationConfig, - root: ( - PendingIntentSignatureValidations<'a>, - TransactionValidationErrorLocation, - ), - non_roots: Vec<( - PendingIntentSignatureValidations<'a>, - TransactionValidationErrorLocation, - )>, - total_signature_validations: usize, -} - -pub struct SignatureValidationSummary { - root_signer_keys: IndexSet, - non_root_signer_keys: Vec>, - total_signature_validations: usize, -} - -impl<'a> AllPendingSignatureValidations<'a> { - fn new_with_root( - transaction_version: TransactionVersion, - config: &'a TransactionValidationConfig, - root_intent_hash: IntentHash, - signatures: PendingIntentSignatureValidations<'a>, - ) -> Result { - let intent_signature_validations = signatures.intent_signature_validations(); - let error_location = TransactionValidationErrorLocation::for_root(root_intent_hash); - if intent_signature_validations > config.max_signer_signatures_per_intent { - return Err(TransactionValidationError::SignatureValidationError( - error_location, - SignatureValidationError::TooManySignatures { - total: intent_signature_validations, - limit: config.max_signer_signatures_per_intent, - }, - )); - } - let notary_signature_validations = signatures.notary_signature_validations(); - - Ok(Self { - transaction_version, - config, - root: (signatures, error_location), - non_roots: Default::default(), - total_signature_validations: intent_signature_validations - + notary_signature_validations, - }) - } - - pub fn add_non_root_subintents_v2( - &mut self, - non_root_subintents: &PreparedNonRootSubintentsV2, - signatures: &'a PreparedNonRootSubintentSignaturesV2, - ) -> Result<(), TransactionValidationError> { - let non_root_subintents = &non_root_subintents.subintents; - let non_root_subintent_signatures = &signatures.by_subintent; - if non_root_subintents.len() != non_root_subintent_signatures.len() { - return Err( - SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches - .located(TransactionValidationErrorLocation::AcrossTransaction), - ); - } - for (index, (subintent, signatures)) in non_root_subintents - .iter() - .zip(non_root_subintent_signatures) - .enumerate() - { - self.add_non_root( - SubintentIndex(index), - subintent.subintent_hash(), - PendingIntentSignatureValidations::Subintent { - intent_signatures: &signatures.inner.signatures, - signed_hash: subintent.subintent_hash(), - }, - )?; - } - Ok(()) - } - - pub fn add_non_root_preview_subintents_v2( - &mut self, - non_root_subintents: &PreparedNonRootSubintentsV2, - non_root_subintent_signers: &'a Vec>, - ) -> Result<(), TransactionValidationError> { - let non_root_subintents = &non_root_subintents.subintents; - if non_root_subintents.len() != non_root_subintent_signers.len() { - return Err( - SignatureValidationError::IncorrectNumberOfSubintentSignatureBatches - .located(TransactionValidationErrorLocation::AcrossTransaction), - ); - } - for (index, (subintent, signers)) in non_root_subintents - .iter() - .zip(non_root_subintent_signers) - .enumerate() - { - self.add_non_root( - SubintentIndex(index), - subintent.subintent_hash(), - PendingIntentSignatureValidations::PreviewSubintent { - intent_public_keys: signers, - }, - )?; - } - Ok(()) - } - - fn add_non_root( - &mut self, - subintent_index: SubintentIndex, - subintent_hash: SubintentHash, - signatures: PendingIntentSignatureValidations<'a>, - ) -> Result<(), TransactionValidationError> { - let intent_signature_validations = signatures.intent_signature_validations(); - let error_location = - TransactionValidationErrorLocation::NonRootSubintent(subintent_index, subintent_hash); - if intent_signature_validations > self.config.max_signer_signatures_per_intent { - return Err(TransactionValidationError::SignatureValidationError( - error_location, - SignatureValidationError::TooManySignatures { - total: intent_signature_validations, - limit: self.config.max_signer_signatures_per_intent, - }, - )); - } - - self.non_roots.push((signatures, error_location)); - self.total_signature_validations += intent_signature_validations; - Ok(()) - } - - pub fn validate_all(self) -> Result { - if self.total_signature_validations > self.config.max_total_signature_validations { - return Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::AcrossTransaction, - SignatureValidationError::TooManySignatures { - total: self.total_signature_validations, - limit: self.config.max_total_signature_validations, - }, - )); - } - let config = self.config; - let transaction_version = self.transaction_version; - let root_signer_keys = Self::validate_signatures(self.root.0, config, transaction_version) - .map_err(|err| { - TransactionValidationError::SignatureValidationError(self.root.1, err) - })?; - - let non_root_signer_keys = self - .non_roots - .into_iter() - .map(|non_root| { - Self::validate_signatures(non_root.0, config, transaction_version).map_err(|err| { - TransactionValidationError::SignatureValidationError(non_root.1, err) - }) - }) - .collect::>()?; - - Ok(SignatureValidationSummary { - root_signer_keys, - non_root_signer_keys, - total_signature_validations: self.total_signature_validations, - }) - } - - fn validate_signatures( - signatures: PendingIntentSignatureValidations, - config: &TransactionValidationConfig, - transaction_version: TransactionVersion, - ) -> Result, SignatureValidationError> { - let public_keys = match signatures { - PendingIntentSignatureValidations::TransactionIntent { - notary_is_signatory, - notary_public_key, - notary_signature, - notarized_hash, - intent_signatures, - signed_hash, - } => { - let mut intent_public_keys: IndexSet = Default::default(); - for signature in intent_signatures { - let public_key = verify_and_recover(signed_hash.as_hash(), &signature.0) - .ok_or(SignatureValidationError::InvalidIntentSignature)?; - - if !intent_public_keys.insert(public_key) { - return Err(SignatureValidationError::DuplicateSigner); - } - } - - if !verify( - notarized_hash.as_hash(), - ¬ary_public_key, - ¬ary_signature, - ) { - return Err(SignatureValidationError::InvalidNotarySignature); - } - - if notary_is_signatory { - if !intent_public_keys.insert(notary_public_key) - && !config.allow_notary_to_duplicate_signer(transaction_version) - { - return Err( - SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner, - ); - } - } - - intent_public_keys - } - PendingIntentSignatureValidations::PreviewTransactionIntent { - notary_is_signatory, - notary_public_key, - intent_public_keys, - } => { - let mut checked_intent_public_keys: IndexSet = Default::default(); - for key in intent_public_keys { - if !checked_intent_public_keys.insert(key.clone()) { - return Err(SignatureValidationError::DuplicateSigner); - } - } - if notary_is_signatory { - if !checked_intent_public_keys.insert(notary_public_key) - && !config.allow_notary_to_duplicate_signer(transaction_version) - { - return Err( - SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner, - ); - } - } - checked_intent_public_keys - } - PendingIntentSignatureValidations::Subintent { - intent_signatures, - signed_hash, - } => { - let mut intent_public_keys: IndexSet = Default::default(); - for signature in intent_signatures { - let public_key = verify_and_recover(signed_hash.as_hash(), &signature.0) - .ok_or(SignatureValidationError::InvalidIntentSignature)?; - - if !intent_public_keys.insert(public_key) { - return Err(SignatureValidationError::DuplicateSigner); - } - } - intent_public_keys - } - PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys } => { - let mut checked_intent_public_keys: IndexSet = Default::default(); - for key in intent_public_keys { - if !checked_intent_public_keys.insert(key.clone()) { - return Err(SignatureValidationError::DuplicateSigner); - } - } - checked_intent_public_keys - } - }; - - Ok(public_keys) - } -} - -/// This can assume that the signature counts are within checked limits, -/// so calculations cannot overflow. -enum PendingIntentSignatureValidations<'a> { - TransactionIntent { - notary_is_signatory: bool, - notary_public_key: PublicKey, - notary_signature: SignatureV1, - notarized_hash: SignedTransactionIntentHash, - intent_signatures: &'a [IntentSignatureV1], - signed_hash: TransactionIntentHash, - }, - PreviewTransactionIntent { - notary_is_signatory: bool, - notary_public_key: PublicKey, - intent_public_keys: &'a [PublicKey], - }, - Subintent { - intent_signatures: &'a [IntentSignatureV1], - signed_hash: SubintentHash, - }, - PreviewSubintent { - intent_public_keys: &'a [PublicKey], - }, -} - -impl<'a> PendingIntentSignatureValidations<'a> { - fn intent_signature_validations(&self) -> usize { - match self { - PendingIntentSignatureValidations::TransactionIntent { - intent_signatures, .. - } => intent_signatures.len(), - PendingIntentSignatureValidations::PreviewTransactionIntent { - intent_public_keys, - .. - } => intent_public_keys.len(), - PendingIntentSignatureValidations::Subintent { - intent_signatures, .. - } => intent_signatures.len(), - PendingIntentSignatureValidations::PreviewSubintent { intent_public_keys } => { - intent_public_keys.len() - } - } - } - - fn notary_signature_validations(&self) -> usize { - match self { - PendingIntentSignatureValidations::TransactionIntent { .. } - | PendingIntentSignatureValidations::PreviewTransactionIntent { .. } => 1, - PendingIntentSignatureValidations::Subintent { .. } - | PendingIntentSignatureValidations::PreviewSubintent { .. } => 0, - } - } -} - -// This type is public so it can be used by the toolkit. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ManifestYieldSummary { - parent_yields: usize, - child_yields: IndexMap, -} - -impl ManifestInterpretationVisitor for ManifestYieldSummary { - type Output = ManifestValidationError; - - fn on_end_instruction(&mut self, details: OnEndInstruction) -> ControlFlow { - // Safe from overflow due to checking max instruction count - match details.effect { - ManifestInstructionEffect::Invocation { - kind: InvocationKind::YieldToParent, - .. - } => { - self.parent_yields += 1; - } - ManifestInstructionEffect::Invocation { - kind: - InvocationKind::YieldToChild { - child_index: ManifestNamedIntent(index), - }, - .. - } => { - let index = index as usize; - - // This should exist because we are handling this after the instruction, - // so the interpreter should have errored with ChildIntentNotRegistered - // if the child yield was invalid. - let (_, count) = self.child_yields.get_index_mut(index).unwrap(); - *count += 1; - } - _ => {} - } - ControlFlow::Continue(()) - } -} - -struct IntentRelationships { - pub root_intent: RootIntentRelationshipDetails, - pub non_root_subintents: IndexMap, -} -#[derive(Default)] -pub struct RootIntentRelationshipDetails { - children: Vec, -} - -pub struct SubintentRelationshipDetails { - index: SubintentIndex, - parent: IntentHash, - depth: usize, - children: Vec, } -impl SubintentRelationshipDetails { - fn default_for(index: SubintentIndex) -> Self { - Self { - index, - parent: PLACEHOLDER_PARENT, - depth: Default::default(), - children: Default::default(), - } - } -} - -const PLACEHOLDER_PARENT: IntentHash = - IntentHash::Transaction(TransactionIntentHash(Hash([0u8; Hash::LENGTH]))); - -#[cfg(test)] -mod tests { - use std::ops::AddAssign; - - use radix_common::network::NetworkDefinition; - - use super::*; - use crate::{builder::ManifestBuilder, builder::TransactionBuilder}; - - macro_rules! assert_invalid_tx { - ($result: pat, ($start_epoch: expr, $end_epoch: expr, $nonce: expr, $signers: expr, $notary: expr)) => {{ - let validator = TransactionValidator::new_for_latest_simulator(); - assert_matches!( - create_transaction($start_epoch, $end_epoch, $nonce, $signers, $notary) - .prepare_and_validate(&validator) - .expect_err("Should be an error"), - $result, - ); - }}; - } - - #[test] - fn test_invalid_header() { - assert_invalid_tx!( - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - IntentValidationError::HeaderValidationError( - HeaderValidationError::InvalidEpochRange - ), - ), - (Epoch::zero(), Epoch::zero(), 5, vec![1], 2) - ); - assert_invalid_tx!( - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - IntentValidationError::HeaderValidationError( - HeaderValidationError::InvalidEpochRange - ), - ), - ( - Epoch::zero(), - Epoch::of(TransactionValidationConfig::latest().max_epoch_range + 1), - 5, - vec![1], - 2 - ) - ); - } - - #[test] - fn test_epoch_overflow() { - assert_invalid_tx!( - TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - IntentValidationError::HeaderValidationError( - HeaderValidationError::InvalidEpochRange - ), - ), - (Epoch::of(u64::MAX - 5), Epoch::of(u64::MAX), 5, vec![1], 2) - ); - } - - #[test] - fn test_too_many_signatures() { - assert_invalid_tx!( - TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::TooManySignatures { - total: 19, - limit: 16, - } - ), - (Epoch::zero(), Epoch::of(100), 5, (1..20).collect(), 2) - ); - } - - #[test] - fn test_duplicate_signers() { - assert_invalid_tx!( - TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::DuplicateSigner - ), - (Epoch::zero(), Epoch::of(100), 5, vec![1, 1], 2) - ); - } - - #[test] - fn test_valid_preview() { - // Build the whole transaction but only really care about the intent - let tx = create_transaction(Epoch::zero(), Epoch::of(100), 5, vec![1, 2], 2); - - let validator = TransactionValidator::new_for_latest_simulator(); - - let preview_intent = PreviewIntentV1 { - intent: tx.signed_intent.intent, - signer_public_keys: Vec::new(), - flags: PreviewFlags { - use_free_credit: true, - assume_all_signature_proofs: false, - skip_epoch_check: false, - disable_auth: false, - }, - }; - - let result = validator.validate_preview_intent_v1(preview_intent); - - assert!(result.is_ok()); - } - - #[test] - fn test_valid_messages() { - // None - { - let message = MessageV1::None; - let result = validate_default(&create_transaction_with_message(message)); - assert!(result.is_ok()); - } - // Plaintext - { - let message = MessageV1::Plaintext(PlaintextMessageV1 { - mime_type: "text/plain".to_owned(), - message: MessageContentsV1::String("Hello world!".to_string()), - }); - let result = validate_default(&create_transaction_with_message(message)); - assert!(result.is_ok()); - } - // Encrypted - { - // Note - this isn't actually a validly encrypted message, - // this just shows that a sufficiently valid encrypted message can pass validation - let message = MessageV1::Encrypted(EncryptedMessageV1 { - encrypted: AesGcmPayload(vec![]), - decryptors_by_curve: indexmap!( - CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { - dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), - decryptors: indexmap!( - PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), - ), - }, - CurveType::Secp256k1 => DecryptorsByCurve::Secp256k1 { - dh_ephemeral_public_key: Secp256k1PublicKey([0; Secp256k1PublicKey::LENGTH]), - decryptors: indexmap!( - PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), - PublicKeyFingerprint([1; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), - ), - }, - ), - }); - let result = validate_default(&create_transaction_with_message(message)); - assert!(result.is_ok()); - } - } - - #[test] - fn test_invalid_message_errors() { - // MimeTypeTooLong - { - let message = MessageV1::Plaintext(PlaintextMessageV1 { - mime_type: "very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, ".to_owned(), - message: MessageContentsV1::String("Hello".to_string()), - }); - let error = - validate_default_expecting_message_error(&create_transaction_with_message(message)); - assert_matches!(error, InvalidMessageError::MimeTypeTooLong { .. }) - } - - // PlaintextMessageTooLong - { - let mut long_message: String = "".to_owned(); - while long_message.len() <= 2048 { - long_message.push_str("more text please!"); - } - let message = MessageV1::Plaintext(PlaintextMessageV1 { - mime_type: "text/plain".to_owned(), - message: MessageContentsV1::String(long_message), - }); - let error = - validate_default_expecting_message_error(&create_transaction_with_message(message)); - assert_matches!(error, InvalidMessageError::PlaintextMessageTooLong { .. }) - } - - // EncryptedMessageTooLong - { - let mut message_which_is_too_long: String = "".to_owned(); - while message_which_is_too_long.len() <= 2048 + 50 { - // Some more bytes for the AES padding - message_which_is_too_long.push_str("more text please!"); - } - let message = MessageV1::Encrypted(EncryptedMessageV1 { - encrypted: AesGcmPayload(message_which_is_too_long.as_bytes().to_vec()), - decryptors_by_curve: indexmap!( - CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { - dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), - decryptors: indexmap!( - PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), - ), - } - ), - }); - let error = - validate_default_expecting_message_error(&create_transaction_with_message(message)); - assert_matches!(error, InvalidMessageError::EncryptedMessageTooLong { .. }) - } - - // NoDecryptors - { - let message = MessageV1::Encrypted(EncryptedMessageV1 { - encrypted: AesGcmPayload(vec![]), - decryptors_by_curve: indexmap!(), - }); - let error = - validate_default_expecting_message_error(&create_transaction_with_message(message)); - assert_matches!(error, InvalidMessageError::NoDecryptors) - } - - // NoDecryptorsForCurveType - { - let message = MessageV1::Encrypted(EncryptedMessageV1 { - encrypted: AesGcmPayload(vec![]), - decryptors_by_curve: indexmap!( - CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { - dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), - decryptors: indexmap!(), - } - ), - }); - let error = - validate_default_expecting_message_error(&create_transaction_with_message(message)); - assert_matches!( - error, - InvalidMessageError::NoDecryptorsForCurveType { - curve_type: CurveType::Ed25519 - } - ) - } - - // MismatchingDecryptorCurves - { - let message = MessageV1::Encrypted(EncryptedMessageV1 { - encrypted: AesGcmPayload(vec![]), - decryptors_by_curve: indexmap!( - CurveType::Ed25519 => DecryptorsByCurve::Secp256k1 { - dh_ephemeral_public_key: Secp256k1PublicKey([0; Secp256k1PublicKey::LENGTH]), - decryptors: indexmap!( - PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), - ), - } - ), - }); - let error = - validate_default_expecting_message_error(&create_transaction_with_message(message)); - assert_matches!( - error, - InvalidMessageError::MismatchingDecryptorCurves { - actual: CurveType::Secp256k1, - expected: CurveType::Ed25519 - } - ) - } - - // TooManyDecryptors - { - let mut decryptors = IndexMap::::default(); - for i in 0..30 { - decryptors.insert( - PublicKeyFingerprint([0, 0, 0, 0, 0, 0, 0, i as u8]), - AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), - ); - } - let message = MessageV1::Encrypted(EncryptedMessageV1 { - encrypted: AesGcmPayload(vec![]), - decryptors_by_curve: indexmap!( - CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { - dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), - decryptors, - } - ), - }); - let error = - validate_default_expecting_message_error(&create_transaction_with_message(message)); - assert_matches!( - error, - InvalidMessageError::TooManyDecryptors { - actual: 30, - permitted: 20 - } - ) - } - } - - #[test] - fn test_demonstrate_behaviour_with_notary_duplicating_signer() { - // Arrange - let network = NetworkDefinition::simulator(); - let notary = Secp256k1PrivateKey::from_u64(1).unwrap(); - - let babylon_validator = TransactionValidator::new_with_static_config( - TransactionValidationConfig::babylon(), - network.id, - ); - let latest_validator = TransactionValidator::new_with_static_config( - TransactionValidationConfig::latest(), - network.id, - ); - - let transaction_v1 = TransactionBuilder::new() - .header(TransactionHeaderV1 { - network_id: network.id, - start_epoch_inclusive: Epoch::of(1), - end_epoch_exclusive: Epoch::of(10), - nonce: 0, - notary_public_key: notary.public_key().into(), - notary_is_signatory: true, - tip_percentage: 0, - }) - .manifest(ManifestBuilder::new().drop_auth_zone_proofs().build()) - .sign(¬ary) - .notarize(¬ary) - .build(); - - let transaction_v2 = TransactionV2Builder::new() - .intent_header(IntentHeaderV2 { - network_id: network.id, - start_epoch_inclusive: Epoch::of(1), - end_epoch_exclusive: Epoch::of(10), - min_proposer_timestamp_inclusive: None, - max_proposer_timestamp_exclusive: None, - intent_discriminator: 0, - }) - .transaction_header(TransactionHeaderV2 { - notary_public_key: notary.public_key().into(), - notary_is_signatory: true, - tip_basis_points: 0, - }) - .manifest(ManifestBuilder::new_v2().drop_auth_zone_proofs().build()) - .sign(¬ary) - .notarize(¬ary) - .build_minimal_no_validate(); - - // Act & Assert - Transaction V1 permits using notary as signatory and also having it sign - // It was deemed that we didn't want to start failing V1 transactions for this at Cuttlefish - // as we didn't want existing integrations to break. - assert!(transaction_v1 - .prepare_and_validate(&babylon_validator) - .is_ok()); - assert!(transaction_v1 - .prepare_and_validate(&latest_validator) - .is_ok()); - - // Act & Assert - Transaction V2 does not permit duplicating a notary is signatory as a signatory - assert_matches!( - transaction_v2.prepare_and_validate(&babylon_validator), - Err(TransactionValidationError::PrepareError( - PrepareError::TransactionTypeNotSupported - )) - ); - assert_matches!( - transaction_v2.prepare_and_validate(&latest_validator), - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::NotaryIsSignatorySoShouldNotAlsoBeASigner - )), - ); - } - - struct FakeSigner<'a, S: Signer> { - signer: &'a S, - } - - impl<'a, S: Signer> FakeSigner<'a, S> { - fn new(signer: &'a S) -> Self { - Self { signer } - } - } - - impl<'a, S: Signer> Signer for FakeSigner<'a, S> { - fn public_key(&self) -> PublicKey { - self.signer.public_key().into() - } - - fn sign_without_public_key(&self, message_hash: &impl IsHash) -> SignatureV1 { - let mut signature = self.signer.sign_without_public_key(message_hash); - match &mut signature { - SignatureV1::Secp256k1(inner_signature) => inner_signature.0[5].add_assign(1), - SignatureV1::Ed25519(inner_signature) => inner_signature.0[5].add_assign(1), - } - signature - } - - fn sign_with_public_key(&self, message_hash: &impl IsHash) -> SignatureWithPublicKeyV1 { - let mut signature = self.signer.sign_with_public_key(message_hash); - match &mut signature { - SignatureWithPublicKeyV1::Secp256k1 { signature } => signature.0[5].add_assign(1), - SignatureWithPublicKeyV1::Ed25519 { - signature, - public_key: _, - } => signature.0[5].add_assign(1), - } - signature - } - } - - #[test] - fn test_invalid_signatures() { - let network = NetworkDefinition::simulator(); - - let validator = TransactionValidator::new_with_static_config( - TransactionValidationConfig::latest(), - network.id, - ); - - let versions_to_test = [TransactionVersion::V1, TransactionVersion::V2]; - - fn validate_transaction( - validator: &TransactionValidator, - version: TransactionVersion, - signer: &impl Signer, - notary: &impl Signer, - ) -> Result, TransactionValidationError> { - let signer_keys = match version { - TransactionVersion::V1 => { - unsigned_v1_builder(notary.public_key().into()) - .sign(signer) - .notarize(notary) - .build() - .prepare_and_validate(validator)? - .signer_keys - } - TransactionVersion::V2 => { - unsigned_v2_builder(notary.public_key().into()) - .sign(signer) - .notarize(notary) - .build_minimal_no_validate() - .prepare_and_validate(validator)? - .transaction_intent_info - .signer_keys - } - }; - Ok(signer_keys) - } - - { - // Test Secp256k1 - let notary = Secp256k1PrivateKey::from_u64(1).unwrap(); - let signer = Secp256k1PrivateKey::from_u64(13).unwrap(); - for version in versions_to_test { - assert_matches!( - validate_transaction(&validator, version, &signer, ¬ary), - Ok(signer_keys) => { - assert_eq!(signer_keys.len(), 1); - assert_eq!(signer_keys[0], signer.public_key().into()); - } - ); - match validate_transaction(&validator, version, &FakeSigner::new(&signer), ¬ary) - { - // Coincidentally, between V1 and V2 we hit both cases below - Ok(signer_keys) => { - // NOTE: Because we recover our Secp256k1 public keys, by mutating the signature - // we might have stumbled on a valid signature for a different key - but that's okay. - // As long as we can't fake the signature of a particular key, that's okay. - assert_eq!(signer_keys.len(), 1); - assert_ne!(signer_keys[0], signer.public_key().into()); - } - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::InvalidIntentSignature, - )) => {} - other_result => { - panic!("Unexpected result: {other_result:?}"); - } - } - assert_matches!( - validate_transaction(&validator, version, &signer, &FakeSigner::new(¬ary)), - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::InvalidNotarySignature - )) - ); - } - } - - { - // Test Ed25519 - let notary = Ed25519PrivateKey::from_u64(1).unwrap(); - let signer = Ed25519PrivateKey::from_u64(13).unwrap(); - for version in versions_to_test { - assert_matches!( - validate_transaction(&validator, version, &signer, ¬ary), - Ok(signer_keys) => { - assert_eq!(signer_keys.len(), 1); - assert_eq!(signer_keys[0], signer.public_key().into()); - } - ); - assert_matches!( - validate_transaction(&validator, version, &FakeSigner::new(&signer), ¬ary), - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::InvalidIntentSignature - )) - ); - assert_matches!( - validate_transaction(&validator, version, &signer, &FakeSigner::new(¬ary)), - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::InvalidNotarySignature - )) - ); - } - } - } - - fn validate_default_expecting_message_error( - transaction: &NotarizedTransactionV1, - ) -> InvalidMessageError { - match validate_default(transaction).expect_err("Expected validation error") { - TransactionValidationError::IntentValidationError( - _, - IntentValidationError::InvalidMessage(error), - ) => error, - error => { - panic!("Expected InvalidMessage error, got: {:?}", error) - } - } - } - - fn validate_default( - transaction: &NotarizedTransactionV1, - ) -> Result<(), TransactionValidationError> { - let validator = TransactionValidator::new_for_latest_simulator(); - transaction.prepare_and_validate(&validator).map(|_| ()) - } - - fn unsigned_v1_builder(notary_public_key: PublicKey) -> TransactionV1Builder { - TransactionBuilder::new() - .header(TransactionHeaderV1 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: Epoch::of(1), - end_epoch_exclusive: Epoch::of(10), - nonce: 0, - notary_public_key, - notary_is_signatory: false, - tip_percentage: 5, - }) - .manifest(ManifestBuilder::new().drop_auth_zone_proofs().build()) - } - - fn unsigned_v2_builder(notary_public_key: PublicKey) -> TransactionV2Builder { - TransactionBuilder::new_v2() - .transaction_header(TransactionHeaderV2 { - notary_public_key, - notary_is_signatory: false, - tip_basis_points: 5, - }) - .intent_header(IntentHeaderV2 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: Epoch::of(1), - end_epoch_exclusive: Epoch::of(10), - min_proposer_timestamp_inclusive: None, - max_proposer_timestamp_exclusive: None, - intent_discriminator: 0, - }) - .manifest(ManifestBuilder::new_v2().drop_auth_zone_proofs().build()) - } - - fn create_transaction_with_message(message: MessageV1) -> NotarizedTransactionV1 { - let sk_notary = Secp256k1PrivateKey::from_u64(1).unwrap(); - - let mut builder = TransactionBuilder::new() - .header(TransactionHeaderV1 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: Epoch::of(1), - end_epoch_exclusive: Epoch::of(10), - nonce: 0, - notary_public_key: sk_notary.public_key().into(), - notary_is_signatory: false, - tip_percentage: 5, - }) - .manifest(ManifestBuilder::new().drop_auth_zone_proofs().build()) - .message(message); - - builder = builder.notarize(&sk_notary); - - builder.build() - } - - fn create_transaction( - start_epoch: Epoch, - end_epoch: Epoch, - nonce: u32, - signers: Vec, - notary: u64, - ) -> NotarizedTransactionV1 { - create_transaction_advanced( - start_epoch, - end_epoch, - nonce, - signers, - notary, - ManifestBuilder::new().drop_auth_zone_proofs().build(), - ) - } - - fn create_transaction_advanced( - start_epoch: Epoch, - end_epoch: Epoch, - nonce: u32, - signers: Vec, - notary: u64, - manifest: TransactionManifestV1, - ) -> NotarizedTransactionV1 { - let sk_notary = Secp256k1PrivateKey::from_u64(notary).unwrap(); - - let mut builder = TransactionBuilder::new() - .header(TransactionHeaderV1 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: start_epoch, - end_epoch_exclusive: end_epoch, - nonce, - notary_public_key: sk_notary.public_key().into(), - notary_is_signatory: false, - tip_percentage: 5, - }) - .manifest(manifest); - - for signer in signers { - builder = builder.sign(&Secp256k1PrivateKey::from_u64(signer).unwrap()); - } - builder = builder.notarize(&sk_notary); - - builder.build() - } - - #[test] - fn test_drop_bucket_before_proof() { - let transaction = create_transaction_advanced( - Epoch::of(0), - Epoch::of(40), - 123, - vec![55], - 66, - ManifestBuilder::new() - .take_from_worktop(XRD, dec!(100), "bucket") - .create_proof_from_bucket_of_amount("bucket", dec!(5), "proof1") - .return_to_worktop("bucket") - .drop_proof("proof1") - .build_no_validate(), - ); - let validator = TransactionValidator::new_for_latest_simulator(); - assert_matches!( - transaction.prepare_and_validate(&validator), - Err(TransactionValidationError::IntentValidationError( - _, - IntentValidationError::ManifestValidationError( - ManifestValidationError::BucketConsumedWhilstLockedByProof( - ManifestBucket(0), - _, - ) - ) - )) - ); - } - - #[test] - fn test_clone_invalid_proof() { - let transaction = create_transaction_advanced( - Epoch::of(0), - Epoch::of(40), - 123, - vec![55], - 66, - #[allow(deprecated)] - ManifestBuilder::new() - .take_from_worktop(XRD, dec!(100), "bucket") - .create_proof_from_bucket_of_amount("bucket", dec!(5), "proof1") - .then(|builder| { - let lookup = builder.name_lookup(); - let proof_id = lookup.proof("proof1"); - - builder - .drop_proof("proof1") - .return_to_worktop("bucket") - .add_raw_instruction_ignoring_all_side_effects(CloneProof { proof_id }) - }) - .build_no_validate(), - ); - let validator = TransactionValidator::new_for_latest_simulator(); - assert_matches!( - transaction.prepare_and_validate(&validator), - Err(TransactionValidationError::IntentValidationError( - _, - IntentValidationError::ManifestValidationError( - ManifestValidationError::ProofAlreadyUsed(ManifestProof(0), _,) - ) - )) - ); - } - - #[test] - fn verify_call_direct_method_args_are_processed() { - let transaction = create_transaction_advanced( - Epoch::of(0), - Epoch::of(40), - 123, - vec![55], - 66, - ManifestBuilder::new() - .take_from_worktop(XRD, dec!(100), "bucket") - .then(|builder| { - let lookup = builder.name_lookup(); - builder - .call_direct_access_method( - InternalAddress::new_or_panic( - [EntityType::InternalFungibleVault as u8; NodeId::LENGTH], - ), - "test", - manifest_args!(lookup.bucket("bucket")), - ) - .return_to_worktop("bucket") - }) - .build_no_validate(), - ); - let validator = TransactionValidator::new_for_latest_simulator(); - assert_matches!( - transaction.prepare_and_validate(&validator), - Err(TransactionValidationError::IntentValidationError( - _, - IntentValidationError::ManifestValidationError( - ManifestValidationError::BucketAlreadyUsed(ManifestBucket(0), _,) - ) - )) - ); - } - - #[test] - fn too_many_signatures_should_be_rejected() { - fn create_partial_transaction( - subintent_index: usize, - num_signatures: usize, - ) -> SignedPartialTransactionV2 { - let mut builder = PartialTransactionV2Builder::new() - .intent_header(IntentHeaderV2 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: Epoch::of(0), - end_epoch_exclusive: Epoch::of(1), - min_proposer_timestamp_inclusive: None, - max_proposer_timestamp_exclusive: None, - intent_discriminator: subintent_index as u64, - }) - .manifest_builder(|builder| builder.yield_to_parent(())); - - for i in 0..num_signatures { - let signer = - Secp256k1PrivateKey::from_u64(((subintent_index + 1) * 1000 + i) as u64) - .unwrap(); - builder = builder.sign(&signer); - } - - builder.build_minimal() - } - - fn create_transaction( - root_signature_count: usize, - signature_counts: Vec, - ) -> NotarizedTransactionV2 { - let notary = Secp256k1PrivateKey::from_u64(2).unwrap(); - let mut builder = TransactionV2Builder::new(); - - for (i, signature_count) in signature_counts.iter().enumerate() { - builder = builder.add_signed_child( - format!("child{i}"), - create_partial_transaction(i, *signature_count), - ) - } - - let mut builder = builder - .intent_header(IntentHeaderV2 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: Epoch::of(0), - end_epoch_exclusive: Epoch::of(1), - min_proposer_timestamp_inclusive: None, - max_proposer_timestamp_exclusive: None, - intent_discriminator: 0, - }) - .manifest_builder(|mut builder| { - builder = builder.lock_fee_from_faucet(); - for (i, _) in signature_counts.iter().enumerate() { - builder = builder.yield_to_child(format!("child{i}"), ()); - } - builder - }) - .transaction_header(TransactionHeaderV2 { - notary_public_key: notary.public_key().into(), - notary_is_signatory: false, - tip_basis_points: 0, - }); - - for i in 0..root_signature_count { - let signer = Secp256k1PrivateKey::from_u64((100 + i) as u64).unwrap(); - builder = builder.sign(&signer); - } - - builder.notarize(¬ary).build_minimal_no_validate() - } - - let validator = TransactionValidator::new_for_latest_simulator(); - assert_matches!( - create_transaction(1, vec![10]).prepare_and_validate(&validator), - Ok(_) - ); - assert_matches!( - create_transaction(1, vec![10, 20]).prepare_and_validate(&validator), - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(1), _), - SignatureValidationError::TooManySignatures { - total: 20, - limit: 16, - }, - )) - ); - assert_matches!( - create_transaction(17, vec![0, 3]).prepare_and_validate(&validator), - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::RootTransactionIntent(_), - SignatureValidationError::TooManySignatures { - total: 17, - limit: 16, - }, - )) - ); - assert_matches!( - create_transaction(1, vec![10, 10, 10, 10, 10, 10, 10]) - .prepare_and_validate(&validator), - Err(TransactionValidationError::SignatureValidationError( - TransactionValidationErrorLocation::AcrossTransaction, - SignatureValidationError::TooManySignatures { - total: 72, // 70 from subintent, 1 from transaction intent, 1 from notarization - limit: 64 - }, - )) - ); - } - - #[test] - fn too_many_references_should_be_rejected() { - fn create_partial_transaction( - subintent_index: usize, - num_references: usize, - ) -> SignedPartialTransactionV2 { - PartialTransactionV2Builder::new() - .intent_header(IntentHeaderV2 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: Epoch::of(0), - end_epoch_exclusive: Epoch::of(1), - min_proposer_timestamp_inclusive: None, - max_proposer_timestamp_exclusive: None, - intent_discriminator: subintent_index as u64, - }) - .manifest_builder(|mut builder| { - for i in 0..num_references { - let mut address = - [EntityType::GlobalPreallocatedSecp256k1Account as u8; NodeId::LENGTH]; - address[1..9].copy_from_slice( - &(((subintent_index + 1) * 1000 + i) as u64).to_le_bytes(), - ); - builder = builder.call_method( - ComponentAddress::new_or_panic(address), - "method_name", - (), - ); - } - - builder.yield_to_parent(()) - }) - .sign(&Secp256k1PrivateKey::from_u64(1000 + subintent_index as u64).unwrap()) - .build_minimal() - } - - fn create_transaction(reference_counts: Vec) -> NotarizedTransactionV2 { - let signer = Secp256k1PrivateKey::from_u64(1).unwrap(); - let notary = Secp256k1PrivateKey::from_u64(2).unwrap(); - let mut builder = TransactionV2Builder::new(); - - for (i, reference_count) in reference_counts.iter().enumerate() { - builder = builder.add_signed_child( - format!("child{i}"), - create_partial_transaction(i, *reference_count), - ) - } - - builder - .intent_header(IntentHeaderV2 { - network_id: NetworkDefinition::simulator().id, - start_epoch_inclusive: Epoch::of(0), - end_epoch_exclusive: Epoch::of(1), - min_proposer_timestamp_inclusive: None, - max_proposer_timestamp_exclusive: None, - intent_discriminator: 0, - }) - .manifest_builder(|mut builder| { - builder = builder.lock_fee_from_faucet(); - for (i, _) in reference_counts.iter().enumerate() { - builder = builder.yield_to_child(format!("child{i}"), ()); - } - builder - }) - .transaction_header(TransactionHeaderV2 { - notary_public_key: notary.public_key().into(), - notary_is_signatory: false, - tip_basis_points: 0, - }) - .sign(&signer) - .notarize(¬ary) - .build_minimal_no_validate() - } - - let validator = TransactionValidator::new_for_latest_simulator(); - assert_matches!( - create_transaction(vec![100]).prepare_and_validate(&validator), - Ok(_) - ); - assert_matches!( - create_transaction(vec![100, 600]).prepare_and_validate(&validator), - Err(TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(1), _), - IntentValidationError::TooManyReferences { - total: 600, - limit: 512, - } - )) - ); - assert_matches!( - create_transaction(vec![500, 500]).prepare_and_validate(&validator), - Err(TransactionValidationError::IntentValidationError( - TransactionValidationErrorLocation::AcrossTransaction, - IntentValidationError::TooManyReferences { - total: 1001, // 1000 from subintent, 1 from transaction intent - limit: 512, - } - )) - ); - } -} +// Concrete methods on `TransactionValidator` are implemented across +// other modules, such as `transaction_validator_v1`, to avoid this +// file growing to an unmanagable size. diff --git a/radix-transactions/src/validation/transaction_validator_v1.rs b/radix-transactions/src/validation/transaction_validator_v1.rs new file mode 100644 index 00000000000..5ef227cdbf5 --- /dev/null +++ b/radix-transactions/src/validation/transaction_validator_v1.rs @@ -0,0 +1,742 @@ +use crate::internal_prelude::*; + +impl TransactionValidator { + #[allow(deprecated)] + pub fn validate_notarized_v1( + &self, + transaction: PreparedNotarizedTransactionV1, + ) -> Result { + let transaction_intent = &transaction.signed_intent.intent; + + let signatures = AllPendingSignatureValidations::new_with_root( + TransactionVersion::V1, + &self.config, + transaction_intent.transaction_intent_hash().into(), + PendingIntentSignatureValidations::TransactionIntent { + notary_is_signatory: transaction_intent.header.inner.notary_is_signatory, + notary_public_key: transaction_intent.header.inner.notary_public_key, + notary_signature: transaction.notary_signature.inner.0, + notarized_hash: transaction.signed_transaction_intent_hash(), + intent_signatures: transaction + .signed_intent + .intent_signatures + .inner + .signatures + .as_slice(), + signed_hash: transaction_intent.transaction_intent_hash(), + }, + )?; + + let aggregation = self + .validate_intent_v1(&transaction.signed_intent.intent) + .map_err(|err| { + TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent( + transaction_intent.transaction_intent_hash(), + ), + err, + ) + })?; + let _ = aggregation.finalize(&self.config)?; // Don't use the overall validity range in V1 + + let encoded_instructions = + manifest_encode(&transaction.signed_intent.intent.instructions.inner.0)?; + + let SignatureValidationSummary { + root_signer_keys: signer_keys, + non_root_signer_keys: _, // Not used in V1 + total_signature_validations: num_of_signature_validations, + } = signatures.validate_all()?; + + Ok(ValidatedNotarizedTransactionV1 { + prepared: transaction, + encoded_instructions, + signer_keys, + num_of_signature_validations, + }) + } + + #[allow(deprecated)] + pub fn validate_preview_intent_v1( + &self, + preview_intent: PreviewIntentV1, + ) -> Result { + let fake_intent_hash = SimulatedTransactionIntentNullification.transaction_intent_hash(); + let intent = preview_intent.intent.prepare(self.preparation_settings())?; + + let aggregation = self.validate_intent_v1(&intent).map_err(|err| { + TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(fake_intent_hash), + err, + ) + })?; + aggregation.finalize(&self.config)?; + + let encoded_instructions = manifest_encode(&intent.instructions.inner.0)?; + + Ok(ValidatedPreviewIntent { + intent, + encoded_instructions, + signer_public_keys: preview_intent.signer_public_keys, + flags: preview_intent.flags, + }) + } + + // This method is public so it can be used by the toolkit. + #[allow(deprecated)] + pub fn validate_intent_v1( + &self, + intent: &PreparedIntentV1, + ) -> Result { + let mut aggregation = AcrossIntentAggregation::start(); + self.validate_header_v1(&intent.header.inner)?; + self.validate_message_v1(&intent.message.inner)?; + aggregation.record_reference_count(intent.instructions.references.len(), &self.config)?; + self.validate_instructions_v1(&intent.instructions.inner.0, &intent.blobs.blobs_by_hash)?; + + Ok(aggregation) + } + + pub fn validate_instructions_v1( + &self, + instructions: &[InstructionV1], + blobs: &IndexMap>, + ) -> Result<(), IntentValidationError> { + if instructions.len() > self.config.max_instructions { + return Err(ManifestValidationError::TooManyInstructions.into()); + } + + match self.config.manifest_validation { + ManifestValidationRuleset::BabylonBasicValidator => self + .validate_instructions_basic_v1(instructions) + .map_err(|err| err.into()), + ManifestValidationRuleset::Interpreter(specifier) => StaticManifestInterpreter::new( + ValidationRuleset::for_specifier(specifier), + &EphemeralManifest::new_childless_transaction_manifest(instructions, blobs), + ) + .validate() + .map_err(|err| err.into()), + } + } + + pub fn validate_instructions_basic_v1( + &self, + instructions: &[InstructionV1], + ) -> Result<(), ManifestBasicValidatorError> { + let mut id_validator = BasicManifestValidator::new(); + for instruction in instructions { + match instruction.effect() { + ManifestInstructionEffect::CreateBucket { .. } => { + let _ = id_validator.new_bucket(); + } + ManifestInstructionEffect::CreateProof { source_amount, .. } => { + let _ = id_validator.new_proof(source_amount.proof_kind())?; + } + ManifestInstructionEffect::ConsumeBucket { + consumed_bucket: bucket, + .. + } => { + id_validator.drop_bucket(&bucket)?; + } + ManifestInstructionEffect::ConsumeProof { + consumed_proof: proof, + .. + } => { + id_validator.drop_proof(&proof)?; + } + ManifestInstructionEffect::CloneProof { cloned_proof, .. } => { + let _ = id_validator.clone_proof(&cloned_proof)?; + } + ManifestInstructionEffect::DropManyProofs { + drop_all_named_proofs, + .. + } => { + if drop_all_named_proofs { + id_validator.drop_all_named_proofs()?; + } + } + ManifestInstructionEffect::Invocation { args, .. } => { + id_validator.process_call_data(args)?; + } + ManifestInstructionEffect::CreateAddressAndReservation { .. } => { + let _ = id_validator.new_address_reservation(); + id_validator.new_named_address(); + } + ManifestInstructionEffect::ResourceAssertion { .. } => {} + ManifestInstructionEffect::Verification { .. } => { + unreachable!("No InstructionV1 returns this effect"); + } + } + } + Ok(()) + } + + pub fn validate_header_v1( + &self, + header: &TransactionHeaderV1, + ) -> Result<(), HeaderValidationError> { + // network + if let Some(required_network_id) = self.required_network_id { + if header.network_id != required_network_id { + return Err(HeaderValidationError::InvalidNetwork); + } + } + + // epoch + if header.end_epoch_exclusive <= header.start_epoch_inclusive { + return Err(HeaderValidationError::InvalidEpochRange); + } + let max_end_epoch = header + .start_epoch_inclusive + .after(self.config.max_epoch_range) + .ok_or(HeaderValidationError::InvalidEpochRange)?; + if header.end_epoch_exclusive > max_end_epoch { + return Err(HeaderValidationError::InvalidEpochRange); + } + + // tip percentage + if header.tip_percentage < self.config.min_tip_percentage + || header.tip_percentage > self.config.max_tip_percentage + { + return Err(HeaderValidationError::InvalidTip); + } + + Ok(()) + } + + pub fn validate_message_v1(&self, message: &MessageV1) -> Result<(), InvalidMessageError> { + let validation = &self.config.message_validation; + match message { + MessageV1::None => {} + MessageV1::Plaintext(plaintext_message) => { + let PlaintextMessageV1 { mime_type, message } = plaintext_message; + if mime_type.len() > validation.max_mime_type_length { + return Err(InvalidMessageError::MimeTypeTooLong { + actual: mime_type.len(), + permitted: validation.max_mime_type_length, + }); + } + if message.len() > validation.max_plaintext_message_length { + return Err(InvalidMessageError::PlaintextMessageTooLong { + actual: message.len(), + permitted: validation.max_plaintext_message_length, + }); + } + } + MessageV1::Encrypted(encrypted_message) => { + let EncryptedMessageV1 { + encrypted, + decryptors_by_curve, + } = encrypted_message; + if encrypted.0.len() > validation.max_encrypted_message_length { + return Err(InvalidMessageError::EncryptedMessageTooLong { + actual: encrypted.0.len(), + permitted: validation.max_encrypted_message_length, + }); + } + if decryptors_by_curve.len() == 0 { + return Err(InvalidMessageError::NoDecryptors); + } + let mut total_decryptors = 0; + for (curve_type, decryptors) in decryptors_by_curve.iter() { + if decryptors.curve_type() != *curve_type { + return Err(InvalidMessageError::MismatchingDecryptorCurves { + actual: decryptors.curve_type(), + expected: *curve_type, + }); + } + if decryptors.number_of_decryptors() == 0 { + return Err(InvalidMessageError::NoDecryptorsForCurveType { + curve_type: decryptors.curve_type(), + }); + } + // Can't overflow because decryptor count << size of a transaction < 1MB < usize, + total_decryptors += decryptors.number_of_decryptors(); + } + if total_decryptors > validation.max_decryptors { + return Err(InvalidMessageError::TooManyDecryptors { + actual: total_decryptors, + permitted: validation.max_decryptors, + }); + } + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::internal_prelude::*; + + macro_rules! assert_invalid_tx { + ($result: pat, ($start_epoch: expr, $end_epoch: expr, $nonce: expr, $signers: expr, $notary: expr)) => {{ + let validator = TransactionValidator::new_for_latest_simulator(); + assert_matches!( + create_transaction($start_epoch, $end_epoch, $nonce, $signers, $notary) + .prepare_and_validate(&validator) + .expect_err("Should be an error"), + $result, + ); + }}; + } + + #[test] + fn test_invalid_header() { + assert_invalid_tx!( + TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + ), + (Epoch::zero(), Epoch::zero(), 5, vec![1], 2) + ); + assert_invalid_tx!( + TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + ), + ( + Epoch::zero(), + Epoch::of(TransactionValidationConfig::latest().max_epoch_range + 1), + 5, + vec![1], + 2 + ) + ); + } + + #[test] + fn test_epoch_overflow() { + assert_invalid_tx!( + TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + ), + (Epoch::of(u64::MAX - 5), Epoch::of(u64::MAX), 5, vec![1], 2) + ); + } + + #[test] + fn test_too_many_signatures() { + assert_invalid_tx!( + TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::TooManySignatures { + total: 19, + limit: 16, + } + ), + (Epoch::zero(), Epoch::of(100), 5, (1..20).collect(), 2) + ); + } + + #[test] + fn test_duplicate_signers() { + assert_invalid_tx!( + TransactionValidationError::SignatureValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + SignatureValidationError::DuplicateSigner + ), + (Epoch::zero(), Epoch::of(100), 5, vec![1, 1], 2) + ); + } + + #[test] + fn test_valid_preview() { + // Build the whole transaction but only really care about the intent + let tx = create_transaction(Epoch::zero(), Epoch::of(100), 5, vec![1, 2], 2); + + let validator = TransactionValidator::new_for_latest_simulator(); + + let preview_intent = PreviewIntentV1 { + intent: tx.signed_intent.intent, + signer_public_keys: Vec::new(), + flags: PreviewFlags { + use_free_credit: true, + assume_all_signature_proofs: false, + skip_epoch_check: false, + disable_auth: false, + }, + }; + + let result = validator.validate_preview_intent_v1(preview_intent); + + assert!(result.is_ok()); + } + + #[test] + fn test_valid_messages() { + // None + { + let message = MessageV1::None; + let result = validate_default(&create_transaction_with_message(message)); + assert!(result.is_ok()); + } + // Plaintext + { + let message = MessageV1::Plaintext(PlaintextMessageV1 { + mime_type: "text/plain".to_owned(), + message: MessageContentsV1::String("Hello world!".to_string()), + }); + let result = validate_default(&create_transaction_with_message(message)); + assert!(result.is_ok()); + } + // Encrypted + { + // Note - this isn't actually a validly encrypted message, + // this just shows that a sufficiently valid encrypted message can pass validation + let message = MessageV1::Encrypted(EncryptedMessageV1 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), + ), + }, + CurveType::Secp256k1 => DecryptorsByCurve::Secp256k1 { + dh_ephemeral_public_key: Secp256k1PublicKey([0; Secp256k1PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), + PublicKeyFingerprint([1; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), + ), + }, + ), + }); + let result = validate_default(&create_transaction_with_message(message)); + assert!(result.is_ok()); + } + } + + #[test] + fn test_invalid_message_errors() { + // MimeTypeTooLong + { + let message = MessageV1::Plaintext(PlaintextMessageV1 { + mime_type: "very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, ".to_owned(), + message: MessageContentsV1::String("Hello".to_string()), + }); + let error = + validate_default_expecting_message_error(&create_transaction_with_message(message)); + assert_matches!(error, InvalidMessageError::MimeTypeTooLong { .. }) + } + + // PlaintextMessageTooLong + { + let mut long_message: String = "".to_owned(); + while long_message.len() <= 2048 { + long_message.push_str("more text please!"); + } + let message = MessageV1::Plaintext(PlaintextMessageV1 { + mime_type: "text/plain".to_owned(), + message: MessageContentsV1::String(long_message), + }); + let error = + validate_default_expecting_message_error(&create_transaction_with_message(message)); + assert_matches!(error, InvalidMessageError::PlaintextMessageTooLong { .. }) + } + + // EncryptedMessageTooLong + { + let mut message_which_is_too_long: String = "".to_owned(); + while message_which_is_too_long.len() <= 2048 + 50 { + // Some more bytes for the AES padding + message_which_is_too_long.push_str("more text please!"); + } + let message = MessageV1::Encrypted(EncryptedMessageV1 { + encrypted: AesGcmPayload(message_which_is_too_long.as_bytes().to_vec()), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), + ), + } + ), + }); + let error = + validate_default_expecting_message_error(&create_transaction_with_message(message)); + assert_matches!(error, InvalidMessageError::EncryptedMessageTooLong { .. }) + } + + // NoDecryptors + { + let message = MessageV1::Encrypted(EncryptedMessageV1 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!(), + }); + let error = + validate_default_expecting_message_error(&create_transaction_with_message(message)); + assert_matches!(error, InvalidMessageError::NoDecryptors) + } + + // NoDecryptorsForCurveType + { + let message = MessageV1::Encrypted(EncryptedMessageV1 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors: indexmap!(), + } + ), + }); + let error = + validate_default_expecting_message_error(&create_transaction_with_message(message)); + assert_matches!( + error, + InvalidMessageError::NoDecryptorsForCurveType { + curve_type: CurveType::Ed25519 + } + ) + } + + // MismatchingDecryptorCurves + { + let message = MessageV1::Encrypted(EncryptedMessageV1 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurve::Secp256k1 { + dh_ephemeral_public_key: Secp256k1PublicKey([0; Secp256k1PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), + ), + } + ), + }); + let error = + validate_default_expecting_message_error(&create_transaction_with_message(message)); + assert_matches!( + error, + InvalidMessageError::MismatchingDecryptorCurves { + actual: CurveType::Secp256k1, + expected: CurveType::Ed25519 + } + ) + } + + // TooManyDecryptors + { + let mut decryptors = IndexMap::::default(); + for i in 0..30 { + decryptors.insert( + PublicKeyFingerprint([0, 0, 0, 0, 0, 0, 0, i as u8]), + AesWrapped128BitKey([0; AesWrapped128BitKey::LENGTH]), + ); + } + let message = MessageV1::Encrypted(EncryptedMessageV1 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurve::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors, + } + ), + }); + let error = + validate_default_expecting_message_error(&create_transaction_with_message(message)); + assert_matches!( + error, + InvalidMessageError::TooManyDecryptors { + actual: 30, + permitted: 20 + } + ) + } + } + + fn validate_default_expecting_message_error( + transaction: &NotarizedTransactionV1, + ) -> InvalidMessageError { + match validate_default(transaction).expect_err("Expected validation error") { + TransactionValidationError::IntentValidationError( + _, + IntentValidationError::InvalidMessage(error), + ) => error, + error => { + panic!("Expected InvalidMessage error, got: {:?}", error) + } + } + } + + fn validate_default( + transaction: &NotarizedTransactionV1, + ) -> Result<(), TransactionValidationError> { + let validator = TransactionValidator::new_for_latest_simulator(); + transaction.prepare_and_validate(&validator).map(|_| ()) + } + + fn create_transaction_with_message(message: MessageV1) -> NotarizedTransactionV1 { + let sk_notary = Secp256k1PrivateKey::from_u64(1).unwrap(); + + let mut builder = TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive: Epoch::of(1), + end_epoch_exclusive: Epoch::of(10), + nonce: 0, + notary_public_key: sk_notary.public_key().into(), + notary_is_signatory: false, + tip_percentage: 5, + }) + .manifest(ManifestBuilder::new().drop_auth_zone_proofs().build()) + .message(message); + + builder = builder.notarize(&sk_notary); + + builder.build() + } + + fn create_transaction( + start_epoch: Epoch, + end_epoch: Epoch, + nonce: u32, + signers: Vec, + notary: u64, + ) -> NotarizedTransactionV1 { + create_transaction_advanced( + start_epoch, + end_epoch, + nonce, + signers, + notary, + ManifestBuilder::new().drop_auth_zone_proofs().build(), + ) + } + + fn create_transaction_advanced( + start_epoch: Epoch, + end_epoch: Epoch, + nonce: u32, + signers: Vec, + notary: u64, + manifest: TransactionManifestV1, + ) -> NotarizedTransactionV1 { + let sk_notary = Secp256k1PrivateKey::from_u64(notary).unwrap(); + + let mut builder = TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive: start_epoch, + end_epoch_exclusive: end_epoch, + nonce, + notary_public_key: sk_notary.public_key().into(), + notary_is_signatory: false, + tip_percentage: 5, + }) + .manifest(manifest); + + for signer in signers { + builder = builder.sign(&Secp256k1PrivateKey::from_u64(signer).unwrap()); + } + builder = builder.notarize(&sk_notary); + + builder.build() + } + + #[test] + fn test_drop_bucket_before_proof() { + let transaction = create_transaction_advanced( + Epoch::of(0), + Epoch::of(40), + 123, + vec![55], + 66, + ManifestBuilder::new() + .take_from_worktop(XRD, dec!(100), "bucket") + .create_proof_from_bucket_of_amount("bucket", dec!(5), "proof1") + .return_to_worktop("bucket") + .drop_proof("proof1") + .build_no_validate(), + ); + let validator = TransactionValidator::new_for_latest_simulator(); + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::IntentValidationError( + _, + IntentValidationError::ManifestValidationError( + ManifestValidationError::BucketConsumedWhilstLockedByProof( + ManifestBucket(0), + _, + ) + ) + )) + ); + } + + #[test] + fn test_clone_invalid_proof() { + let transaction = create_transaction_advanced( + Epoch::of(0), + Epoch::of(40), + 123, + vec![55], + 66, + ManifestBuilder::new() + .take_from_worktop(XRD, dec!(100), "bucket") + .create_proof_from_bucket_of_amount("bucket", dec!(5), "proof1") + .then(|builder| { + let lookup = builder.name_lookup(); + let proof_id = lookup.proof("proof1"); + + builder + .drop_proof("proof1") + .return_to_worktop("bucket") + .add_raw_instruction_ignoring_all_side_effects(CloneProof { proof_id }) + }) + .build_no_validate(), + ); + let validator = TransactionValidator::new_for_latest_simulator(); + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::IntentValidationError( + _, + IntentValidationError::ManifestValidationError( + ManifestValidationError::ProofAlreadyUsed(ManifestProof(0), _,) + ) + )) + ); + } + + #[test] + fn verify_call_direct_method_args_are_processed() { + let transaction = create_transaction_advanced( + Epoch::of(0), + Epoch::of(40), + 123, + vec![55], + 66, + ManifestBuilder::new() + .take_from_worktop(XRD, dec!(100), "bucket") + .then(|builder| { + let lookup = builder.name_lookup(); + builder + .call_direct_access_method( + InternalAddress::new_or_panic( + [EntityType::InternalFungibleVault as u8; NodeId::LENGTH], + ), + "test", + manifest_args!(lookup.bucket("bucket")), + ) + .return_to_worktop("bucket") + }) + .build_no_validate(), + ); + let validator = TransactionValidator::new_for_latest_simulator(); + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::IntentValidationError( + _, + IntentValidationError::ManifestValidationError( + ManifestValidationError::BucketAlreadyUsed(ManifestBucket(0), _,) + ) + )) + ); + } +} diff --git a/radix-transactions/src/validation/transaction_validator_v2.rs b/radix-transactions/src/validation/transaction_validator_v2.rs new file mode 100644 index 00000000000..2b07a4093d4 --- /dev/null +++ b/radix-transactions/src/validation/transaction_validator_v2.rs @@ -0,0 +1,2453 @@ +use crate::internal_prelude::*; + +impl TransactionValidator { + pub fn validate_notarized_v2( + &self, + prepared: PreparedNotarizedTransactionV2, + ) -> Result { + let ValidatedTransactionTreeV2 { + overall_validity_range, + total_signature_validations, + root_intent_info, + root_yield_to_parent_count: _, // Checked to be 0 in the manifest validator. + non_root_subintents_info, + } = self.validate_transaction_tree_v2( + &prepared, + &prepared.signed_intent.transaction_intent.root_intent_core, + &prepared + .signed_intent + .transaction_intent + .non_root_subintents, + )?; + + Ok(ValidatedNotarizedTransactionV2 { + prepared, + overall_validity_range, + total_signature_validations, + transaction_intent_info: root_intent_info, + non_root_subintents_info, + }) + } + + pub fn validate_preview_transaction_v2( + &self, + prepared: PreparedPreviewTransactionV2, + ) -> Result { + let ValidatedTransactionTreeV2 { + overall_validity_range, + total_signature_validations: total_expected_signature_validations, + root_intent_info, + root_yield_to_parent_count: _, // Checked to be 0 in the manifest validator. + non_root_subintents_info, + } = self.validate_transaction_tree_v2( + &prepared, + &prepared.transaction_intent.root_intent_core, + &prepared.transaction_intent.non_root_subintents, + )?; + + Ok(ValidatedPreviewTransactionV2 { + prepared, + overall_validity_range, + total_expected_signature_validations, + transaction_intent_info: root_intent_info, + non_root_subintents_info, + }) + } + + // This method is public so it can be used by the toolkit. + pub fn validate_transaction_header_v2( + &self, + header: &TransactionHeaderV2, + ) -> Result<(), HeaderValidationError> { + if header.tip_basis_points < self.config.min_tip_basis_points + || header.tip_basis_points > self.config.max_tip_basis_points + { + return Err(HeaderValidationError::InvalidTip); + } + + Ok(()) + } + + pub fn validate_signed_partial_transaction_v2( + &self, + prepared: PreparedSignedPartialTransactionV2, + ) -> Result { + let ValidatedTransactionTreeV2 { + overall_validity_range, + root_intent_info, + root_yield_to_parent_count, + non_root_subintents_info, + total_signature_validations, + } = self.validate_transaction_tree_v2( + &prepared, + &prepared.partial_transaction.root_subintent.intent_core, + &prepared.partial_transaction.non_root_subintents, + )?; + + Ok(ValidatedSignedPartialTransactionV2 { + prepared, + total_signature_validations, + overall_validity_range, + root_subintent_info: root_intent_info, + root_subintent_yield_to_parent_count: root_yield_to_parent_count, + non_root_subintents_info, + }) + } + + pub fn validate_transaction_tree_v2( + &self, + signed_intent_tree: &impl SignedIntentTreeStructure, + root_intent_core: &PreparedIntentCoreV2, + non_root_subintents: &PreparedNonRootSubintentsV2, + ) -> Result { + if !self.config.v2_transactions_allowed { + return Err(TransactionValidationError::TransactionVersionNotPermitted( + 2, + )); + } + + let signatures = + signed_intent_tree.construct_pending_signature_validations(&self.config)?; + + let ValidatedIntentTreeInformation { + intent_relationships, + overall_validity_range, + root_yield_summary, + } = self.validate_intents_and_structure(signed_intent_tree.intent_tree())?; + + // We delay signature validation until after the other validations as it's more expensive. + let SignatureValidationSummary { + root_signer_keys, + non_root_signer_keys, + total_signature_validations, + } = signatures.validate_all()?; + + let root_intent_info = ValidatedIntentInformationV2 { + encoded_instructions: manifest_encode(&root_intent_core.instructions.inner.0)?.into(), + children_subintent_indices: intent_relationships.root_intent.children, + signer_keys: root_signer_keys, + }; + let non_root_subintents_info = non_root_subintents + .subintents + .iter() + .zip(non_root_signer_keys) + .zip(intent_relationships.non_root_subintents.into_values()) + .map( + |((subintent, signer_keys), info)| -> Result<_, TransactionValidationError> { + Ok(ValidatedIntentInformationV2 { + encoded_instructions: manifest_encode( + &subintent.intent_core.instructions.inner.0, + )? + .into(), + signer_keys, + children_subintent_indices: info.children, + }) + }, + ) + .collect::>()?; + + Ok(ValidatedTransactionTreeV2 { + overall_validity_range, + root_intent_info, + root_yield_to_parent_count: root_yield_summary.parent_yields, + non_root_subintents_info, + total_signature_validations, + }) + } + + // This method is public so it can be used by the toolkit. + pub fn validate_v2_intent_core( + &self, + intent_core: &PreparedIntentCoreV2, + aggregation: &mut AcrossIntentAggregation, + is_subintent: bool, + ) -> Result { + self.validate_intent_header_v2(&intent_core.header.inner, aggregation)?; + self.validate_message_v2(&intent_core.message.inner)?; + aggregation + .record_reference_count(intent_core.instructions.references.len(), &self.config)?; + let yield_summary = self.validate_manifest_v2( + &intent_core.instructions.inner.0, + &intent_core.blobs.blobs_by_hash, + &intent_core.children.children, + is_subintent, + )?; + Ok(yield_summary) + } + + // This method is public so it can be used by the toolkit. + pub fn validate_intent_header_v2( + &self, + header: &IntentHeaderV2, + aggregation: &mut AcrossIntentAggregation, + ) -> Result<(), HeaderValidationError> { + // Network + if let Some(required_network_id) = self.required_network_id { + if header.network_id != required_network_id { + return Err(HeaderValidationError::InvalidNetwork); + } + } + + // Epoch + if header.end_epoch_exclusive <= header.start_epoch_inclusive { + return Err(HeaderValidationError::InvalidEpochRange); + } + let max_end_epoch = header + .start_epoch_inclusive + .after(self.config.max_epoch_range) + .ok_or(HeaderValidationError::InvalidEpochRange)?; + if header.end_epoch_exclusive > max_end_epoch { + return Err(HeaderValidationError::InvalidEpochRange); + } + + match ( + header.min_proposer_timestamp_inclusive.as_ref(), + header.max_proposer_timestamp_exclusive.as_ref(), + ) { + (Some(min_timestamp_inclusive), Some(max_timestamp_exclusive)) => { + if min_timestamp_inclusive >= max_timestamp_exclusive { + return Err(HeaderValidationError::InvalidTimestampRange); + } + } + _ => {} + }; + + aggregation.update_headers( + header.start_epoch_inclusive, + header.end_epoch_exclusive, + header.min_proposer_timestamp_inclusive.as_ref(), + header.max_proposer_timestamp_exclusive.as_ref(), + )?; + + Ok(()) + } + + // This method is public so it can be used by the toolkit. + pub fn validate_message_v2(&self, message: &MessageV2) -> Result<(), InvalidMessageError> { + let validation = &self.config.message_validation; + match message { + MessageV2::None => {} + MessageV2::Plaintext(plaintext_message) => { + let PlaintextMessageV1 { mime_type, message } = plaintext_message; + if mime_type.len() > validation.max_mime_type_length { + return Err(InvalidMessageError::MimeTypeTooLong { + actual: mime_type.len(), + permitted: validation.max_mime_type_length, + }); + } + if message.len() > validation.max_plaintext_message_length { + return Err(InvalidMessageError::PlaintextMessageTooLong { + actual: message.len(), + permitted: validation.max_plaintext_message_length, + }); + } + } + MessageV2::Encrypted(encrypted_message) => { + let EncryptedMessageV2 { + encrypted, + decryptors_by_curve, + } = encrypted_message; + if encrypted.0.len() > validation.max_encrypted_message_length { + return Err(InvalidMessageError::EncryptedMessageTooLong { + actual: encrypted.0.len(), + permitted: validation.max_encrypted_message_length, + }); + } + if decryptors_by_curve.len() == 0 { + return Err(InvalidMessageError::NoDecryptors); + } + let mut total_decryptors = 0; + for (curve_type, decryptors) in decryptors_by_curve.iter() { + if decryptors.curve_type() != *curve_type { + return Err(InvalidMessageError::MismatchingDecryptorCurves { + actual: decryptors.curve_type(), + expected: *curve_type, + }); + } + if decryptors.number_of_decryptors() == 0 { + return Err(InvalidMessageError::NoDecryptorsForCurveType { + curve_type: decryptors.curve_type(), + }); + } + // Can't overflow because decryptor count << size of a transaction < 1MB < usize, + total_decryptors += decryptors.number_of_decryptors(); + } + if total_decryptors > validation.max_decryptors { + return Err(InvalidMessageError::TooManyDecryptors { + actual: total_decryptors, + permitted: validation.max_decryptors, + }); + } + } + } + Ok(()) + } + + // This method is public so it can be used by the toolkit. + /// The `is_subintent` property indicates whether it should be treated as a subintent. + /// A subintent is able to `YIELD_TO_PARENT` and is required to end with a `YIELD_TO_PARENT`. + pub fn validate_manifest_v2( + &self, + instructions: &[InstructionV2], + blobs: &IndexMap>, + children: &IndexSet, + is_subintent: bool, + ) -> Result { + if instructions.len() > self.config.max_instructions { + return Err(ManifestValidationError::TooManyInstructions); + } + + let mut yield_summary = + ManifestYieldSummary::new_with_children(children.iter().map(|c| c.hash)); + StaticManifestInterpreter::new( + ValidationRuleset::cuttlefish(), + &EphemeralManifest::new(instructions, blobs, children, is_subintent), + ) + .validate_and_apply_visitor(&mut yield_summary)?; + + Ok(yield_summary) + } +} + +impl IntentStructure for PreparedTransactionIntentV2 { + fn intent_hash(&self) -> IntentHash { + self.transaction_intent_hash().into() + } + + fn children(&self) -> impl ExactSizeIterator { + self.root_intent_core + .children + .children + .iter() + .map(|child| child.hash) + } + + fn validate_intent( + &self, + validator: &TransactionValidator, + aggregation: &mut AcrossIntentAggregation, + ) -> Result { + validator + .validate_transaction_header_v2(&self.transaction_header.inner) + .map_err(IntentValidationError::HeaderValidationError)?; + validator.validate_v2_intent_core(&self.root_intent_core, aggregation, false) + } +} + +impl IntentStructure for PreparedSubintentV2 { + fn intent_hash(&self) -> IntentHash { + self.subintent_hash().into() + } + + fn children(&self) -> impl ExactSizeIterator { + self.intent_core + .children + .children + .iter() + .map(|child| child.hash) + } + + fn validate_intent( + &self, + validator: &TransactionValidator, + aggregation: &mut AcrossIntentAggregation, + ) -> Result { + validator.validate_v2_intent_core(&self.intent_core, aggregation, true) + } +} + +impl IntentTreeStructure for PreparedTransactionIntentV2 { + type RootIntentStructure = Self; + type SubintentStructure = PreparedSubintentV2; + + fn root(&self) -> &Self::RootIntentStructure { + self + } + + fn non_root_subintents<'a>( + &'a self, + ) -> impl ExactSizeIterator { + self.non_root_subintents.subintents.iter() + } +} + +impl IntentTreeStructure for PreparedPartialTransactionV2 { + type RootIntentStructure = PreparedSubintentV2; + type SubintentStructure = PreparedSubintentV2; + + fn root(&self) -> &Self::RootIntentStructure { + &self.root_subintent + } + + fn non_root_subintents<'a>( + &'a self, + ) -> impl ExactSizeIterator { + self.non_root_subintents.subintents.iter() + } +} + +impl SignedIntentTreeStructure for PreparedNotarizedTransactionV2 { + type IntentTree = PreparedTransactionIntentV2; + + fn root_signatures(&self) -> PendingIntentSignatureValidations { + let transaction_intent = &self.signed_intent.transaction_intent; + PendingIntentSignatureValidations::TransactionIntent { + notary_is_signatory: transaction_intent + .transaction_header + .inner + .notary_is_signatory, + notary_public_key: transaction_intent + .transaction_header + .inner + .notary_public_key, + notary_signature: self.notary_signature.inner.0, + notarized_hash: self.signed_transaction_intent_hash(), + intent_signatures: self + .signed_intent + .transaction_intent_signatures + .inner + .signatures + .as_slice(), + signed_hash: transaction_intent.transaction_intent_hash(), + } + } + + fn non_root_subintent_signatures( + &self, + ) -> impl ExactSizeIterator { + self.signed_intent + .non_root_subintent_signatures + .by_subintent + .iter() + .map( + |signatures| PendingSubintentSignatureValidations::Subintent { + intent_signatures: signatures.inner.signatures.as_slice(), + }, + ) + } + + fn intent_tree(&self) -> &Self::IntentTree { + &self.signed_intent.transaction_intent + } + + fn transaction_version(&self) -> TransactionVersion { + TransactionVersion::V2 + } +} + +impl SignedIntentTreeStructure for PreparedSignedPartialTransactionV2 { + type IntentTree = PreparedPartialTransactionV2; + + fn root_signatures(&self) -> PendingIntentSignatureValidations { + PendingIntentSignatureValidations::Subintent { + intent_signatures: self.root_subintent_signatures.inner.signatures.as_slice(), + signed_hash: self.intent_tree().subintent_hash(), + } + } + + fn non_root_subintent_signatures( + &self, + ) -> impl ExactSizeIterator { + self.non_root_subintent_signatures + .by_subintent + .iter() + .map( + |signatures| PendingSubintentSignatureValidations::Subintent { + intent_signatures: signatures.inner.signatures.as_slice(), + }, + ) + } + + fn intent_tree(&self) -> &Self::IntentTree { + &self.partial_transaction + } + + fn transaction_version(&self) -> TransactionVersion { + TransactionVersion::V2 + } +} + +impl SignedIntentTreeStructure for PreparedPreviewTransactionV2 { + type IntentTree = PreparedTransactionIntentV2; + + fn root_signatures(&self) -> PendingIntentSignatureValidations { + let transaction_intent = &self.transaction_intent; + PendingIntentSignatureValidations::PreviewTransactionIntent { + notary_is_signatory: transaction_intent + .transaction_header + .inner + .notary_is_signatory, + notary_public_key: transaction_intent + .transaction_header + .inner + .notary_public_key, + intent_public_keys: self.root_subintent_signatures.inner.as_slice(), + } + } + + fn non_root_subintent_signatures( + &self, + ) -> impl ExactSizeIterator { + self.non_root_subintent_signatures + .inner + .iter() + .map( + |public_keys| PendingSubintentSignatureValidations::PreviewSubintent { + intent_public_keys: public_keys.as_slice(), + }, + ) + } + + fn intent_tree(&self) -> &Self::IntentTree { + &self.transaction_intent + } + + fn transaction_version(&self) -> TransactionVersion { + TransactionVersion::V2 + } +} + +#[cfg(test)] +mod tests { + use crate::internal_prelude::*; + + fn mutate_subintents( + transaction: &mut NotarizedTransactionV2, + subintents_mutate: impl FnOnce(&mut Vec), + subintent_signatures_mutate: impl FnOnce(&mut Vec), + ) { + subintents_mutate( + &mut transaction + .signed_transaction_intent + .transaction_intent + .non_root_subintents + .0, + ); + subintent_signatures_mutate( + &mut transaction + .signed_transaction_intent + .non_root_subintent_signatures + .by_subintent, + ); + } + + #[test] + fn test_subintent_structure_errors() { + let validator = TransactionValidator::new_for_latest_simulator(); + + // SubintentStructureError::DuplicateSubintent + { + let duplicated_subintent = create_leaf_partial_transaction(0, 0); + let duplicated_subintent_hash = duplicated_subintent.root_subintent_hash; + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_children([duplicated_subintent]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate(); + + mutate_subintents( + &mut transaction, + |subintents| { + subintents.push(subintents[0].clone()); + }, + |subintent_signatures| { + subintent_signatures.push(subintent_signatures[0].clone()); + }, + ); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(1), subintent_hash), + SubintentStructureError::DuplicateSubintent, + )) => { + assert_eq!(subintent_hash, duplicated_subintent_hash); + } + ); + } + + // SubintentStructureError::SubintentHasMultipleParents + // ==================================================== + // CASE 1 - Two duplicates as children in the same intent + // =======> This isn't possible because `ChildSubintentSpecifiersV2` wraps an `IndexSet` + // Case 2 - Both duplicates across different intents + // =======> This is tested below + { + let duplicated_subintent = create_leaf_partial_transaction(1, 0); + + let parent_subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_children([duplicated_subintent.clone()]) + .add_manifest_calling_each_child_once() + .build(); + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_children([parent_subintent, duplicated_subintent.clone()]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate(); + + mutate_subintents( + &mut transaction, + |subintents| { + subintents.remove(1); + }, + |subintent_signatures| { + subintent_signatures.remove(1); + }, + ); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(1), subintent_hash), + SubintentStructureError::SubintentHasMultipleParents, + )) => { + assert_eq!(subintent_hash, duplicated_subintent.root_subintent_hash); + } + ); + } + + // SubintentStructureError::ChildSubintentNotIncludedInTransaction(SubintentHash) + { + let missing_subintent = create_leaf_partial_transaction(0, 0); + let missing_subintent_hash = missing_subintent.root_subintent_hash; + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_children([missing_subintent]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate(); + + mutate_subintents( + &mut transaction, + |subintents| { + subintents.pop(); + }, + |subintent_signatures| { + subintent_signatures.pop(); + }, + ); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::Unlocatable, + SubintentStructureError::ChildSubintentNotIncludedInTransaction(subintent_hash), + )) => { + assert_eq!(subintent_hash, missing_subintent_hash); + } + ); + } + + // SubintentStructureError::SubintentExceedsMaxDepth + { + let depth_4 = create_leaf_partial_transaction(0, 0); + let depth_4_hash = depth_4.root_subintent_hash; + let depth_3 = PartialTransactionV2Builder::new_with_test_defaults() + .add_children([depth_4]) + .add_manifest_calling_each_child_once() + .build(); + let depth_2 = PartialTransactionV2Builder::new_with_test_defaults() + .add_children([depth_3]) + .add_manifest_calling_each_child_once() + .build(); + let depth_1 = PartialTransactionV2Builder::new_with_test_defaults() + .add_children([depth_2]) + .add_manifest_calling_each_child_once() + .build(); + let transaction = TransactionV2Builder::new_with_test_defaults() + .add_children([depth_1]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate(); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(_), subintent_hash), + SubintentStructureError::SubintentExceedsMaxDepth, + )) => { + assert_eq!(subintent_hash, depth_4_hash); + } + ); + } + + // SubintentStructureError::SubintentIsNotReachableFromTheTransactionIntent + // ======================================================================== + // CASE 1 - The subintent is superfluous / has no parent + // This is tested below + // + // CASE 2 - Without a "no parent" short-circuit. + // To hit this error (but none of the previous errors) requires that we have + // a cycle in the subintent graph. + // + // But, because parents include a subintent hash of their direct children, + // which is itself part of their hash, a cycle would require a hash collision! + // + // But we can hack around this by explicitly overwriting the prepared subintent + // hashes. + { + // CASE 1 - The subintent has no parent + let no_parent_subintent = create_leaf_partial_transaction(0, 0); + let no_parent_subintent_hash = no_parent_subintent.root_subintent_hash; + + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate(); + + mutate_subintents( + &mut transaction, + |subintents| { + subintents.push( + no_parent_subintent + .partial_transaction + .partial_transaction + .root_subintent, + ); + }, + |subintent_signatures| { + subintent_signatures.push(IntentSignaturesV2::none()); + }, + ); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(0), subintent_hash), + SubintentStructureError::SubintentIsNotReachableFromTheTransactionIntent, + )) => { + assert_eq!(subintent_hash, no_parent_subintent_hash); + } + ); + + // CASE 2 - Without a potential "no parent" short-circuit + let faked_hash = SubintentHash::from_bytes([1; 32]); + + let self_parent_subintent = SubintentV2 { + intent_core: IntentCoreV2 { + header: IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive: Epoch::of(0), + end_epoch_exclusive: Epoch::of(1), + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 0, + }, + message: MessageV2::None, + instructions: InstructionsV2(vec![InstructionV2::YieldToParent( + YieldToParent::empty(), + )]), + blobs: BlobsV1::none(), + children: ChildSubintentSpecifiersV2 { + children: indexset![faked_hash.into()], + }, + }, + }; + + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate(); + + mutate_subintents( + &mut transaction, + |subintents| { + subintents.push(self_parent_subintent); + }, + |subintent_signatures| { + subintent_signatures.push(IntentSignaturesV2::none()); + }, + ); + + let mut prepared = transaction + .prepare(validator.preparation_settings()) + .unwrap(); + + // We overwrite the subintent hash to the faked hash + prepared + .signed_intent + .transaction_intent + .non_root_subintents + .subintents[0] + .summary + .hash = faked_hash.0; + + assert_matches!( + prepared.validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(0), subintent_hash), + SubintentStructureError::SubintentIsNotReachableFromTheTransactionIntent, + )) => { + assert_eq!(subintent_hash, faked_hash); + } + ); + } + + // SubintentStructureError::MismatchingYieldChildAndYieldParentCountsForSubintent + { + let single_yield_subintent = create_leaf_partial_transaction(0, 0); + let single_yield_subintent_hash = single_yield_subintent.root_subintent_hash; + + // CASE 1: We yield twice to it, but it yields to us only once + let transaction = TransactionV2Builder::new_with_test_defaults() + .add_signed_child("child", single_yield_subintent.clone()) + .manifest_builder(|builder| { + builder + .yield_to_child("child", ()) + .yield_to_child("child", ()) + }) + .default_notarize() + .build_minimal_no_validate(); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(0), subintent_hash), + SubintentStructureError::MismatchingYieldChildAndYieldParentCountsForSubintent, + )) => { + assert_eq!(subintent_hash, single_yield_subintent_hash); + } + ); + + // CASE 2: We yield zero times to it + let transaction = TransactionV2Builder::new_with_test_defaults() + .add_signed_child("child", single_yield_subintent) + .manifest_builder(|builder| builder) + .default_notarize() + .build_minimal_no_validate(); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(0), subintent_hash), + SubintentStructureError::MismatchingYieldChildAndYieldParentCountsForSubintent, + )) => { + assert_eq!(subintent_hash, single_yield_subintent_hash); + } + ); + + // CASE 3: More complex example, between two subintents, with 2 parent and 3 child yields: + let two_parent_yield_subintent = PartialTransactionV2Builder::new_with_test_defaults() + .manifest_builder(|builder| builder.yield_to_parent(()).yield_to_parent(())) + .build(); + let two_parent_yield_subintent_hash = two_parent_yield_subintent.root_subintent_hash; + + let three_child_yield_parent = PartialTransactionV2Builder::new_with_test_defaults() + .add_signed_child("child", two_parent_yield_subintent) + .manifest_builder(|builder| { + builder + .yield_to_child("child", ()) + .yield_to_child("child", ()) + .yield_to_child("child", ()) + .yield_to_parent(()) + }) + .build(); + + let transaction = TransactionV2Builder::new_with_test_defaults() + .add_children([three_child_yield_parent]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate(); + + assert_matches!( + transaction.prepare_and_validate(&validator), + Err(TransactionValidationError::SubintentStructureError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(_), subintent_hash), + SubintentStructureError::MismatchingYieldChildAndYieldParentCountsForSubintent, + )) => { + assert_eq!(subintent_hash, two_parent_yield_subintent_hash); + } + ); + } + } + + // NOTE: This is very similar to the V1 tests, just adjusted to the V2 models + #[test] + fn test_valid_messages() { + // None + { + let message = MessageV2::None; + assert_matches!(validate_transaction_with_message(message), Ok(_),); + } + // Plaintext + { + let message = MessageV2::Plaintext(PlaintextMessageV1 { + mime_type: "text/plain".to_owned(), + message: MessageContentsV1::String("Hello world!".to_string()), + }); + assert_matches!(validate_transaction_with_message(message), Ok(_),); + } + // Encrypted + { + // Note - this isn't actually a validly encrypted message, + // this just shows that a sufficiently valid encrypted message can pass validation + let message = MessageV2::Encrypted(EncryptedMessageV2 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurveV2::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped256BitKey([0; AesWrapped256BitKey::LENGTH]), + ), + }, + CurveType::Secp256k1 => DecryptorsByCurveV2::Secp256k1 { + dh_ephemeral_public_key: Secp256k1PublicKey([0; Secp256k1PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped256BitKey([0; AesWrapped256BitKey::LENGTH]), + PublicKeyFingerprint([1; PublicKeyFingerprint::LENGTH]) => AesWrapped256BitKey([0; AesWrapped256BitKey::LENGTH]), + ), + }, + ), + }); + assert_matches!(validate_transaction_with_message(message), Ok(_),); + } + } + + // NOTE: This is very similar to the V1 tests, just adjusted to the V2 models + #[test] + fn test_invalid_message_errors() { + // MimeTypeTooLong + { + let message = MessageV2::Plaintext(PlaintextMessageV1 { + mime_type: "very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, very long mimetype, ".to_owned(), + message: MessageContentsV1::String("Hello".to_string()), + }); + assert_matches!( + validate_transaction_with_message(message), + Err(InvalidMessageError::MimeTypeTooLong { .. }), + ); + } + + // PlaintextMessageTooLong + { + let mut long_message: String = "".to_owned(); + while long_message.len() <= 2048 { + long_message.push_str("more text please!"); + } + let message = MessageV2::Plaintext(PlaintextMessageV1 { + mime_type: "text/plain".to_owned(), + message: MessageContentsV1::String(long_message), + }); + assert_matches!( + validate_transaction_with_message(message), + Err(InvalidMessageError::PlaintextMessageTooLong { .. }), + ); + } + + // EncryptedMessageTooLong + { + let mut message_which_is_too_long: String = "".to_owned(); + while message_which_is_too_long.len() <= 2048 + 50 { + // Some more bytes for the AES padding + message_which_is_too_long.push_str("more text please!"); + } + let message = MessageV2::Encrypted(EncryptedMessageV2 { + encrypted: AesGcmPayload(message_which_is_too_long.as_bytes().to_vec()), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurveV2::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped256BitKey([0; AesWrapped256BitKey::LENGTH]), + ), + } + ), + }); + assert_matches!( + validate_transaction_with_message(message), + Err(InvalidMessageError::EncryptedMessageTooLong { .. }), + ); + } + + // NoDecryptors + { + let message = MessageV2::Encrypted(EncryptedMessageV2 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!(), + }); + assert_matches!( + validate_transaction_with_message(message), + Err(InvalidMessageError::NoDecryptors), + ); + } + + // NoDecryptorsForCurveType + { + let message = MessageV2::Encrypted(EncryptedMessageV2 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurveV2::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors: indexmap!(), + } + ), + }); + assert_matches!( + validate_transaction_with_message(message), + Err(InvalidMessageError::NoDecryptorsForCurveType { + curve_type: CurveType::Ed25519 + }), + ); + } + + // MismatchingDecryptorCurves + { + let message = MessageV2::Encrypted(EncryptedMessageV2 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurveV2::Secp256k1 { + dh_ephemeral_public_key: Secp256k1PublicKey([0; Secp256k1PublicKey::LENGTH]), + decryptors: indexmap!( + PublicKeyFingerprint([0; PublicKeyFingerprint::LENGTH]) => AesWrapped256BitKey([0; AesWrapped256BitKey::LENGTH]), + ), + } + ), + }); + assert_matches!( + validate_transaction_with_message(message), + Err(InvalidMessageError::MismatchingDecryptorCurves { + actual: CurveType::Secp256k1, + expected: CurveType::Ed25519 + }), + ); + } + + // TooManyDecryptors + { + let mut decryptors = IndexMap::::default(); + for i in 0..30 { + decryptors.insert( + PublicKeyFingerprint([0, 0, 0, 0, 0, 0, 0, i as u8]), + AesWrapped256BitKey([0; AesWrapped256BitKey::LENGTH]), + ); + } + let message = MessageV2::Encrypted(EncryptedMessageV2 { + encrypted: AesGcmPayload(vec![]), + decryptors_by_curve: indexmap!( + CurveType::Ed25519 => DecryptorsByCurveV2::Ed25519 { + dh_ephemeral_public_key: Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + decryptors, + } + ), + }); + assert_matches!( + validate_transaction_with_message(message), + Err(InvalidMessageError::TooManyDecryptors { + actual: 30, + permitted: 20 + }), + ); + } + } + + fn validate_transaction_with_message( + message: MessageV2, + ) -> Result { + TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .message(message) + .default_notarize_and_validate() + .map_err(|e| match e { + TransactionValidationError::IntentValidationError( + _, + IntentValidationError::InvalidMessage(e), + ) => e, + _ => panic!("Expected InvalidMessageError, but got: {:?}", e), + }) + } + + #[test] + fn too_many_references_should_be_rejected() { + fn create_partial_transaction( + subintent_index: usize, + num_references: usize, + ) -> SignedPartialTransactionV2 { + PartialTransactionV2Builder::new_with_test_defaults() + .intent_discriminator(subintent_index as u64) + .manifest_builder(|mut builder| { + for i in 0..num_references { + let mut address = + [EntityType::GlobalPreallocatedSecp256k1Account as u8; NodeId::LENGTH]; + address[1..9].copy_from_slice( + &(((subintent_index + 1) * 1000 + i) as u64).to_le_bytes(), + ); + builder = builder.call_method( + ComponentAddress::new_or_panic(address), + "method_name", + (), + ); + } + + builder.yield_to_parent(()) + }) + .sign(&Secp256k1PrivateKey::from_u64(1000 + subintent_index as u64).unwrap()) + .build_minimal() + } + + fn validate_transaction( + reference_counts: Vec, + ) -> Result { + TransactionV2Builder::new_with_test_defaults() + .add_children( + reference_counts + .iter() + .enumerate() + .map(|(i, reference_count)| { + create_partial_transaction(i, *reference_count) + }), + ) + .add_manifest_calling_each_child_once() + .sign(&Secp256k1PrivateKey::from_u64(1).unwrap()) + .default_notarize_and_validate() + } + + assert_matches!(validate_transaction(vec![100]), Ok(_)); + assert_matches!( + validate_transaction(vec![100, 600]), + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent(SubintentIndex(1), _), + IntentValidationError::TooManyReferences { + total: 600, + limit: 512, + } + )) + ); + assert_matches!( + validate_transaction(vec![500, 500]), + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::AcrossTransaction, + IntentValidationError::TooManyReferences { + total: 1001, // 1000 from subintent, 1 from transaction intent + limit: 512, + } + )) + ); + } + + #[test] + fn test_header_validations() { + let simulator_validator = TransactionValidator::new_for_latest_simulator(); + let network_agnostic_validator = + TransactionValidator::new_with_latest_config_network_agnostic(); + let config = simulator_validator.config().clone(); + + // InvalidEpochRange + { + // CASE 1 - Negative range + let result = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(98)) + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + )), + ); + + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(98)) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + )), + ); + + // CASE 2 - Equal range + let result = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(100)) + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + )), + ); + + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(100)) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + )), + ); + + // CASE 3 - Range too large + let result = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(100 + config.max_epoch_range + 1)) + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + )), + ); + + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(100 + config.max_epoch_range + 1)) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidEpochRange + ), + )), + ); + } + + // InvalidTimestampRange + { + // CASE 1 - Negative range + let result = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(5000))) + .max_proposer_timestamp_exclusive(Some(Instant::new(4999))) + .end_epoch_exclusive(Epoch::of(98)) + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidTimestampRange + ), + )), + ); + + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(5000))) + .max_proposer_timestamp_exclusive(Some(Instant::new(4999))) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidTimestampRange + ), + )), + ); + + // CASE 2 - Equal range + let result = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(5000))) + .max_proposer_timestamp_exclusive(Some(Instant::new(5000))) + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidTimestampRange + ), + )), + ); + + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(5000))) + .max_proposer_timestamp_exclusive(Some(Instant::new(5000))) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidTimestampRange + ), + )), + ); + + // And for good measure, let's test some valid ranges: + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(5000))) + .max_proposer_timestamp_exclusive(Some(Instant::new(5001))) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!(result, Ok(_),); + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(5000))) + .max_proposer_timestamp_exclusive(None) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!(result, Ok(_),); + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(None) + .max_proposer_timestamp_exclusive(Some(Instant::new(5000))) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!(result, Ok(_),); + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(None) + .max_proposer_timestamp_exclusive(None) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!(result, Ok(_),); + } + + // InvalidNetwork + { + let result = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .network_id(NetworkDefinition::mainnet().id) + .default_notarize() + .build_minimal_no_validate() + .prepare_and_validate(&simulator_validator); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidNetwork + ), + )), + ); + + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .network_id(NetworkDefinition::mainnet().id) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate() + .prepare_and_validate(&simulator_validator); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::InvalidNetwork + ), + )), + ); + + // And for good measure, demonstrate that the network agnostic validator is okay with this: + // (even with different intents being for different networks(!) - which is a bit weird, but + // it's only intended for testing) + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .network_id(NetworkDefinition::mainnet().id) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize() + .build_minimal_no_validate() + .prepare_and_validate(&network_agnostic_validator); + assert_matches!(result, Ok(_),); + } + + // InvalidTip + { + // Note - min tip is 0, so we can't hit that error + let result = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .tip_basis_points(config.max_tip_basis_points + 1) + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::RootTransactionIntent(_), + IntentValidationError::HeaderValidationError(HeaderValidationError::InvalidTip), + )), + ); + } + + // NoValidEpochRangeAcrossAllIntents + { + // Subintent doesn't overlap with TransactionIntent + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(102)) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .start_epoch_inclusive(Epoch::of(102)) + .end_epoch_exclusive(Epoch::of(103)) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::NoValidEpochRangeAcrossAllIntents + ), + )), + ); + + // Only one pair of subintents don't overlap + let subintent_1 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(102)) + .build(); + let subintent_2 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(101)) + .end_epoch_exclusive(Epoch::of(103)) + .build(); + let subintent_3 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(102)) + .end_epoch_exclusive(Epoch::of(104)) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent_1, subintent_2, subintent_3]) + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(105)) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::NoValidEpochRangeAcrossAllIntents + ), + )), + ); + + // There is an overlap + let subintent_1 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(102)) + .build(); + let subintent_2 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(101)) + .end_epoch_exclusive(Epoch::of(103)) + .build(); + let subintent_3 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .start_epoch_inclusive(Epoch::of(101)) + .end_epoch_exclusive(Epoch::of(104)) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent_1, subintent_2, subintent_3]) + .start_epoch_inclusive(Epoch::of(100)) + .end_epoch_exclusive(Epoch::of(105)) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Ok(validated) => { + assert_eq!(validated.overall_validity_range.epoch_range.start_epoch_inclusive, Epoch::of(101)); + assert_eq!(validated.overall_validity_range.epoch_range.end_epoch_exclusive, Epoch::of(102)); + }, + ); + } + + // NoValidTimestampRangeAcrossAllIntents + { + // Subintent doesn't overlap with TransactionIntent + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(5000))) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .max_proposer_timestamp_exclusive(Some(Instant::new(5000))) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::NoValidTimestampRangeAcrossAllIntents + ), + )), + ); + + // Only one pair of subintents don't overlap + let subintent_1 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .max_proposer_timestamp_exclusive(Some(Instant::new(4000))) + .build(); + let subintent_2 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .max_proposer_timestamp_exclusive(Some(Instant::new(4003))) + .build(); + let subintent_3 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(4001))) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent_1, subintent_2, subintent_3]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Err(TransactionValidationError::IntentValidationError( + TransactionValidationErrorLocation::NonRootSubintent { .. }, + IntentValidationError::HeaderValidationError( + HeaderValidationError::NoValidTimestampRangeAcrossAllIntents + ), + )), + ); + + // There is an overlap + let subintent_1 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .max_proposer_timestamp_exclusive(Some(Instant::new(4003))) + .build(); + let subintent_2 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .max_proposer_timestamp_exclusive(Some(Instant::new(4005))) + .build(); + let subintent_3 = PartialTransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .min_proposer_timestamp_inclusive(Some(Instant::new(3998))) + .max_proposer_timestamp_exclusive(Some(Instant::new(4001))) + .build(); + let result = TransactionV2Builder::new_with_test_defaults() + .add_children([subintent_1, subintent_2, subintent_3]) + .min_proposer_timestamp_inclusive(Some(Instant::new(3999))) + .max_proposer_timestamp_exclusive(Some(Instant::new(5999))) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate(); + assert_matches!( + result, + Ok(validated) => { + assert_eq!(validated.overall_validity_range.proposer_timestamp_range.start_timestamp_inclusive, Some(Instant::new(3999))); + assert_eq!(validated.overall_validity_range.proposer_timestamp_range.end_timestamp_exclusive, Some(Instant::new(4001))); + }, + ); + } + } + + trait ManifestBuilderExtensions { + fn add_test_method_call_with(self, value: impl ManifestEncode) -> Self; + } + + impl ManifestBuilderExtensions for ManifestBuilder + where + CallMethod: Into, + { + fn add_test_method_call_with(self, value: impl ManifestEncode) -> Self { + self.add_raw_instruction_ignoring_all_side_effects(CallMethod { + address: XRD.into(), + method_name: "method".into(), + args: manifest_decode(&manifest_encode(&(value,)).unwrap()).unwrap(), + }) + } + } + + #[test] + fn test_manifest_validations() { + let account_address = ComponentAddress::preallocated_account_from_public_key( + &Ed25519PublicKey([0; Ed25519PublicKey::LENGTH]), + ); + + fn validate_transaction_manifest( + manifest: TransactionManifestV2, + ) -> Result { + let builder = TransactionV2Builder::new_with_test_defaults().manifest(manifest); + validate_transaction_builder_manifest(builder) + } + + fn validate_transaction_builder_manifest( + builder: TransactionV2Builder, + ) -> Result { + builder + .default_notarize_and_validate() + .map_err(|err| match err { + TransactionValidationError::IntentValidationError( + _, + IntentValidationError::ManifestValidationError(err), + ) => err, + _ => panic!("Expected ManifestValidationError, but got: {:?}", err), + }) + } + + fn validate_subintent_manifest( + subintent_manifest: SubintentManifestV2, + ) -> Result { + let subintent = PartialTransactionV2Builder::new_with_test_defaults() + .manifest(subintent_manifest) + .build(); + TransactionV2Builder::new_with_test_defaults() + .add_children([subintent]) + .add_manifest_calling_each_child_once() + .default_notarize_and_validate() + .map_err(|err| match err { + TransactionValidationError::IntentValidationError( + _, + IntentValidationError::ManifestValidationError(err), + ) => err, + _ => panic!("Expected ManifestValidationError, but got: {:?}", err), + }) + } + + // DuplicateBlob(ManifestBlobRef) + { + // This is not actually possible to get in TransactionV2, because the manifest stores an IndexMap. + // Currently we remove duplicates at the `PreparedBlobsV1` layer. + } + + // BlobNotRegistered(ManifestBlobRef) + { + let transaction_manifest = ManifestBuilder::new_v2() + .add_test_method_call_with(ManifestBlobRef([2; 32])) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::BlobNotRegistered(ManifestBlobRef(blob_ref))) => { + assert_eq!(blob_ref, [2; 32]); + } + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .add_test_method_call_with(ManifestBlobRef([3; 32])) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::BlobNotRegistered(ManifestBlobRef(blob_ref))) => { + assert_eq!(blob_ref, [3; 32]); + } + ); + } + + // BucketNotYetCreated(ManifestBucket) + { + let transaction_manifest = ManifestBuilder::new_v2() + .add_test_method_call_with(ManifestBucket(2)) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::BucketNotYetCreated(bucket)) => { + assert_eq!(bucket, ManifestBucket(2)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .add_test_method_call_with(ManifestBucket(3)) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::BucketNotYetCreated(bucket)) => { + assert_eq!(bucket, ManifestBucket(3)); + } + ); + } + + // BucketAlreadyUsed(ManifestBucket, String) + { + let transaction_manifest = ManifestBuilder::new_v2() + .take_all_from_worktop(XRD, "reused_bucket") + .add_test_method_call_with(ManifestBucket(0)) + .add_test_method_call_with(ManifestBucket(0)) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::BucketAlreadyUsed(bucket, _)) => { + assert_eq!(bucket, ManifestBucket(0)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .take_all_from_worktop(XRD, "reused_bucket") + .add_test_method_call_with(ManifestBucket(0)) + .add_test_method_call_with(ManifestBucket(0)) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::BucketAlreadyUsed(bucket, _)) => { + assert_eq!(bucket, ManifestBucket(0)); + }, + ); + } + + // BucketConsumedWhilstLockedByProof(ManifestBucket, String) + { + let transaction_manifest = ManifestBuilder::new_v2() + .take_all_from_worktop(XRD, "my_bucket") + .create_proof_from_bucket_of_all("my_bucket", "my_proof") + .deposit(account_address, "my_bucket") + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::BucketConsumedWhilstLockedByProof(bucket, _)) => { + assert_eq!(bucket, ManifestBucket(0)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .take_all_from_worktop(XRD, "my_bucket") + .create_proof_from_bucket_of_all("my_bucket", "my_proof") + .deposit(account_address, "my_bucket") + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::BucketConsumedWhilstLockedByProof(bucket, _)) => { + assert_eq!(bucket, ManifestBucket(0)); + }, + ); + } + + // ProofNotYetCreated(ManifestProof) + { + let transaction_manifest = ManifestBuilder::new_v2() + .add_test_method_call_with(ManifestProof(2)) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::ProofNotYetCreated(proof)) => { + assert_eq!(proof, ManifestProof(2)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .add_test_method_call_with(ManifestProof(2)) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::ProofNotYetCreated(proof)) => { + assert_eq!(proof, ManifestProof(2)); + }, + ); + } + + // ProofAlreadyUsed(ManifestProof, String) + { + let transaction_manifest = ManifestBuilder::new_v2() + .create_proof_from_auth_zone_of_all(XRD, "proof") + .add_test_method_call_with(ManifestProof(0)) + .add_test_method_call_with(ManifestProof(0)) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::ProofAlreadyUsed(proof, _)) => { + assert_eq!(proof, ManifestProof(0)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .create_proof_from_auth_zone_of_all(XRD, "proof") + .add_test_method_call_with(ManifestProof(0)) + .add_test_method_call_with(ManifestProof(0)) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::ProofAlreadyUsed(proof, _)) => { + assert_eq!(proof, ManifestProof(0)); + }, + ); + } + + // AddressReservationNotYetCreated(ManifestAddressReservation) + { + let transaction_manifest = ManifestBuilder::new_v2() + .add_test_method_call_with(ManifestAddressReservation(2)) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::AddressReservationNotYetCreated(reservation)) => { + assert_eq!(reservation, ManifestAddressReservation(2)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .add_test_method_call_with(ManifestAddressReservation(2)) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::AddressReservationNotYetCreated(reservation)) => { + assert_eq!(reservation, ManifestAddressReservation(2)); + }, + ); + } + + // AddressReservationAlreadyUsed(ManifestAddressReservation, String) + { + let transaction_manifest = ManifestBuilder::new_v2() + .allocate_global_address( + RESOURCE_PACKAGE, + FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT, + "my_address_reservation", + "my_address", + ) + .add_test_method_call_with(ManifestAddressReservation(0)) + .add_test_method_call_with(ManifestAddressReservation(0)) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::AddressReservationAlreadyUsed(reservation, _)) => { + assert_eq!(reservation, ManifestAddressReservation(0)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .allocate_global_address( + RESOURCE_PACKAGE, + FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT, + "my_address_reservation", + "my_address", + ) + .add_test_method_call_with(ManifestAddressReservation(0)) + .add_test_method_call_with(ManifestAddressReservation(0)) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::AddressReservationAlreadyUsed(reservation, _)) => { + assert_eq!(reservation, ManifestAddressReservation(0)); + }, + ); + } + + // NamedAddressNotYetCreated(ManifestNamedAddress) + { + let transaction_manifest = ManifestBuilder::new_v2() + .add_test_method_call_with(ManifestAddress::Named(ManifestNamedAddress(2))) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::NamedAddressNotYetCreated(named_address)) => { + assert_eq!(named_address, ManifestNamedAddress(2)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .add_test_method_call_with(ManifestAddress::Named(ManifestNamedAddress(2))) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::NamedAddressNotYetCreated(named_address)) => { + assert_eq!(named_address, ManifestNamedAddress(2)); + }, + ); + } + + // ChildIntentNotRegistered(ManifestNamedIntent) + { + let transaction_manifest = ManifestBuilder::new_v2() + .add_raw_instruction_ignoring_all_side_effects(YieldToChild { + child_index: ManifestNamedIntentIndex(2), + args: ManifestValue::unit(), + }) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::ChildIntentNotRegistered(named_intent)) => { + assert_eq!(named_intent, ManifestNamedIntent(2)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .add_raw_instruction_ignoring_all_side_effects(YieldToChild { + child_index: ManifestNamedIntentIndex(3), + args: ManifestValue::unit(), + }) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::ChildIntentNotRegistered(named_intent)) => { + assert_eq!(named_intent, ManifestNamedIntent(3)); + }, + ); + } + + // DanglingBucket(ManifestBucket, String) + { + let transaction_manifest = ManifestBuilder::new_v2() + .take_all_from_worktop(XRD, "my_bucket") + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::DanglingBucket(bucket, _)) => { + assert_eq!(bucket, ManifestBucket(0)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .take_all_from_worktop(XRD, "my_bucket") + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::DanglingBucket(bucket, _)) => { + assert_eq!(bucket, ManifestBucket(0)); + }, + ); + } + + // DanglingAddressReservation(ManifestAddressReservation, String) + { + let transaction_manifest = ManifestBuilder::new_v2() + .allocate_global_address( + RESOURCE_PACKAGE, + FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT, + "my_address_reservation", + "my_address", + ) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::DanglingAddressReservation(reservation, _)) => { + assert_eq!(reservation, ManifestAddressReservation(0)); + }, + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .allocate_global_address( + RESOURCE_PACKAGE, + FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT, + "my_address_reservation", + "my_address", + ) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::DanglingAddressReservation(reservation, _)) => { + assert_eq!(reservation, ManifestAddressReservation(0)); + }, + ); + } + + // ArgsEncodeError(EncodeError) + { + // Hard to create when coming from a prepared transaction, because the values + // come from being decoded + } + + // ArgsDecodeError(DecodeError) + { + // Hard to create when coming from a prepared transaction, because the values + // come from being decoded + } + + // InstructionNotSupportedInTransactionIntent + { + // YIELD_TO_PARENT + let transaction_manifest = ManifestBuilder::new_v2() + .add_raw_instruction_ignoring_all_side_effects(YieldToParent { + args: ManifestValue::unit(), + }) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::InstructionNotSupportedInTransactionIntent), + ); + + // VERIFY_PARENT + let transaction_manifest = ManifestBuilder::new_v2() + .add_raw_instruction_ignoring_all_side_effects(VerifyParent { + access_rule: rule!(allow_all), + }) + .build_no_validate(); + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::InstructionNotSupportedInTransactionIntent), + ); + } + + // SubintentDoesNotEndWithYieldToParent + { + // CASE 1: At least 1 instruction + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .add_test_method_call_with(()) + .build_no_validate(); + + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::SubintentDoesNotEndWithYieldToParent), + ); + + // CASE 2: No instructions + let subintent_manifest = ManifestBuilder::new_subintent_v2().build_no_validate(); + + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::SubintentDoesNotEndWithYieldToParent), + ); + } + + // ProofCannotBePassedToAnotherIntent + { + let subintent = create_leaf_partial_transaction(0, 0); + let builder = ManifestBuilder::new_v2(); + let lookup = builder.name_lookup(); + let transaction_manifest = builder + .use_child("child_1", subintent.root_subintent_hash) + .create_proof_from_auth_zone_of_all(XRD, "my_proof") + .yield_to_child("child_1", (lookup.proof("my_proof"),)) + .build_no_validate(); + let builder = TransactionV2Builder::new_with_test_defaults() + .add_signed_child("child_1", subintent) + .manifest(transaction_manifest); + + assert_matches!( + validate_transaction_builder_manifest(builder), + Err(ManifestValidationError::ProofCannotBePassedToAnotherIntent), + ); + + let builder = ManifestBuilder::new_subintent_v2(); + let lookup = builder.name_lookup(); + let subintent_manifest = builder + .create_proof_from_auth_zone_of_all(XRD, "my_proof") + .yield_to_parent((lookup.proof("my_proof"),)) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::ProofCannotBePassedToAnotherIntent), + ); + } + + // TooManyInstructions + { + let mut builder = ManifestBuilder::new_v2(); + for _ in 0..1001 { + builder = builder.drop_all_proofs(); + } + let transaction_manifest = builder.build_no_validate(); + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::TooManyInstructions), + ); + // And test that one less is fine + let mut builder = ManifestBuilder::new_v2(); + for _ in 0..1000 { + builder = builder.drop_all_proofs(); + } + let transaction_manifest = builder.build_no_validate(); + assert_matches!(validate_transaction_manifest(transaction_manifest), Ok(_),); + + let mut builder = ManifestBuilder::new_subintent_v2(); + for _ in 0..1000 { + // Only 1000 because we're adding a yield_to_parent below + builder = builder.drop_all_proofs(); + } + let subintent_manifest = builder.yield_to_parent(()).build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::TooManyInstructions), + ); + // And test that one less is fine + let mut builder = ManifestBuilder::new_subintent_v2(); + for _ in 0..999 { + builder = builder.drop_all_proofs(); + } + let subintent_manifest = builder.yield_to_parent(()).build_no_validate(); + assert_matches!(validate_subintent_manifest(subintent_manifest), Ok(_),); + } + + // InvalidResourceConstraint + { + // Invalid because there's no overlap between `required_ids` and `allowed_ids` + let invalid_constraints = ManifestResourceConstraints::new().with_unchecked( + XRD, + ManifestResourceConstraint::General(GeneralResourceConstraint { + required_ids: indexset!(NonFungibleLocalId::integer(3)), + lower_bound: LowerBound::NonZero, + upper_bound: UpperBound::Unbounded, + allowed_ids: AllowedIds::Allowlist(indexset!( + NonFungibleLocalId::integer(4), + NonFungibleLocalId::integer(5), + )), + }), + ); + + let transaction_manifest = ManifestBuilder::new_v2() + .add_raw_instruction_ignoring_all_side_effects(AssertWorktopResourcesOnly { + constraints: invalid_constraints.clone(), + }) + .build_no_validate(); + + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::InvalidResourceConstraint), + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .assert_worktop_resources_only(invalid_constraints.clone()) + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::InvalidResourceConstraint), + ); + } + + // InstructionFollowingNextCallAssertionWasNotInvocation + { + let transaction_manifest = ManifestBuilder::new_v2() + .assert_next_call_returns_include(ManifestResourceConstraints::new()) + .drop_all_proofs() // This is not an invocation + .add_test_method_call_with(()) + .build_no_validate(); + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::InstructionFollowingNextCallAssertionWasNotInvocation), + ); + + let subintent_manifest = ManifestBuilder::new_subintent_v2() + .assert_next_call_returns_only(ManifestResourceConstraints::new()) + .drop_auth_zone_signature_proofs() + .yield_to_parent(()) + .build_no_validate(); + assert_matches!( + validate_subintent_manifest(subintent_manifest), + Err(ManifestValidationError::InstructionFollowingNextCallAssertionWasNotInvocation), + ); + } + + // ManifestEndedWhilstExpectingNextCallAssertion + { + let transaction_manifest = ManifestBuilder::new_v2() + .assert_next_call_returns_include(ManifestResourceConstraints::new()) + .build_no_validate(); + assert_matches!( + validate_transaction_manifest(transaction_manifest), + Err(ManifestValidationError::ManifestEndedWhilstExpectingNextCallAssertion), + ); + } + } + + #[test] + fn test_prepare_errors() { + let babylon_validator = TransactionValidator::new_with_static_config( + TransactionValidationConfig::babylon(), + NetworkDefinition::simulator().id, + ); + let latest_validator = TransactionValidator::new_for_latest_simulator(); + + fn create_unvalidated_notarized_transaction_from_manifest( + manifest: TransactionManifestV2, + ) -> NotarizedTransactionV2 { + NotarizedTransactionV2 { + signed_transaction_intent: SignedTransactionIntentV2 { + transaction_intent: TransactionIntentV2 { + transaction_header: + TransactionV2Builder::testing_default_transaction_header(), + root_intent_core: manifest.to_intent_core( + TransactionV2Builder::testing_default_intent_header(), + MessageV2::None, + ), + non_root_subintents: NonRootSubintentsV2(vec![]), + }, + transaction_intent_signatures: IntentSignaturesV2::none(), + non_root_subintent_signatures: NonRootSubintentSignaturesV2 { + by_subintent: vec![], + }, + }, + notary_signature: NotarySignatureV2(SignatureV1::Ed25519(Ed25519Signature( + [0; Ed25519Signature::LENGTH], + ))), + } + } + + fn create_unvalidated_raw_notarized_transaction_from_manifest( + manifest: TransactionManifestV2, + ) -> RawNotarizedTransaction { + let transaction = create_unvalidated_notarized_transaction_from_manifest(manifest); + let manually_encoded_transaction = manifest_encode_with_depth_limit( + &AnyTransaction::NotarizedTransactionV2(transaction), + 100, + ) + .unwrap(); + RawNotarizedTransaction::from_vec(manually_encoded_transaction) + } + + // TransactionTypeNotSupported + { + let transaction_v2 = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate(); + + assert_matches!( + transaction_v2.prepare_and_validate(&babylon_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::TransactionTypeNotSupported + )), + ); + } + + // TransactionTooLarge + { + let mut manifest_builder = ManifestBuilder::new_v2(); + manifest_builder.add_blob(vec![0; 1_100_000]); + let manifest = manifest_builder.build_no_validate(); + let transaction = TransactionV2Builder::new_with_test_defaults() + .manifest(manifest) + .default_notarize() + .build_minimal_no_validate(); + + assert_matches!( + transaction.prepare_and_validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::TransactionTooLarge + )), + ); + } + + // EncodeError(EncodeError) and DecodeError(DecodeError) + // Note that EncodeError doesn't happen as part of preparation in the node, only when preparing from + // an encodable model. But we can test it here anyway... + { + let mut nested_value = ManifestValue::unit(); + for _ in 0..50 { + nested_value = ManifestValue::tuple([nested_value]); + } + let manifest = ManifestBuilder::new_v2() + .add_raw_instruction_ignoring_all_side_effects(CallMethod { + address: XRD.into(), + method_name: "method".into(), + args: nested_value, + }) + .build_no_validate(); + + let transaction = + create_unvalidated_notarized_transaction_from_manifest(manifest.clone()); + + // We get an EncodeError when preparing directly from the model + assert_matches!( + transaction.prepare_and_validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::EncodeError(EncodeError::MaxDepthExceeded(24)) + )), + ); + + // We get a DecodeError when preparing directly from the raw transaction + let raw_transaction = + create_unvalidated_raw_notarized_transaction_from_manifest(manifest); + assert_matches!( + raw_transaction.validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::DecodeError(DecodeError::MaxDepthExceeded(24)) + )), + ); + } + + // TooManyValues { value_type: ValueType, actual: usize, max: usize, } + { + // Blob + { + let mut manifest_builder = ManifestBuilder::new_v2(); + for i in 0..65 { + manifest_builder.add_blob(vec![0; i as usize]); + } + let transaction = create_unvalidated_notarized_transaction_from_manifest( + manifest_builder.build_no_validate(), + ); + + assert_matches!( + transaction.prepare_and_validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::TooManyValues { + value_type: ValueType::Blob, + actual: 65, + max: 64, + } + )), + ); + } + + // Subintent + { + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate(); + let subintents = (0..33) + .map(|i| { + create_leaf_partial_transaction(i, 0) + .partial_transaction + .partial_transaction + .root_subintent + }) + .collect::>(); + transaction + .signed_transaction_intent + .transaction_intent + .non_root_subintents = NonRootSubintentsV2(subintents); + assert_matches!( + transaction.prepare_and_validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::TooManyValues { + value_type: ValueType::Subintent, + actual: 33, + max: 32, + } + )), + ); + } + + // ChildSubintentSpecifier + { + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate(); + let child_specifiers = (0..33) + .map(|i| ChildSubintentSpecifier { + hash: SubintentHash::from_bytes([i as u8; Hash::LENGTH]), + }) + .collect(); + transaction + .signed_transaction_intent + .transaction_intent + .root_intent_core + .children = ChildSubintentSpecifiersV2 { + children: child_specifiers, + }; + assert_matches!( + transaction.prepare_and_validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::TooManyValues { + value_type: ValueType::ChildSubintentSpecifier, + actual: 33, + max: 32, + } + )), + ); + } + + // SubintentSignatureBatches + { + let mut transaction = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate(); + let subintent_signature_batches = (0..33) + .map(|_| IntentSignaturesV2::none()) + .collect::>(); + transaction + .signed_transaction_intent + .non_root_subintent_signatures = NonRootSubintentSignaturesV2 { + by_subintent: subintent_signature_batches, + }; + assert_matches!( + transaction.prepare_and_validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::TooManyValues { + value_type: ValueType::SubintentSignatureBatches, + actual: 33, + max: 32, + } + )), + ); + } + } + + // LengthOverflow + // -> Rather hard to test, we can leave this. + + // UnexpectedTransactionDiscriminator + { + let raw_transaction = TransactionV2Builder::new_with_test_defaults() + .add_trivial_manifest() + .default_notarize() + .build_minimal_no_validate() + .to_raw() + .unwrap(); + + let mut amended_payload = raw_transaction.to_vec(); + amended_payload[2] = 4; + let amended_raw = RawNotarizedTransaction::from_vec(amended_payload); + assert_eq!( + amended_raw.validate(&latest_validator), + Err(TransactionValidationError::PrepareError( + PrepareError::UnexpectedTransactionDiscriminator { actual: Some(4) } + )) + ) + } + } +} diff --git a/radix-transactions/src/validation/validation_test_helpers.rs b/radix-transactions/src/validation/validation_test_helpers.rs new file mode 100644 index 00000000000..32a37c388ba --- /dev/null +++ b/radix-transactions/src/validation/validation_test_helpers.rs @@ -0,0 +1,267 @@ +// NOTE: +// This file is only included if #[cfg(test)] is present + +use crate::internal_prelude::*; + +pub(crate) fn unsigned_v1_builder(notary_public_key: PublicKey) -> TransactionV1Builder { + TransactionBuilder::new() + .header(TransactionHeaderV1 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive: Epoch::of(1), + end_epoch_exclusive: Epoch::of(10), + nonce: 0, + notary_public_key, + notary_is_signatory: false, + tip_percentage: 5, + }) + .manifest(ManifestBuilder::new().drop_auth_zone_proofs().build()) +} + +// All of these are only added when in #[cfg(test)] +impl TransactionV2Builder { + pub fn testing_default_notary() -> Ed25519PrivateKey { + Ed25519PrivateKey::from_u64(1337).unwrap() + } + + pub fn testing_default_intent_header() -> IntentHeaderV2 { + IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive: Epoch::of(0), + end_epoch_exclusive: Epoch::of(1), + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 0, + } + } + + pub fn testing_default_transaction_header() -> TransactionHeaderV2 { + TransactionHeaderV2 { + notary_public_key: Self::testing_default_notary().public_key().into(), + notary_is_signatory: false, + tip_basis_points: 0, + } + } + + pub fn new_with_test_defaults() -> Self { + Self::new() + .intent_header(Self::testing_default_intent_header()) + .transaction_header(Self::testing_default_transaction_header()) + } + + pub fn add_children( + mut self, + children: impl IntoIterator, + ) -> Self { + for (i, child) in children.into_iter().enumerate() { + let child_name = format!("child_{i}"); + self = self.add_signed_child(child_name, child); + } + self + } + + pub fn add_trivial_manifest(self) -> Self { + self.manifest( + ManifestBuilder::new_v2() + .drop_named_proofs() + .build_no_validate(), + ) + } + + /// It calls into each child once + pub fn add_manifest_calling_each_child_once(self) -> Self { + let child_names = self + .child_partial_transactions + .keys() + .cloned() + .collect::>(); + self.manifest_builder(|mut builder| { + builder = builder.lock_fee_from_faucet(); + for child_name in child_names { + builder = builder.yield_to_child(child_name, ()); + } + builder + }) + } + + pub fn transaction_header_mut(&mut self) -> &mut TransactionHeaderV2 { + self.transaction_header.as_mut().expect( + "Transaction Header should already have been set, e.g. via new_with_test_defaults()", + ) + } + + pub fn notary_is_signatory(mut self, notary_is_signatory: bool) -> Self { + self.transaction_header_mut().notary_is_signatory = notary_is_signatory; + self + } + + pub fn notary_public_key(mut self, notary_public_key: impl Into) -> Self { + self.transaction_header_mut().notary_public_key = notary_public_key.into(); + self + } + + pub fn tip_basis_points(mut self, tip_basis_points: u32) -> Self { + self.transaction_header_mut().tip_basis_points = tip_basis_points; + self + } + + pub fn intent_header_mut(&mut self) -> &mut IntentHeaderV2 { + self.transaction_intent_header + .as_mut() + .expect("Intent Header should already have been set, e.g. via new_with_test_defaults()") + } + + pub fn network_id(mut self, network_id: u8) -> Self { + self.intent_header_mut().network_id = network_id; + self + } + + pub fn start_epoch_inclusive(mut self, start_epoch_inclusive: Epoch) -> Self { + self.intent_header_mut().start_epoch_inclusive = start_epoch_inclusive; + self + } + + pub fn end_epoch_exclusive(mut self, end_epoch_exclusive: Epoch) -> Self { + self.intent_header_mut().end_epoch_exclusive = end_epoch_exclusive; + self + } + + pub fn min_proposer_timestamp_inclusive( + mut self, + min_proposer_timestamp_inclusive: Option, + ) -> Self { + self.intent_header_mut().min_proposer_timestamp_inclusive = + min_proposer_timestamp_inclusive; + self + } + + pub fn max_proposer_timestamp_exclusive( + mut self, + max_proposer_timestamp_exclusive: Option, + ) -> Self { + self.intent_header_mut().max_proposer_timestamp_exclusive = + max_proposer_timestamp_exclusive; + self + } + + pub fn intent_discriminator(mut self, intent_discriminator: u64) -> Self { + self.intent_header_mut().intent_discriminator = intent_discriminator; + self + } + + pub fn default_notarize(self) -> Self { + self.notarize(&Self::testing_default_notary()) + } + + pub fn default_notarize_and_validate( + self, + ) -> Result { + self.default_notarize() + .build_minimal_no_validate() + .prepare_and_validate(&TransactionValidator::new_for_latest_simulator()) + } +} + +// All of these are only added when in #[cfg(test)] +impl PartialTransactionV2Builder { + pub fn new_with_test_defaults() -> Self { + Self::new().intent_header(IntentHeaderV2 { + network_id: NetworkDefinition::simulator().id, + start_epoch_inclusive: Epoch::of(0), + end_epoch_exclusive: Epoch::of(1), + min_proposer_timestamp_inclusive: None, + max_proposer_timestamp_exclusive: None, + intent_discriminator: 0, + }) + } + + pub fn add_children( + mut self, + children: impl IntoIterator, + ) -> Self { + for (i, child) in children.into_iter().enumerate() { + let child_name = format!("child_{i}"); + self = self.add_signed_child(child_name, child); + } + self + } + + pub fn add_trivial_manifest(self) -> Self { + self.manifest( + ManifestBuilder::new_subintent_v2() + .yield_to_parent(()) + .build_no_validate(), + ) + } + + /// It calls into each child once + pub fn add_manifest_calling_each_child_once(self) -> Self { + let child_names = self + .child_partial_transactions + .keys() + .cloned() + .collect::>(); + self.manifest_builder(|mut builder| { + for child_name in child_names { + builder = builder.yield_to_child(child_name, ()); + } + builder + }) + } + + pub fn intent_header_mut(&mut self) -> &mut IntentHeaderV2 { + self.root_subintent_header + .as_mut() + .expect("Intent Header should already have been set, e.g. via new_with_test_defaults()") + } + + pub fn network_id(mut self, network_id: u8) -> Self { + self.intent_header_mut().network_id = network_id; + self + } + + pub fn start_epoch_inclusive(mut self, start_epoch_inclusive: Epoch) -> Self { + self.intent_header_mut().start_epoch_inclusive = start_epoch_inclusive; + self + } + + pub fn end_epoch_exclusive(mut self, end_epoch_exclusive: Epoch) -> Self { + self.intent_header_mut().end_epoch_exclusive = end_epoch_exclusive; + self + } + + pub fn min_proposer_timestamp_inclusive( + mut self, + min_proposer_timestamp_inclusive: Option, + ) -> Self { + self.intent_header_mut().min_proposer_timestamp_inclusive = + min_proposer_timestamp_inclusive; + self + } + + pub fn max_proposer_timestamp_exclusive( + mut self, + max_proposer_timestamp_exclusive: Option, + ) -> Self { + self.intent_header_mut().max_proposer_timestamp_exclusive = + max_proposer_timestamp_exclusive; + self + } + + pub fn intent_discriminator(mut self, intent_discriminator: u64) -> Self { + self.intent_header_mut().intent_discriminator = intent_discriminator; + self + } +} + +pub(crate) fn create_leaf_partial_transaction( + intent_discriminator: u64, + num_signatures: usize, +) -> DetailedSignedPartialTransactionV2 { + PartialTransactionV2Builder::new_with_test_defaults() + .intent_discriminator(intent_discriminator) + .add_trivial_manifest() + .multi_sign((0..num_signatures).into_iter().map(|i| { + Secp256k1PrivateKey::from_u64((intent_discriminator + 1) * 1000 + (i as u64)).unwrap() + })) + .build() +} diff --git a/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_1.diag b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_1.diag new file mode 100644 index 00000000000..59a54b0e77c --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_1.diag @@ -0,0 +1,11 @@ +error: an argument's structure does not fit with the radix_common::data::manifest::model::manifest_resource_assertion::ManifestResourceConstraints type. [ERROR] byte offset: 36-38, value path: ManifestResourceConstraints.[0].Value->ManifestResourceConstraint::{8}, cause: { unknown_variant_id: 8 } + | +1 | ASSERT_WORKTOP_RESOURCES_INCLUDE +2 | Map( + | ^^^ cannot be decoded as a radix_common::data::manifest::model::manifest_resource_assertion::ManifestResourceConstraints +3 | Address("resource_sim1thqu8jcc3zh8ukuh0rwtllr84dgrd3z8j9zjdelkx3zf4kjasgvnm8") => Enum<8u8>( +4 | Decimal("10") +5 | ) +6 | ) +7 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_1.rtm new file mode 100644 index 00000000000..3fa56747833 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_1.rtm @@ -0,0 +1,7 @@ +ASSERT_WORKTOP_RESOURCES_INCLUDE + Map( + Address("resource_sim1thqu8jcc3zh8ukuh0rwtllr84dgrd3z8j9zjdelkx3zf4kjasgvnm8") => Enum<8u8>( + Decimal("10") + ) + ) +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_2.diag b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_2.diag new file mode 100644 index 00000000000..98e6a751d5b --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_2.diag @@ -0,0 +1,11 @@ +error: an argument's structure does not fit with the radix_common::data::manifest::model::manifest_resource_assertion::ManifestResourceConstraint type. [ERROR] byte offset: 1-26, value path: [Root], cause: { expected_type: Enum, found: Custom(Decimal) } + | +2 | Address("resource_sim1thqu8jcc3zh8ukuh0rwtllr84dgrd3z8j9zjdelkx3zf4kjasgvnm8") +3 | Bucket("my_bucket") +4 | ; +5 | ASSERT_BUCKET_CONTENTS +6 | Bucket("my_bucket") +7 | Decimal("10") + | ^^^^^^^ cannot be decoded as a radix_common::data::manifest::model::manifest_resource_assertion::ManifestResourceConstraint +8 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_2.rtm b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_2.rtm new file mode 100644 index 00000000000..5bf7bacfb68 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_2.rtm @@ -0,0 +1,8 @@ +TAKE_ALL_FROM_WORKTOP + Address("resource_sim1thqu8jcc3zh8ukuh0rwtllr84dgrd3z8j9zjdelkx3zf4kjasgvnm8") + Bucket("my_bucket") +; +ASSERT_BUCKET_CONTENTS + Bucket("my_bucket") + Decimal("10") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_3.diag b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_3.diag new file mode 100644 index 00000000000..9c39c013ba9 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_3.diag @@ -0,0 +1,7 @@ +error: an argument's structure does not fit with the radix_engine_interface::blueprints::resource::proof_rule::AccessRule type. [ERROR] byte offset: 1-3, value path: [Root], cause: { expected_type: Enum, found: Tuple } + | +1 | VERIFY_PARENT +2 | Tuple() + | ^^^^^ cannot be decoded as a radix_engine_interface::blueprints::resource::proof_rule::AccessRule +3 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_3.rtm b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_3.rtm new file mode 100644 index 00000000000..46a27ab9016 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_argument_could_not_be_read_as_expected_type_3.rtm @@ -0,0 +1,3 @@ +VERIFY_PARENT + Tuple() +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_blob_not_found_1.diag b/radix-transactions/tests/assets/manifest_generator_error_blob_not_found_1.diag index 6f7b2ac96f1..61a2198dfa3 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_blob_not_found_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_blob_not_found_1.diag @@ -1,15 +1,15 @@ -error: blob with hash 'f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1' not found - | -12 | PUBLISH_PACKAGE_ADVANCED -13 | Enum() -14 | Tuple( -15 | Map() -16 | ) -17 | Blob("f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ blob not found -18 | Map() -19 | Some(AddressReservation("my_reservation")) -20 | ; -21 | CALL_FUNCTION -22 | NamedAddress("my_package") - | \ No newline at end of file +error: blob with hash 'f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1' not found + | +12 | PUBLISH_PACKAGE_ADVANCED +13 | Enum() +14 | Tuple( +15 | Map() +16 | ) +17 | Blob("f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ blob not found +18 | Map() +19 | Some(AddressReservation("my_reservation")) +20 | ; +21 | CALL_FUNCTION +22 | NamedAddress("my_package") + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_bucket_locked_1.diag b/radix-transactions/tests/assets/manifest_generator_error_bucket_locked_1.diag index 700ab8e825c..b263cd9f252 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_bucket_locked_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_bucket_locked_1.diag @@ -1,11 +1,11 @@ -error: cannot consume bucket 'some_bucket' because it's believed to be currently locked - | -18 | Bucket("some_bucket") -19 | Decimal("1") -20 | Proof("proof"); -21 | -22 | RETURN_TO_WORKTOP -23 | Bucket("some_bucket"); - | ^^^^^^^^^^^^^ bucket locked -24 | - | \ No newline at end of file +error: cannot consume bucket 'some_bucket' because it's believed to be currently locked + | +18 | Bucket("some_bucket") +19 | Decimal("1") +20 | Proof("proof"); +21 | +22 | RETURN_TO_WORKTOP +23 | Bucket("some_bucket"); + | ^^^^^^^^^^^^^ bucket locked +24 | + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_1.diag b/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_1.diag index d9f38247632..c6636b835de 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_1.diag @@ -1,15 +1,15 @@ -error: bucket id 'ManifestBucket(1)' not found - | - 7 | TAKE_ALL_FROM_WORKTOP - 8 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") - 9 | Bucket("some_xrd"); -10 | -11 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT -12 | Bucket(1u32) - | ^^^^ bucket not found -13 | Decimal("1") -14 | Proof("proof"); -15 | -16 | DROP_PROOF -17 | Proof("proof"); - | \ No newline at end of file +error: bucket id 'ManifestBucket(1)' not found + | + 7 | TAKE_ALL_FROM_WORKTOP + 8 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") + 9 | Bucket("some_xrd"); +10 | +11 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT +12 | Bucket(1u32) + | ^^^^ bucket not found +13 | Decimal("1") +14 | Proof("proof"); +15 | +16 | DROP_PROOF +17 | Proof("proof"); + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_2.diag b/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_2.diag index d90077a7c84..b401a209f67 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_bucket_not_found_2.diag @@ -1,10 +1,10 @@ -error: bucket id 'ManifestBucket(1)' not found - | - 7 | TAKE_ALL_FROM_WORKTOP - 8 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") - 9 | Bucket("some_xrd"); -10 | -11 | BURN_RESOURCE -12 | Bucket(1u32); - | ^^^^ bucket not found - | \ No newline at end of file +error: bucket id 'ManifestBucket(1)' not found + | + 7 | TAKE_ALL_FROM_WORKTOP + 8 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") + 9 | Bucket("some_xrd"); +10 | +11 | BURN_RESOURCE +12 | Bucket(1u32); + | ^^^^ bucket not found + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_child_subintents_unsupported_by_manifest_type_1.diag b/radix-transactions/tests/assets/manifest_generator_error_child_subintents_unsupported_by_manifest_type_1.diag new file mode 100644 index 00000000000..0937897f28c --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_child_subintents_unsupported_by_manifest_type_1.diag @@ -0,0 +1,8 @@ +error: child subintents are not supported in this manifest type + | +1 | / USE_CHILD +2 | | NamedIntent("my_child") +3 | | Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +4 | | ; + | |_^ unsupported instruction + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_child_subintents_unsupported_by_manifest_type_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_child_subintents_unsupported_by_manifest_type_1.rtm new file mode 100644 index 00000000000..e6d8e9232c5 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_child_subintents_unsupported_by_manifest_type_1.rtm @@ -0,0 +1,4 @@ +USE_CHILD + NamedIntent("my_child") + Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_duplicate_subintent_hash_1.diag b/radix-transactions/tests/assets/manifest_generator_error_duplicate_subintent_hash_1.diag new file mode 100644 index 00000000000..7edfc392d4f --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_duplicate_subintent_hash_1.diag @@ -0,0 +1,12 @@ +error: child subintents cannot have the same hash + | +1 | USE_CHILD +2 | NamedIntent("my_child") +3 | Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +4 | ; +5 | / USE_CHILD +6 | | NamedIntent("my_second_child") +7 | | Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +8 | | ; + | |_^ duplicate hash + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_duplicate_subintent_hash_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_duplicate_subintent_hash_1.rtm new file mode 100644 index 00000000000..732a3e3b23d --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_duplicate_subintent_hash_1.rtm @@ -0,0 +1,8 @@ +USE_CHILD + NamedIntent("my_child") + Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +; +USE_CHILD + NamedIntent("my_second_child") + Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_header_instruction_must_come_first_1.diag b/radix-transactions/tests/assets/manifest_generator_error_header_instruction_must_come_first_1.diag new file mode 100644 index 00000000000..6cac57f9cc6 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_header_instruction_must_come_first_1.diag @@ -0,0 +1,9 @@ +error: a psuedo-instruction such as USE_CHILD must come before all other instructions + | +1 | DROP_ALL_PROOFS; +2 | / USE_CHILD +3 | | NamedIntent("my_child") +4 | | Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +5 | | ; + | |_^ must be at the start of the manifest + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_header_instruction_must_come_first_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_header_instruction_must_come_first_1.rtm new file mode 100644 index 00000000000..1f76645ebbe --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_header_instruction_must_come_first_1.rtm @@ -0,0 +1,5 @@ +DROP_ALL_PROOFS; +USE_CHILD + NamedIntent("my_child") + Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_instruction_not_supported_in_manifest_version_1.diag b/radix-transactions/tests/assets/manifest_generator_error_instruction_not_supported_in_manifest_version_1.diag new file mode 100644 index 00000000000..b1c3b00cddf --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_instruction_not_supported_in_manifest_version_1.diag @@ -0,0 +1,5 @@ +error: unsupported instruction for this manifest version + | +1 | YIELD_TO_PARENT; + | ^^^^^^^^^^^^^^^^ unsupported instruction + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_instruction_not_supported_in_manifest_version_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_instruction_not_supported_in_manifest_version_1.rtm new file mode 100644 index 00000000000..8c1cf6d7144 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_instruction_not_supported_in_manifest_version_1.rtm @@ -0,0 +1 @@ +YIELD_TO_PARENT; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_as_value_kind_1.diag b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_as_value_kind_1.diag new file mode 100644 index 00000000000..3243f202e1c --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_as_value_kind_1.diag @@ -0,0 +1,9 @@ +error: an Intent cannot be used as a value kind + | +1 | CALL_METHOD +2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +3 | "my_method" +4 | Array() + | ^^^^^^ cannot be used as a value kind +5 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_as_value_kind_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_as_value_kind_1.rtm new file mode 100644 index 00000000000..02dbb30ee70 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_as_value_kind_1.rtm @@ -0,0 +1,5 @@ +CALL_METHOD + Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + "my_method" + Array() +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_in_value_1.diag b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_in_value_1.diag new file mode 100644 index 00000000000..281acb638ce --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_in_value_1.diag @@ -0,0 +1,9 @@ +error: an Intent(...) cannot currently be used inside a value + | +1 | CALL_METHOD +2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +3 | "my_method" +4 | Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") + | ^^^^^^ cannot be used inside a value +5 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_in_value_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_in_value_1.rtm new file mode 100644 index 00000000000..b147cab9fbb --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_intent_cannot_be_used_in_value_1.rtm @@ -0,0 +1,5 @@ +CALL_METHOD + Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + "my_method" + Intent("subtxid_sim1f0dp5mcrzvgnujw33lgnjy9usluaek6edfkqdjkj5vhuqf8t7apqnaqedd") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_type_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_type_1.diag index 08eee13ac3d..816d18a8043 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_type_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_type_1.diag @@ -1,15 +1,15 @@ -error: expected NonFungibleLocalId, found String - | - 1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "withdraw" Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Decimal("5.0"); - 2 | - 3 | # Create a proof from bucket, clone it and drop both - 4 | TAKE_ALL_FROM_WORKTOP Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Bucket("some_xrd"); - 5 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") Decimal("1") Proof("proof1a"); - 6 | CREATE_PROOF_FROM_BUCKET_OF_NON_FUNGIBLES Bucket("some_xrd") Array("some_string") Proof("proof1b"); - | ^^^^^^ expected NonFungibleLocalId - 7 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1c"); - 8 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); - 9 | DROP_PROOF Proof("proof1d"); -10 | DROP_PROOF Proof("proof1c"); -11 | DROP_AUTH_ZONE_PROOFS; - | \ No newline at end of file +error: expected NonFungibleLocalId, found String + | + 1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "withdraw" Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Decimal("5.0"); + 2 | + 3 | # Create a proof from bucket, clone it and drop both + 4 | TAKE_ALL_FROM_WORKTOP Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Bucket("some_xrd"); + 5 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") Decimal("1") Proof("proof1a"); + 6 | CREATE_PROOF_FROM_BUCKET_OF_NON_FUNGIBLES Bucket("some_xrd") Array("some_string") Proof("proof1b"); + | ^^^^^^ expected NonFungibleLocalId + 7 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1c"); + 8 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); + 9 | DROP_PROOF Proof("proof1d"); +10 | DROP_PROOF Proof("proof1c"); +11 | DROP_AUTH_ZONE_PROOFS; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_1.diag index 54f96d3133c..7e13b060a93 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_1.diag @@ -1,11 +1,11 @@ -error: expected String, found U32 - | -1 | CALL_METHOD -2 | Address(100u32) - | ^^^^^^ expected String -3 | "lock_fee" -4 | ; -5 | CALL_FUNCTION -6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") -7 | "Hello" - | \ No newline at end of file +error: expected String, found U32 + | +1 | CALL_METHOD +2 | Address(100u32) + | ^^^^^^ expected String +3 | "lock_fee" +4 | ; +5 | CALL_FUNCTION +6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") +7 | "Hello" + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_2.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_2.diag index 994ff43d6f0..1f4541e6743 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_2.diag @@ -1,11 +1,11 @@ -error: expected String, found Decimal - | - 6 | CALL_METHOD - 7 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - 8 | "create_proof_of_amount" - 9 | Address("resource_sim1t5lam43hk5fg9h2x7a8zgpfxsd78539kzm4h0xugldymfexuh2wzru") -10 | Expression( -11 | Decimal("1") - | ^^^^^^^ expected String -12 | ); - | \ No newline at end of file +error: expected String, found Decimal + | + 6 | CALL_METHOD + 7 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + 8 | "create_proof_of_amount" + 9 | Address("resource_sim1t5lam43hk5fg9h2x7a8zgpfxsd78539kzm4h0xugldymfexuh2wzru") +10 | Expression( +11 | Decimal("1") + | ^^^^^^^ expected String +12 | ); + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_3.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_3.diag index 0c40c5ed9db..caeabfd6453 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_3.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_3.diag @@ -1,15 +1,15 @@ -error: expected Address or NamedAddress, found String - | - 8 | "create_proof_of_amount" - 9 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") -10 | Decimal("1"); -11 | -12 | MINT_FUNGIBLE -13 | "component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh" - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected Address or NamedAddress -14 | Decimal("20"); -15 | -16 | CALL_METHOD -17 | Address("${account_address}") -18 | "deposit_batch" - | \ No newline at end of file +error: expected Address or NamedAddress, found String + | + 8 | "create_proof_of_amount" + 9 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") +10 | Decimal("1"); +11 | +12 | MINT_FUNGIBLE +13 | "component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh" + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected Address or NamedAddress +14 | Decimal("20"); +15 | +16 | CALL_METHOD +17 | Address("${account_address}") +18 | "deposit_batch" + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_4.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_4.diag index 51848754627..49ffe39d905 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_4.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_4.diag @@ -1,15 +1,15 @@ -error: expected NonFungibleLocalId, found U32 - | - 1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "withdraw" Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Decimal("5.0"); - 2 | - 3 | # Create a proof from bucket, clone it and drop both - 4 | TAKE_ALL_FROM_WORKTOP Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Bucket("some_xrd"); - 5 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") Decimal("1") Proof("proof1a"); - 6 | CREATE_PROOF_FROM_BUCKET_OF_NON_FUNGIBLES Bucket("some_xrd") Array(1u32) Proof("proof1b"); - | ^^^^ expected NonFungibleLocalId - 7 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1c"); - 8 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); - 9 | DROP_PROOF Proof("proof1d"); -10 | DROP_PROOF Proof("proof1c"); -11 | DROP_AUTH_ZONE_PROOFS; - | \ No newline at end of file +error: expected NonFungibleLocalId, found U32 + | + 1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "withdraw" Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Decimal("5.0"); + 2 | + 3 | # Create a proof from bucket, clone it and drop both + 4 | TAKE_ALL_FROM_WORKTOP Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Bucket("some_xrd"); + 5 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") Decimal("1") Proof("proof1a"); + 6 | CREATE_PROOF_FROM_BUCKET_OF_NON_FUNGIBLES Bucket("some_xrd") Array(1u32) Proof("proof1b"); + | ^^^^ expected NonFungibleLocalId + 7 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1c"); + 8 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); + 9 | DROP_PROOF Proof("proof1d"); +10 | DROP_PROOF Proof("proof1c"); +11 | DROP_AUTH_ZONE_PROOFS; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_5.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_5.diag index 858487a368b..c63c806ba2b 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_5.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_ast_value_5.diag @@ -1,11 +1,11 @@ -error: expected String, found Bool - | - 6 | CALL_METHOD - 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 8 | "some_method" - 9 | "text mixed with 🤓 and 🥸 unicodes" -10 | "\uD83D\uDC69" -11 | Decimal(true) - | ^^^^ expected String -12 | ; - | \ No newline at end of file +error: expected String, found Bool + | + 6 | CALL_METHOD + 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 8 | "some_method" + 9 | "text mixed with 🤓 and 🥸 unicodes" +10 | "\uD83D\uDC69" +11 | Decimal(true) + | ^^^^ expected String +12 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_1.diag index 328c2e55bae..6ba19d89a25 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_1.diag @@ -1,15 +1,15 @@ -error: invalid blob hash 'abbcc' - invalid hex value - | -12 | PUBLISH_PACKAGE_ADVANCED -13 | Enum() -14 | Tuple( -15 | Map() -16 | ) -17 | Blob("abbcc") - | ^^^^^^^ invalid blob hash -18 | Map() -19 | Some(AddressReservation("my_reservation")) -20 | ; -21 | CALL_FUNCTION -22 | NamedAddress("my_package") - | \ No newline at end of file +error: invalid blob hash 'abbcc' - invalid hex value + | +12 | PUBLISH_PACKAGE_ADVANCED +13 | Enum() +14 | Tuple( +15 | Map() +16 | ) +17 | Blob("abbcc") + | ^^^^^^^ invalid blob hash +18 | Map() +19 | Some(AddressReservation("my_reservation")) +20 | ; +21 | CALL_FUNCTION +22 | NamedAddress("my_package") + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_2.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_2.diag index 9761242d5e2..60868b5606f 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_blob_hash_2.diag @@ -1,15 +1,15 @@ -error: invalid blob hash 'aabbcc' - invalid hash length 3, expected 32 - | -12 | PUBLISH_PACKAGE_ADVANCED -13 | Enum() -14 | Tuple( -15 | Map() -16 | ) -17 | Blob("aabbcc") - | ^^^^^^^^ invalid blob hash -18 | Map() -19 | Some(AddressReservation("my_reservation")) -20 | ; -21 | CALL_FUNCTION -22 | NamedAddress("my_package") - | \ No newline at end of file +error: invalid blob hash 'aabbcc' - invalid hash length 3, expected 32 + | +12 | PUBLISH_PACKAGE_ADVANCED +13 | Enum() +14 | Tuple( +15 | Map() +16 | ) +17 | Blob("aabbcc") + | ^^^^^^^^ invalid blob hash +18 | Map() +19 | Some(AddressReservation("my_reservation")) +20 | ; +21 | CALL_FUNCTION +22 | NamedAddress("my_package") + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_bytes_hex_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_bytes_hex_1.diag index 2d42635b6a5..3eb344cb2f5 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_bytes_hex_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_bytes_hex_1.diag @@ -1,11 +1,11 @@ -error: invalid hex value 'invalid_hex' - | - 4 | ; - 5 | CALL_FUNCTION - 6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") - 7 | "Hello" - 8 | "instantiate_hello" - 9 | Bytes("invalid_hex") - | ^^^^^^^^^^^^^ invalid hex value -10 | ; - | \ No newline at end of file +error: invalid hex value 'invalid_hex' + | + 4 | ; + 5 | CALL_FUNCTION + 6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") + 7 | "Hello" + 8 | "instantiate_hello" + 9 | Bytes("invalid_hex") + | ^^^^^^^^^^^^^ invalid hex value +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_1.diag index 7a120a91968..c692b27bada 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_1.diag @@ -1,11 +1,11 @@ -error: invalid decimal '1.000000000000000011111111' - MoreThanEighteenDecimalPlaces - | - 8 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") - 9 | Bucket("some_xrd"); -10 | -11 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT -12 | Bucket("some_xrd") -13 | Decimal("1.000000000000000011111111") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid decimal -14 | Proof("proof1a"); - | \ No newline at end of file +error: invalid decimal '1.000000000000000011111111' - MoreThanEighteenDecimalPlaces + | + 8 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") + 9 | Bucket("some_xrd"); +10 | +11 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT +12 | Bucket("some_xrd") +13 | Decimal("1.000000000000000011111111") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid decimal +14 | Proof("proof1a"); + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_2.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_2.diag index 1ef179e6353..2d1f23b1403 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_2.diag @@ -1,11 +1,11 @@ -error: invalid decimal '基数引擎' - InvalidDigit - | - 4 | ; - 5 | - 6 | CALL_METHOD - 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 8 | "some_method" - 9 | Decimal("基数引擎") - | ^^^^^^^^^^ invalid decimal -10 | ; - | \ No newline at end of file +error: invalid decimal '基数引擎' - InvalidDigit + | + 4 | ; + 5 | + 6 | CALL_METHOD + 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 8 | "some_method" + 9 | Decimal("基数引擎") + | ^^^^^^^^^^ invalid decimal +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_3.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_3.diag index 0c440621062..00d62a99e59 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_3.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_decimal_3.diag @@ -1,11 +1,11 @@ -error: invalid decimal '👩' - InvalidDigit - | - 4 | ; - 5 | - 6 | CALL_METHOD - 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 8 | "some_method" - 9 | Decimal("\uD83D\uDC69") - | ^^^^^^^^^^^^^^ invalid decimal -10 | ; - | \ No newline at end of file +error: invalid decimal '👩' - InvalidDigit + | + 4 | ; + 5 | + 6 | CALL_METHOD + 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 8 | "some_method" + 9 | Decimal("\uD83D\uDC69") + | ^^^^^^^^^^^^^^ invalid decimal +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_expression_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_expression_1.diag index 780ed980a63..27794c88359 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_expression_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_expression_1.diag @@ -1,10 +1,10 @@ -error: invalid expression 'FULL_WORKTOP' - | -24 | Expression("ENTIRE_AUTH_ZONE"); -25 | -26 | CALL_METHOD -27 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -28 | "deposit_batch" -29 | Expression("FULL_WORKTOP"); - | ^^^^^^^^^^^^^^ invalid expression - | \ No newline at end of file +error: invalid expression 'FULL_WORKTOP' + | +24 | Expression("ENTIRE_AUTH_ZONE"); +25 | +26 | CALL_METHOD +27 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +28 | "deposit_batch" +29 | Expression("FULL_WORKTOP"); + | ^^^^^^^^^^^^^^ invalid expression + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_global_address_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_global_address_1.diag index 9b4375b5adb..86e10b6ac26 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_global_address_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_global_address_1.diag @@ -1,15 +1,15 @@ -error: invalid global address 'invalid_address' - | - 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - 3 | "lock_fee" - 4 | Decimal("500"); - 5 | - 6 | CALL_METHOD - 7 | Address("invalid_address") - | ^^^^^^^^^^^^^^^^^ invalid global address - 8 | "create_proof_of_amount" - 9 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") -10 | Decimal("1"); -11 | -12 | MINT_FUNGIBLE - | \ No newline at end of file +error: invalid global address 'invalid_address' + | + 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + 3 | "lock_fee" + 4 | Decimal("500"); + 5 | + 6 | CALL_METHOD + 7 | Address("invalid_address") + | ^^^^^^^^^^^^^^^^^ invalid global address + 8 | "create_proof_of_amount" + 9 | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") +10 | Decimal("1"); +11 | +12 | MINT_FUNGIBLE + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_internal_address_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_internal_address_1.diag index e3257522677..66844e1210a 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_internal_address_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_internal_address_1.diag @@ -1,14 +1,14 @@ -error: invalid internal address 'component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh' - | - 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - 3 | "lock_fee" - 4 | Decimal("5000") - 5 | ; - 6 | FREEZE_VAULT - 7 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid internal address - 8 | Tuple( - 9 | 2u32 -10 | ) -11 | ; - | \ No newline at end of file +error: invalid internal address 'component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh' + | + 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + 3 | "lock_fee" + 4 | Decimal("5000") + 5 | ; + 6 | FREEZE_VAULT + 7 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid internal address + 8 | Tuple( + 9 | 2u32 +10 | ) +11 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_global_id_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_global_id_1.diag index 7f358808963..8ffc245467f 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_global_id_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_global_id_1.diag @@ -1,11 +1,11 @@ -error: invalid non-fungible global id - | - 4 | ; - 5 | CALL_FUNCTION - 6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") - 7 | "Hello" - 8 | "instantiate_hello" - 9 | NonFungibleGlobalId("resource_sim1n2n538l5hpaagvl0phkff3qkdd6pxh0kskh8umuknr8c3whsl62dxp:some_id") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid non-fungible global id -10 | ; - | \ No newline at end of file +error: invalid non-fungible global id + | + 4 | ; + 5 | CALL_FUNCTION + 6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") + 7 | "Hello" + 8 | "instantiate_hello" + 9 | NonFungibleGlobalId("resource_sim1n2n538l5hpaagvl0phkff3qkdd6pxh0kskh8umuknr8c3whsl62dxp:some_id") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid non-fungible global id +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_local_id_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_local_id_1.diag index 96ff669c6fb..bb691917d1d 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_local_id_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_non_fungible_local_id_1.diag @@ -1,15 +1,15 @@ -error: invalid non-fungible local id 'some_id' - | - 1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "withdraw" Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Decimal("5.0"); - 2 | - 3 | # Create a proof from bucket, clone it and drop both - 4 | TAKE_ALL_FROM_WORKTOP Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Bucket("some_xrd"); - 5 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") Decimal("1") Proof("proof1a"); - 6 | CREATE_PROOF_FROM_BUCKET_OF_NON_FUNGIBLES Bucket("some_xrd") Array(NonFungibleLocalId("some_id")) Proof("proof1b"); - | ^^^^^^^^^ invalid non-fungible local id - 7 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1c"); - 8 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); - 9 | DROP_PROOF Proof("proof1d"); -10 | DROP_PROOF Proof("proof1c"); -11 | DROP_AUTH_ZONE_PROOFS; - | \ No newline at end of file +error: invalid non-fungible local id 'some_id' + | + 1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "withdraw" Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Decimal("5.0"); + 2 | + 3 | # Create a proof from bucket, clone it and drop both + 4 | TAKE_ALL_FROM_WORKTOP Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") Bucket("some_xrd"); + 5 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") Decimal("1") Proof("proof1a"); + 6 | CREATE_PROOF_FROM_BUCKET_OF_NON_FUNGIBLES Bucket("some_xrd") Array(NonFungibleLocalId("some_id")) Proof("proof1b"); + | ^^^^^^^^^ invalid non-fungible local id + 7 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1c"); + 8 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); + 9 | DROP_PROOF Proof("proof1d"); +10 | DROP_PROOF Proof("proof1c"); +11 | DROP_AUTH_ZONE_PROOFS; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_1.diag index 7252a8bbc89..611dca21e6a 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_1.diag @@ -1,13 +1,13 @@ -error: invalid package address 'package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk' - | -1 | CALL_METHOD -2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -3 | "lock_fee" -4 | ; -5 | CALL_FUNCTION -6 | Address("package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid package address -7 | "Hello" -8 | "instantiate_hello" -9 | ; - | \ No newline at end of file +error: invalid package address 'package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk' + | +1 | CALL_METHOD +2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +3 | "lock_fee" +4 | ; +5 | CALL_FUNCTION +6 | Address("package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid package address +7 | "Hello" +8 | "instantiate_hello" +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_2.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_2.diag index a5b236ea291..d43dc6fea0e 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_package_address_2.diag @@ -1,15 +1,15 @@ -error: invalid package address 'package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk' - | - 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - 3 | "lock_fee" - 4 | Decimal("500") - 5 | ; - 6 | ALLOCATE_GLOBAL_ADDRESS - 7 | Address("package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid package address - 8 | "Package" - 9 | AddressReservation("my_reservation") -10 | NamedAddress("my_package") -11 | ; -12 | PUBLISH_PACKAGE_ADVANCED - | \ No newline at end of file +error: invalid package address 'package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk' + | + 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + 3 | "lock_fee" + 4 | Decimal("500") + 5 | ; + 6 | ALLOCATE_GLOBAL_ADDRESS + 7 | Address("package_1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid package address + 8 | "Package" + 9 | AddressReservation("my_reservation") +10 | NamedAddress("my_package") +11 | ; +12 | PUBLISH_PACKAGE_ADVANCED + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_precise_decimal_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_precise_decimal_1.diag index 72698659e99..0fba58760fb 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_precise_decimal_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_precise_decimal_1.diag @@ -1,11 +1,11 @@ -error: invalid precise decimal '1.' - EmptyFractionalPart - | - 4 | ; - 5 | CALL_FUNCTION - 6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") - 7 | "Hello" - 8 | "instantiate_hello" - 9 | PreciseDecimal("1.") - | ^^^^ invalid precise decimal -10 | ; - | \ No newline at end of file +error: invalid precise decimal '1.' - EmptyFractionalPart + | + 4 | ; + 5 | CALL_FUNCTION + 6 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") + 7 | "Hello" + 8 | "instantiate_hello" + 9 | PreciseDecimal("1.") + | ^^^^ invalid precise decimal +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_resource_address_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_resource_address_1.diag index e1882fa8921..329416a84c8 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_invalid_resource_address_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_resource_address_1.diag @@ -1,15 +1,15 @@ -error: invalid resource address 'resource_address' - | -13 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -14 | "buy_gumball" -15 | Bucket("xrd_bucket"); -16 | -17 | ASSERT_WORKTOP_CONTAINS_ANY -18 | Address("resource_address"); - | ^^^^^^^^^^^^^^^^^^ invalid resource address -19 | -20 | ASSERT_WORKTOP_CONTAINS -21 | Address("resource_sim1t5q5qqum600pwwu27zl7m4rpr8g0400e72n368g09tgnptqe6kuwst") -22 | Decimal("3.0"); -23 | - | \ No newline at end of file +error: invalid resource address 'resource_address' + | +13 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +14 | "buy_gumball" +15 | Bucket("xrd_bucket"); +16 | +17 | ASSERT_WORKTOP_CONTAINS_ANY +18 | Address("resource_address"); + | ^^^^^^^^^^^^^^^^^^ invalid resource address +19 | +20 | ASSERT_WORKTOP_CONTAINS +21 | Address("resource_sim1t5q5qqum600pwwu27zl7m4rpr8g0400e72n368g09tgnptqe6kuwst") +22 | Decimal("3.0"); +23 | + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_sub_transaction_id_1.diag b/radix-transactions/tests/assets/manifest_generator_error_invalid_sub_transaction_id_1.diag new file mode 100644 index 00000000000..c8e9c84dcd0 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_sub_transaction_id_1.diag @@ -0,0 +1,8 @@ +error: invalid sub transaction id 'subtxid_sim1___oops_this_is_invalid' + | +1 | USE_CHILD +2 | NamedIntent("my_child") +3 | Intent("subtxid_sim1___oops_this_is_invalid") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid sub transaction id +4 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_invalid_sub_transaction_id_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_invalid_sub_transaction_id_1.rtm new file mode 100644 index 00000000000..c255e6b47f8 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_invalid_sub_transaction_id_1.rtm @@ -0,0 +1,4 @@ +USE_CHILD + NamedIntent("my_child") + Intent("subtxid_sim1___oops_this_is_invalid") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_1.diag b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_1.diag index 299b7f08e54..51dbaa555fd 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_1.diag @@ -1,15 +1,15 @@ -error: name already defined 'my_reservation' - | -10 | NamedAddress("my_package") -11 | ; -12 | ALLOCATE_GLOBAL_ADDRESS -13 | Address("package_sim1pkgxxxxxxxxxpackgexxxxxxxxx000726633226xxxxxxxxxlk8hc9") -14 | "Package" -15 | AddressReservation("my_reservation") - | ^^^^^^^^^^^^^^^^ name already defined -16 | NamedAddress("my_second_package") -17 | ; -18 | PUBLISH_PACKAGE_ADVANCED -19 | Enum() -20 | Tuple( - | \ No newline at end of file +error: name already defined 'my_reservation' + | +10 | NamedAddress("my_package") +11 | ; +12 | ALLOCATE_GLOBAL_ADDRESS +13 | Address("package_sim1pkgxxxxxxxxxpackgexxxxxxxxx000726633226xxxxxxxxxlk8hc9") +14 | "Package" +15 | AddressReservation("my_reservation") + | ^^^^^^^^^^^^^^^^ name already defined +16 | NamedAddress("my_second_package") +17 | ; +18 | PUBLISH_PACKAGE_ADVANCED +19 | Enum() +20 | Tuple( + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_2.diag b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_2.diag index f8eafd763a5..ef8932e8635 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_2.diag @@ -1,15 +1,15 @@ -error: name already defined 'my_package' - | -11 | ; -12 | ALLOCATE_GLOBAL_ADDRESS -13 | Address("package_sim1pkgxxxxxxxxxpackgexxxxxxxxx000726633226xxxxxxxxxlk8hc9") -14 | "Package" -15 | AddressReservation("my_second_reservation") -16 | NamedAddress("my_package") - | ^^^^^^^^^^^^ name already defined -17 | ; -18 | PUBLISH_PACKAGE_ADVANCED -19 | Enum() -20 | Tuple( -21 | Map() - | \ No newline at end of file +error: name already defined 'my_package' + | +11 | ; +12 | ALLOCATE_GLOBAL_ADDRESS +13 | Address("package_sim1pkgxxxxxxxxxpackgexxxxxxxxx000726633226xxxxxxxxxlk8hc9") +14 | "Package" +15 | AddressReservation("my_second_reservation") +16 | NamedAddress("my_package") + | ^^^^^^^^^^^^ name already defined +17 | ; +18 | PUBLISH_PACKAGE_ADVANCED +19 | Enum() +20 | Tuple( +21 | Map() + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_3.diag b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_3.diag index de6ecdab440..904c3b6b3f3 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_3.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_3.diag @@ -1,14 +1,14 @@ -error: name already defined 'proof1a' - | - 7 | Bucket("some_xrd") - 8 | Array( - 9 | NonFungibleLocalId("#123#") -10 | ) -11 | Proof("proof1b"); -12 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1a"); - | ^^^^^^^^^ name already defined -13 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); -14 | DROP_PROOF Proof("proof1d"); -15 | DROP_PROOF Proof("proof1c"); -16 | DROP_AUTH_ZONE_PROOFS; - | \ No newline at end of file +error: name already defined 'proof1a' + | + 7 | Bucket("some_xrd") + 8 | Array( + 9 | NonFungibleLocalId("#123#") +10 | ) +11 | Proof("proof1b"); +12 | CREATE_PROOF_FROM_BUCKET_OF_ALL Bucket("some_xrd") Proof("proof1a"); + | ^^^^^^^^^ name already defined +13 | CLONE_PROOF Proof("proof1c") Proof("proof1d"); +14 | DROP_PROOF Proof("proof1d"); +15 | DROP_PROOF Proof("proof1c"); +16 | DROP_AUTH_ZONE_PROOFS; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_4.diag b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_4.diag index e5ca302bb57..d0bbf8406c3 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_4.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_name_already_defined_4.diag @@ -1,15 +1,15 @@ -error: name already defined 'xrd' - | -10 | Bucket("xrd"); -11 | -12 | TAKE_FROM_WORKTOP -13 | Address("resource_sim1t45r6lyr36ypj64sumrmkk9pjesfyl9na849jz00qrwnx28c8sletw") -14 | Decimal("1.0") -15 | Bucket("xrd"); - | ^^^^^ name already defined -16 | -17 | CALL_METHOD -18 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -19 | "buy_gumball" -20 | Bucket("xrd"); - | \ No newline at end of file +error: name already defined 'xrd' + | +10 | Bucket("xrd"); +11 | +12 | TAKE_FROM_WORKTOP +13 | Address("resource_sim1t45r6lyr36ypj64sumrmkk9pjesfyl9na849jz00qrwnx28c8sletw") +14 | Decimal("1.0") +15 | Bucket("xrd"); + | ^^^^^ name already defined +16 | +17 | CALL_METHOD +18 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +19 | "buy_gumball" +20 | Bucket("xrd"); + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_as_value_kind_1.diag b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_as_value_kind_1.diag new file mode 100644 index 00000000000..0271473b8bd --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_as_value_kind_1.diag @@ -0,0 +1,9 @@ +error: a NamedIntent cannot be used as a value kind + | +1 | CALL_METHOD +2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +3 | "my_method" +4 | Array() + | ^^^^^^^^^^^ cannot be used as a value kind +5 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_as_value_kind_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_as_value_kind_1.rtm new file mode 100644 index 00000000000..9cd76399b2f --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_as_value_kind_1.rtm @@ -0,0 +1,5 @@ +CALL_METHOD + Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + "my_method" + Array() +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_in_value_1.diag b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_in_value_1.diag new file mode 100644 index 00000000000..b7d75d84917 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_in_value_1.diag @@ -0,0 +1,9 @@ +error: a NamedIntent(...) cannot currently be used inside a value + | +1 | CALL_METHOD +2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +3 | "my_method" +4 | NamedIntent("my_intent") + | ^^^^^^^^^^^ cannot be used inside a value +5 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_in_value_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_in_value_1.rtm new file mode 100644 index 00000000000..62e95fc4e42 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_named_intent_cannot_be_used_in_value_1.rtm @@ -0,0 +1,5 @@ +CALL_METHOD + Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + "my_method" + NamedIntent("my_intent") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type_1.diag b/radix-transactions/tests/assets/manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type_1.diag new file mode 100644 index 00000000000..3710ba18e16 --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type_1.diag @@ -0,0 +1,10 @@ +error: preallocated addresses are not supported in this manifest type + | +1 | / USE_PREALLOCATED_ADDRESS +2 | | Address("package_sim1pkgxxxxxxxxxresrcexxxxxxxxx000538436477xxxxxxxxxaj0zg9") +3 | | "FungibleResourceManager" +4 | | AddressReservation("reservation1") +5 | | Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") +6 | | ; + | |_^ unsupported instruction + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type_1.rtm b/radix-transactions/tests/assets/manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type_1.rtm new file mode 100644 index 00000000000..7bb1535527b --- /dev/null +++ b/radix-transactions/tests/assets/manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type_1.rtm @@ -0,0 +1,6 @@ +USE_PREALLOCATED_ADDRESS + Address("package_sim1pkgxxxxxxxxxresrcexxxxxxxxx000538436477xxxxxxxxxaj0zg9") + "FungibleResourceManager" + AddressReservation("reservation1") + Address("resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3") +; \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_1.diag b/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_1.diag index 11e7b02d8bc..c29ba35ca78 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_1.diag @@ -1,12 +1,12 @@ -error: proof id 'ManifestProof(1)' not found - | -18 | Bucket("some_bucket") -19 | Decimal("1") -20 | Proof("proof"); -21 | -22 | CLONE_PROOF -23 | Proof(1u32) - | ^^^^ proof not found -24 | Proof("cloned_proof") -25 | ; - | \ No newline at end of file +error: proof id 'ManifestProof(1)' not found + | +18 | Bucket("some_bucket") +19 | Decimal("1") +20 | Proof("proof"); +21 | +22 | CLONE_PROOF +23 | Proof(1u32) + | ^^^^ proof not found +24 | Proof("cloned_proof") +25 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_2.diag b/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_2.diag index 0a3753914b1..63e0d224b20 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_proof_not_found_2.diag @@ -1,11 +1,11 @@ -error: proof id 'ManifestProof(1)' not found - | -18 | Bucket("some_bucket") -19 | Decimal("1") -20 | Proof("proof"); -21 | -22 | DROP_PROOF -23 | Proof(1u32) - | ^^^^ proof not found -24 | ; - | \ No newline at end of file +error: proof id 'ManifestProof(1)' not found + | +18 | Bucket("some_bucket") +19 | Decimal("1") +20 | Proof("proof"); +21 | +22 | DROP_PROOF +23 | Proof(1u32) + | ^^^^ proof not found +24 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_undefined_address_reservation_1.diag b/radix-transactions/tests/assets/manifest_generator_error_undefined_address_reservation_1.diag index 691494056b6..9c62d25a26f 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_undefined_address_reservation_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_undefined_address_reservation_1.diag @@ -1,15 +1,15 @@ -error: undefined address reservation 'invalid_reservation' - | -14 | Tuple( -15 | Map() -16 | ) -17 | Blob("f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1") -18 | Map() -19 | Some(AddressReservation("invalid_reservation")) - | ^^^^^^^^^^^^^^^^^^^^^ undefined address reservation -20 | ; -21 | CALL_FUNCTION -22 | NamedAddress("my_package") -23 | "BlueprintName" -24 | "no_such_function" - | \ No newline at end of file +error: undefined address reservation 'invalid_reservation' + | +14 | Tuple( +15 | Map() +16 | ) +17 | Blob("f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1") +18 | Map() +19 | Some(AddressReservation("invalid_reservation")) + | ^^^^^^^^^^^^^^^^^^^^^ undefined address reservation +20 | ; +21 | CALL_FUNCTION +22 | NamedAddress("my_package") +23 | "BlueprintName" +24 | "no_such_function" + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_undefined_bucket_1.diag b/radix-transactions/tests/assets/manifest_generator_error_undefined_bucket_1.diag index 52eae70ea94..7978e8a0da9 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_undefined_bucket_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_undefined_bucket_1.diag @@ -1,15 +1,15 @@ -error: undefined bucket 'another_xrd_bucket' - | -10 | Bucket("xrd_bucket"); -11 | -12 | CALL_METHOD -13 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -14 | "buy_gumball" -15 | Bucket("another_xrd_bucket"); - | ^^^^^^^^^^^^^^^^^^^^ undefined bucket -16 | -17 | ASSERT_WORKTOP_CONTAINS_ANY -18 | Address("resource_sim1t5q5qqum600pwwu27zl7m4rpr8g0400e72n368g09tgnptqe6kuwst"); -19 | -20 | ASSERT_WORKTOP_CONTAINS - | \ No newline at end of file +error: undefined bucket 'another_xrd_bucket' + | +10 | Bucket("xrd_bucket"); +11 | +12 | CALL_METHOD +13 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +14 | "buy_gumball" +15 | Bucket("another_xrd_bucket"); + | ^^^^^^^^^^^^^^^^^^^^ undefined bucket +16 | +17 | ASSERT_WORKTOP_CONTAINS_ANY +18 | Address("resource_sim1t5q5qqum600pwwu27zl7m4rpr8g0400e72n368g09tgnptqe6kuwst"); +19 | +20 | ASSERT_WORKTOP_CONTAINS + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_undefined_named_address_1.diag b/radix-transactions/tests/assets/manifest_generator_error_undefined_named_address_1.diag index 792e4dcd61e..0c8d9ab0d3f 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_undefined_named_address_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_undefined_named_address_1.diag @@ -1,15 +1,15 @@ -error: undefined named address 'package_address' - | -17 | Blob("f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1") -18 | Map() -19 | Some(AddressReservation("my_reservation")) -20 | ; -21 | CALL_FUNCTION -22 | NamedAddress("package_address") - | ^^^^^^^^^^^^^^^^^ undefined named address -23 | "BlueprintName" -24 | "no_such_function" -25 | Decimal("1.0") -26 | NamedAddress("my_package") -27 | ; - | \ No newline at end of file +error: undefined named address 'package_address' + | +17 | Blob("f531e5e82dc195a7a9f6709f5ee048d9541d51c52904b7a76295a13ddfa377b1") +18 | Map() +19 | Some(AddressReservation("my_reservation")) +20 | ; +21 | CALL_FUNCTION +22 | NamedAddress("package_address") + | ^^^^^^^^^^^^^^^^^ undefined named address +23 | "BlueprintName" +24 | "no_such_function" +25 | Decimal("1.0") +26 | NamedAddress("my_package") +27 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_undefined_proof_1.diag b/radix-transactions/tests/assets/manifest_generator_error_undefined_proof_1.diag index 474eeb3f72e..a5a2f7d2117 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_undefined_proof_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_undefined_proof_1.diag @@ -1,14 +1,14 @@ -error: undefined proof 'proof_2' - | -11 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") -12 | Decimal("1") -13 | Proof("proof_1"); -14 | -15 | CLONE_PROOF -16 | Proof("proof_2") - | ^^^^^^^^^ undefined proof -17 | Proof("cloned_proof"); -18 | -19 | DROP_PROOF Proof("proof_1"); -20 | DROP_PROOF Proof("cloned_proof"); - | \ No newline at end of file +error: undefined proof 'proof_2' + | +11 | CREATE_PROOF_FROM_BUCKET_OF_AMOUNT Bucket("some_xrd") +12 | Decimal("1") +13 | Proof("proof_1"); +14 | +15 | CLONE_PROOF +16 | Proof("proof_2") + | ^^^^^^^^^ undefined proof +17 | Proof("cloned_proof"); +18 | +19 | DROP_PROOF Proof("proof_1"); +20 | DROP_PROOF Proof("cloned_proof"); + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_1.diag b/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_1.diag index 494c002efb0..5ad2117d8b2 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_1.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_1.diag @@ -1,13 +1,13 @@ -error: expected String, found U32 - | - 5 | CALL_METHOD - 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 7 | "some_method" - 8 | Array( - 9 | "one", -10 | 2u32, - | ^^^^ expected String -11 | "three" -12 | ) -13 | ; - | \ No newline at end of file +error: expected String, found U32 + | + 5 | CALL_METHOD + 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 7 | "some_method" + 8 | Array( + 9 | "one", +10 | 2u32, + | ^^^^ expected String +11 | "three" +12 | ) +13 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_2.diag b/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_2.diag index 77f5b22c09e..dd29fe04191 100644 --- a/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_2.diag +++ b/radix-transactions/tests/assets/manifest_generator_error_unexpected_value_2.diag @@ -1,13 +1,13 @@ -error: expected String, found Bytes - | - 5 | CALL_METHOD - 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 7 | "some_method" - 8 | Array( - 9 | "one", -10 | Bytes(1u32), - | ^^^^^ expected String -11 | "three" -12 | ) -13 | ; - | \ No newline at end of file +error: expected String, found Bytes + | + 5 | CALL_METHOD + 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 7 | "some_method" + 8 | Array( + 9 | "one", +10 | Bytes(1u32), + | ^^^^^ expected String +11 | "three" +12 | ) +13 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_1.diag b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_1.diag index b5cd44d3cc7..cbe6d1b9b50 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_1.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_1.diag @@ -1,15 +1,15 @@ -error: invalid integer value '-300i8' - number too small to fit in target type - | - 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - 3 | "lock_fee"; - 4 | CALL_METHOD - 5 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 6 | "some_method" - 7 | -300i8; - | ^^^^^^ invalid integer value - 8 | CALL_METHOD - 9 | Address("account_sim1c956qr3kxlgypxwst89j9yf24tjc7zxd4up38x37zr6q4jxdx9rhma") -10 | "try_deposit_batch_or_abort" -11 | Expression("ENTIRE_WORKTOP") -12 | None; - | \ No newline at end of file +error: invalid integer value '-300i8' - number too small to fit in target type + | + 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + 3 | "lock_fee"; + 4 | CALL_METHOD + 5 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 6 | "some_method" + 7 | -300i8; + | ^^^^^^ invalid integer value + 8 | CALL_METHOD + 9 | Address("account_sim1c956qr3kxlgypxwst89j9yf24tjc7zxd4up38x37zr6q4jxdx9rhma") +10 | "try_deposit_batch_or_abort" +11 | Expression("ENTIRE_WORKTOP") +12 | None; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_2.diag b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_2.diag index 8232fc29dd0..e582b8d24e2 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_2.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_2.diag @@ -1,7 +1,7 @@ -error: invalid integer value '300i8' - number too large to fit in target type - | -1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "lock_fee"; -2 | CALL_METHOD Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") "some_method" 300i8; - | ^^^^^ invalid integer value -3 | CALL_METHOD Address("account_sim1c956qr3kxlgypxwst89j9yf24tjc7zxd4up38x37zr6q4jxdx9rhma") "try_deposit_batch_or_abort" Expression("ENTIRE_WORKTOP") None; - | \ No newline at end of file +error: invalid integer value '300i8' - number too large to fit in target type + | +1 | CALL_METHOD Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") "lock_fee"; +2 | CALL_METHOD Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") "some_method" 300i8; + | ^^^^^ invalid integer value +3 | CALL_METHOD Address("account_sim1c956qr3kxlgypxwst89j9yf24tjc7zxd4up38x37zr6q4jxdx9rhma") "try_deposit_batch_or_abort" Expression("ENTIRE_WORKTOP") None; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_3.diag b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_3.diag index 45e240aad6d..33f188a2ccc 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_3.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_3.diag @@ -1,11 +1,11 @@ -error: invalid integer value '-300u8' - invalid digit found in string - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | -300u8 - | ^^^^^^ invalid integer value -9 | ; - | \ No newline at end of file +error: invalid integer value '-300u8' - invalid digit found in string + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | -300u8 + | ^^^^^^ invalid integer value +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_literal_1.diag b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_literal_1.diag index 7d58aac6ddf..b78b194c0c9 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_literal_1.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_literal_1.diag @@ -1,11 +1,11 @@ -error: invalid integer literal '- ' - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | - 107u32 - | ^^ invalid integer literal -9 | ; - | \ No newline at end of file +error: invalid integer literal '- ' + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | - 107u32 + | ^^ invalid integer literal +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_1.diag b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_1.diag index edd531c5547..8635c184cab 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_1.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_1.diag @@ -1,11 +1,11 @@ -error: invalid integer type 'i9' - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | Decimal(300i9) - | ^^ invalid integer type -9 | ; - | \ No newline at end of file +error: invalid integer type 'i9' + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | Decimal(300i9) + | ^^ invalid integer type +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_2.diag b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_2.diag index 4657f773242..7b8f4b32f7c 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_2.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_invalid_integer_type_2.diag @@ -1,11 +1,11 @@ -error: invalid integer type '_' - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | 3_0u32 - | ^ invalid integer type -9 | - | \ No newline at end of file +error: invalid integer type '_' + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | 3_0u32 + | ^ invalid integer type +9 | + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_invalid_unicode_1.diag b/radix-transactions/tests/assets/manifest_lexer_error_invalid_unicode_1.diag index 852bde78c1d..b48f61e92fc 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_invalid_unicode_1.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_invalid_unicode_1.diag @@ -1,11 +1,11 @@ -error: invalid unicode code point 1238580 - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | "\uDCAC\u1234" - | ^^^^^^^^^^^ invalid unicode code point -9 | ; - | \ No newline at end of file +error: invalid unicode code point 1238580 + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | "\uDCAC\u1234" + | ^^^^^^^^^^^ invalid unicode code point +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_missing_unicode_surrogate_1.diag b/radix-transactions/tests/assets/manifest_lexer_error_missing_unicode_surrogate_1.diag index 01666e39d16..ae91640881b 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_missing_unicode_surrogate_1.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_missing_unicode_surrogate_1.diag @@ -1,11 +1,11 @@ -error: missing unicode 'DCAC' surrogate pair - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | "\uDCACsome_text" - | ^^^^^ missing unicode surrogate pair -9 | ; - | \ No newline at end of file +error: missing unicode 'DCAC' surrogate pair + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | "\uDCACsome_text" + | ^^^^^ missing unicode surrogate pair +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_1.diag b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_1.diag index 753d8cec1d3..c551e1fa4ed 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_1.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_1.diag @@ -1,11 +1,11 @@ -error: unexpected character '%', expected digit, letter, quotation mark or one of punctuation characters '(', ')', '<', '>', ',', ';', '=' - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | Decimal(%27) - | ^ unexpected character -9 | ; - | \ No newline at end of file +error: unexpected character '%', expected digit, letter, quotation mark or one of punctuation characters '(', ')', '<', '>', ',', ';', '=' + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | Decimal(%27) + | ^ unexpected character +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_2.diag b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_2.diag index 50c44558efe..308bc7bc44d 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_2.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_2.diag @@ -1,10 +1,10 @@ -error: unexpected character 'x', expected '"', '\', '/', 'b', 'f', 'n', 'r', 't' or 'u' - | -2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "\xaaaa" - | ^ unexpected character - | \ No newline at end of file +error: unexpected character 'x', expected '"', '\', '/', 'b', 'f', 'n', 'r', 't' or 'u' + | +2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "\xaaaa" + | ^ unexpected character + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_3.diag b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_3.diag index 0a928060467..6e4d8fd48c0 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_3.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_3.diag @@ -1,5 +1,5 @@ -error: unexpected character '7', expected '>' - | -1 | x=7 - | ^ unexpected character - | \ No newline at end of file +error: unexpected character '7', expected '>' + | +1 | x=7 + | ^ unexpected character + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_4.diag b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_4.diag index 71c54c925a2..9809aed1231 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_4.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_4.diag @@ -1,11 +1,11 @@ -error: unexpected character 'K', expected hex digit - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | "\uFAKE" - | ^ unexpected character -9 | ; - | \ No newline at end of file +error: unexpected character 'K', expected hex digit + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | "\uFAKE" + | ^ unexpected character +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_5.diag b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_5.diag index 6eff6d05e73..faa988a7a4b 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_5.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_char_5.diag @@ -1,11 +1,11 @@ -error: unexpected character '基', expected digit, letter, quotation mark or one of punctuation characters '(', ')', '<', '>', ',', ';', '=' - | - 4 | ; - 5 | - 6 | CALL_METHOD - 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 8 | "some_method" - 9 | 基数引擎 - | ^^ unexpected character -10 | ; - | \ No newline at end of file +error: unexpected character '基', expected digit, letter, quotation mark or one of punctuation characters '(', ')', '<', '>', ',', ';', '=' + | + 4 | ; + 5 | + 6 | CALL_METHOD + 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 8 | "some_method" + 9 | 基数引擎 + | ^^ unexpected character +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_1.diag b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_1.diag index a6f61a31e63..737892c261f 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_1.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_1.diag @@ -1,10 +1,10 @@ -error: unexpected end of file - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | "\uDCA - | ^ unexpected end of file - | \ No newline at end of file +error: unexpected end of file + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | "\uDCA + | ^ unexpected end of file + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_2.diag b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_2.diag index 4bde2141752..827f96b6a3a 100644 --- a/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_2.diag +++ b/radix-transactions/tests/assets/manifest_lexer_error_unexpected_eof_2.diag @@ -1,10 +1,10 @@ -error: unexpected end of file - | -2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "xxx - | ^ unexpected end of file - | \ No newline at end of file +error: unexpected end of file + | +2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "xxx + | ^ unexpected end of file + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_1.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_1.diag index eea1243b9df..1c28108464a 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_1.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_1.diag @@ -1,15 +1,15 @@ -error: expected a manifest SBOR value or ';' to end an argument list, found 'SET_COMPONENT_ROYALTY' - | -10 | SET_COMPONENT_ROYALTY -11 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -12 | "method_with_xrd_package_royalty" -13 | Enum<0u8>() -14 | -15 | SET_COMPONENT_ROYALTY - | ^^^^^^^^^^^^^^^^^^^^^ expected a manifest SBOR value or ';' to end an argument list -16 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -17 | "method_with_usd_package_royalty" -18 | Enum<0u8>() -19 | ; -20 | SET_COMPONENT_ROYALTY - | \ No newline at end of file +error: expected a manifest SBOR value or ';' to end an argument list, found 'SET_COMPONENT_ROYALTY' + | +10 | SET_COMPONENT_ROYALTY +11 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +12 | "method_with_xrd_package_royalty" +13 | Enum<0u8>() +14 | +15 | SET_COMPONENT_ROYALTY + | ^^^^^^^^^^^^^^^^^^^^^ expected a manifest SBOR value or ';' to end an argument list +16 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +17 | "method_with_usd_package_royalty" +18 | Enum<0u8>() +19 | ; +20 | SET_COMPONENT_ROYALTY + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_2.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_2.diag index 3ba965733a6..6d7875e73c4 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_2.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_argument_2.diag @@ -1,11 +1,11 @@ -error: expected a manifest SBOR value or ';' to end an argument list, found ')' - | -3 | "lock_fee" -4 | ; -5 | SET_COMPONENT_ROYALTY -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "method_with_no_package_royalty" -8 | Enum<0u8>()) - | ^ expected a manifest SBOR value or ';' to end an argument list -9 | ; - | \ No newline at end of file +error: expected a manifest SBOR value or ';' to end an argument list, found ')' + | +3 | "lock_fee" +4 | ; +5 | SET_COMPONENT_ROYALTY +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "method_with_no_package_royalty" +8 | Enum<0u8>()) + | ^ expected a manifest SBOR value or ';' to end an argument list +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_1.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_1.diag index 0038cfc6e3e..9f1b99d60ef 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_1.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_1.diag @@ -1,15 +1,15 @@ -error: expected 2 number of types, found 3 - | -19 | None, -20 | None, -21 | None -22 | ) -23 | Tuple( -24 | Map( - | ^^^^^^^^^^^^^^^^^^^^ expected 2 number of types -25 | "name" => Tuple( -26 | Some(Enum("MyResource")), -27 | true -28 | ) -29 | ), - | \ No newline at end of file +error: expected 2 number of types, found 3 + | +19 | None, +20 | None, +21 | None +22 | ) +23 | Tuple( +24 | Map( + | ^^^^^^^^^^^^^^^^^^^^ expected 2 number of types +25 | "name" => Tuple( +26 | Some(Enum("MyResource")), +27 | true +28 | ) +29 | ), + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_2.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_2.diag index b6c36886708..4fd23752ad3 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_2.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_2.diag @@ -1,15 +1,15 @@ -error: expected 1 number of types, found 2 - | - 5 | - 6 | CREATE_NON_FUNGIBLE_RESOURCE - 7 | Enum() - 8 | Enum() - 9 | true -10 | Enum<0u8>(Enum<0u8>(Tuple(Array(), Array(), Array())), Enum<0u8>(66u8), Array()) - | ^^^^^^^^^^^^^ expected 1 number of types -11 | Tuple( -12 | Some( -13 | Tuple( -14 | Some(Enum()), -15 | Some(Enum()) - | \ No newline at end of file +error: expected 1 number of types, found 2 + | + 5 | + 6 | CREATE_NON_FUNGIBLE_RESOURCE + 7 | Enum() + 8 | Enum() + 9 | true +10 | Enum<0u8>(Enum<0u8>(Tuple(Array(), Array(), Array())), Enum<0u8>(66u8), Array()) + | ^^^^^^^^^^^^^ expected 1 number of types +11 | Tuple( +12 | Some( +13 | Tuple( +14 | Some(Enum()), +15 | Some(Enum()) + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_3.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_3.diag index 0e47f1e1c95..3b1d28d8f43 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_3.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_3.diag @@ -1,11 +1,11 @@ -error: expected 1 number of types, found 0 - | - 5 | - 6 | CALL_METHOD - 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 8 | "some_method" - 9 | # Invalid array specification -10 | Array<> - | ^^ expected 1 number of types -11 | ; - | \ No newline at end of file +error: expected 1 number of types, found 0 + | + 5 | + 6 | CALL_METHOD + 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 8 | "some_method" + 9 | # Invalid array specification +10 | Array<> + | ^^ expected 1 number of types +11 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_4.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_4.diag index 153642a2f12..3d86edef3e7 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_4.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_types_4.diag @@ -1,14 +1,14 @@ -error: expected 2 number of types, found 0 - | - 5 | - 6 | CALL_METHOD - 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 8 | "some_method" - 9 | # Invalid array specification -10 | Map< - | ________^ -11 | | -12 | | > - | |_____^ expected 2 number of types -13 | ; - | \ No newline at end of file +error: expected 2 number of types, found 0 + | + 5 | + 6 | CALL_METHOD + 7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 8 | "some_method" + 9 | # Invalid array specification +10 | Map< + | ________^ +11 | | +12 | | > + | |_____^ expected 2 number of types +13 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_1.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_1.diag index b429df63b34..0702aa4ec2b 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_1.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_1.diag @@ -1,14 +1,14 @@ -error: expected 1 number of values, found 2 - | -1 | CALL_METHOD -2 | Address( -3 | "component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh", - | _______^ -4 | | "component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - | |____________________________________________________________________________^ expected 1 number of values -5 | "lock_fee" -6 | ; -7 | SET_COMPONENT_ROYALTY -8 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -9 | "method_with_no_package_royalty" - | \ No newline at end of file +error: expected 1 number of values, found 2 + | +1 | CALL_METHOD +2 | Address( + | _____________^ +3 | | "component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh", +4 | | "component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + | |____________________________________________________________________________^ expected 1 number of values +5 | "lock_fee" +6 | ; +7 | SET_COMPONENT_ROYALTY +8 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +9 | "method_with_no_package_royalty" + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_2.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_2.diag index e88191b84c5..696a44b6105 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_2.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_2.diag @@ -1,12 +1,12 @@ -error: expected 1 number of values, found 3 - | - 3 | "lock_fee" - 4 | ; - 5 | CALL_METHOD - 6 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - 7 | "some_method" - 8 | Some("radix", "is", "fun") - | ^^^^^^^^^^^^^^^^^^^^ expected 1 number of values - 9 | Enum<0u8>() -10 | ; - | \ No newline at end of file +error: expected 1 number of values, found 3 + | + 3 | "lock_fee" + 4 | ; + 5 | CALL_METHOD + 6 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + 7 | "some_method" + 8 | Some("radix", "is", "fun") + | ^^^^^^^^^^^^^^^^^^^^ expected 1 number of values + 9 | Enum<0u8>() +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_3.diag b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_3.diag new file mode 100644 index 00000000000..40009e4704c --- /dev/null +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_3.diag @@ -0,0 +1,11 @@ +error: expected 1 number of values, found 0 + | +1 | CALL_METHOD +2 | Address( ) + | ^^ expected 1 number of values +3 | "lock_fee" +4 | ; +5 | SET_COMPONENT_ROYALTY +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "method_with_no_package_royalty" + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_3.rtm b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_3.rtm new file mode 100644 index 00000000000..ca34ace1f5d --- /dev/null +++ b/radix-transactions/tests/assets/manifest_parser_error_invalid_number_of_values_3.rtm @@ -0,0 +1,9 @@ +CALL_METHOD + Address( ) + "lock_fee" +; +SET_COMPONENT_ROYALTY + Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + "method_with_no_package_royalty" + Enum<0u8>() +; diff --git a/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_1.diag b/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_1.diag index 18dc640f84c..29caeaa74ec 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_1.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_1.diag @@ -1,15 +1,15 @@ -error: manifest actual depth 21 exceeded max 20 - | -20 | Tuple( -21 | Tuple( -22 | Tuple( -23 | Tuple( -24 | Tuple( -25 | Tuple( - | ^^^^^ max depth exceeded -26 | Tuple( -27 | Tuple( -28 | Tuple( -29 | Tuple( -30 | Tuple( - | \ No newline at end of file +error: manifest actual depth 21 exceeded max 20 + | +20 | Tuple( +21 | Tuple( +22 | Tuple( +23 | Tuple( +24 | Tuple( +25 | Tuple( + | ^^^^^ max depth exceeded +26 | Tuple( +27 | Tuple( +28 | Tuple( +29 | Tuple( +30 | Tuple( + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_2.diag b/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_2.diag index 81bdf782198..4403e7298d7 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_2.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_max_depth_exceeded_2.diag @@ -1,9 +1,9 @@ -error: manifest actual depth 21 exceeded max 20 - | -1 | CALL_FUNCTION -2 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") -3 | "blueprint" -4 | "func" -5 | Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(0u8))))))))))))))))))))))))))))); - | ^^^^^ max depth exceeded - | \ No newline at end of file +error: manifest actual depth 21 exceeded max 20 + | +1 | CALL_FUNCTION +2 | Address("package_sim1p4r4955skdjq9swg8s5jguvcjvyj7tsxct87a9z6sw76cdfd2jg3zk") +3 | "blueprint" +4 | "func" +5 | Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(Tuple(0u8))))))))))))))))))))))))))))); + | ^^^^^ max depth exceeded + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_1.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_1.diag index defa1575abc..31f6a888d39 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_1.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_1.diag @@ -1,10 +1,10 @@ -error: unexpected end of file - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -7 | "some_method" -8 | Enum<0u8>( - | ^ unexpected end of file - | \ No newline at end of file +error: unexpected end of file + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +7 | "some_method" +8 | Enum<0u8>( + | ^ unexpected end of file + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_2.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_2.diag index a8ef0b94dd2..77c34732f34 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_2.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_2.diag @@ -1,10 +1,10 @@ -error: unexpected end of file - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | Decimal("700" - | ^ unexpected end of file - | \ No newline at end of file +error: unexpected end of file + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | Decimal("700" + | ^ unexpected end of file + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_3.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_3.diag index 4308eb68baa..17dde15bd08 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_3.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_eof_3.diag @@ -1,10 +1,10 @@ -error: unexpected end of file - | -4 | ; -5 | -6 | CALL_METHOD -7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -8 | "some_method" -9 | Address("基数引擎") - | ^ unexpected end of file - | \ No newline at end of file +error: unexpected end of file + | +4 | ; +5 | +6 | CALL_METHOD +7 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +8 | "some_method" +9 | Address("基数引擎") + | ^ unexpected end of file + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_1.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_1.diag index 895ae876dbc..739a1827872 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_1.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_1.diag @@ -1,10 +1,10 @@ -error: expected an instruction, found ';' - | -1 | ; - | ^ expected an instruction -2 | CALL_METHOD -3 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") -4 | "lock_fee" -5 | ; -6 | SET_COMPONENT_ROYALTY - | \ No newline at end of file +error: expected an instruction, found ';' + | +1 | ; + | ^ expected an instruction +2 | CALL_METHOD +3 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") +4 | "lock_fee" +5 | ; +6 | SET_COMPONENT_ROYALTY + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_2.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_2.diag index 4f17e6c39ee..ca6210f2570 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_2.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_2.diag @@ -1,15 +1,15 @@ -error: expected an instruction, found ';' - | -10 | SET_COMPONENT_ROYALTY -11 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -12 | "method_with_xrd_package_royalty" -13 | Enum<0u8>() -14 | ; -15 | ; - | ^ expected an instruction -16 | SET_COMPONENT_ROYALTY -17 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -18 | "method_with_usd_package_royalty" -19 | Enum<0u8>() -20 | ; - | \ No newline at end of file +error: expected an instruction, found ';' + | +10 | SET_COMPONENT_ROYALTY +11 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +12 | "method_with_xrd_package_royalty" +13 | Enum<0u8>() +14 | ; +15 | ; + | ^ expected an instruction +16 | SET_COMPONENT_ROYALTY +17 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +18 | "method_with_usd_package_royalty" +19 | Enum<0u8>() +20 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_3.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_3.diag index c3db3e7698c..8e2eb8a71f6 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_3.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_3.diag @@ -1,11 +1,11 @@ -error: expected an instruction, found '1u32' - | - 4 | ; - 5 | SET_COMPONENT_ROYALTY - 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 7 | "method_with_no_package_royalty" - 8 | Enum<0u8>(); - 9 | 1u32 - | ^^^^ expected an instruction -10 | ; - | \ No newline at end of file +error: expected an instruction, found '1u32' + | + 4 | ; + 5 | SET_COMPONENT_ROYALTY + 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 7 | "method_with_no_package_royalty" + 8 | Enum<0u8>(); + 9 | 1u32 + | ^^^^ expected an instruction +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_4.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_4.diag index 7bd32999df4..95e43a79026 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_4.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_4.diag @@ -1,10 +1,10 @@ -error: expected an instruction, found "CALL_METHOD" - | - 5 | SET_COMPONENT_ROYALTY - 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 7 | "method_with_no_package_royalty" - 8 | Enum<0u8>() - 9 | ; -10 | "CALL_METHOD" - | ^^^^^^^^^^^^^ expected an instruction - | \ No newline at end of file +error: expected an instruction, found "CALL_METHOD" + | + 5 | SET_COMPONENT_ROYALTY + 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 7 | "method_with_no_package_royalty" + 8 | Enum<0u8>() + 9 | ; +10 | "CALL_METHOD" + | ^^^^^^^^^^^^^ expected an instruction + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_5.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_5.diag index 82df10271f5..aaa3c1f9a4b 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_5.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_5.diag @@ -1,11 +1,11 @@ -error: expected an instruction, found 'true' - | - 4 | ; - 5 | SET_COMPONENT_ROYALTY - 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") - 7 | "method_with_no_package_royalty" - 8 | Enum<0u8>(); - 9 | true - | ^^^^ expected an instruction -10 | ; - | \ No newline at end of file +error: expected an instruction, found 'true' + | + 4 | ; + 5 | SET_COMPONENT_ROYALTY + 6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") + 7 | "method_with_no_package_royalty" + 8 | Enum<0u8>(); + 9 | true + | ^^^^ expected an instruction +10 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_6.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_6.diag index 31350cf3297..4794d6d0099 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_6.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_6.diag @@ -1,11 +1,11 @@ -error: expected a u8 enum discriminator or valid discriminator alias, found '1u128' - | -3 | "lock_fee" -4 | ; -5 | SET_COMPONENT_ROYALTY -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "method_with_no_package_royalty" -8 | Enum<1u128>() - | ^^^^^ expected a u8 enum discriminator or valid discriminator alias -9 | ; - | \ No newline at end of file +error: expected a u8 enum discriminator or valid discriminator alias, found '1u128' + | +3 | "lock_fee" +4 | ; +5 | SET_COMPONENT_ROYALTY +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "method_with_no_package_royalty" +8 | Enum<1u128>() + | ^^^^^ expected a u8 enum discriminator or valid discriminator alias +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_7.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_7.diag index ba46bbff952..f3bb7ec8440 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_7.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_7.diag @@ -1,11 +1,11 @@ -error: expected a manifest SBOR value kind, found '1u8' - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | Array<1u8>() - | ^^^ expected a manifest SBOR value kind -9 | ; - | \ No newline at end of file +error: expected a manifest SBOR value kind, found '1u8' + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | Array<1u8>() + | ^^^ expected a manifest SBOR value kind +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_8.diag b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_8.diag index 89c0eb2bf3a..6f2e8f2aab2 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_8.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unexpected_token_8.diag @@ -1,11 +1,11 @@ -error: expected exactly '>', found ')' - | -3 | "lock_fee" -4 | ; -5 | CALL_METHOD -6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") -7 | "some_method" -8 | Enum<0u8) - | ^ expected exactly '>' -9 | ; - | \ No newline at end of file +error: expected exactly '>', found ')' + | +3 | "lock_fee" +4 | ; +5 | CALL_METHOD +6 | Address("component_sim1cpyavrltfeu9ppx24pcpvh93xf44sfjygtv6dgf6uq3cdwafl7f9rq") +7 | "some_method" +8 | Enum<0u8) + | ^ expected exactly '>' +9 | ; + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_1.diag b/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_1.diag index 258bc7cb315..5eb1d0735cd 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_1.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_1.diag @@ -1,15 +1,15 @@ -error: unknown enum discriminator found 'OwnerRol::Updatable' - | - 1 | CALL_METHOD - 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") - 3 | "lock_fee" - 4 | ; - 5 | CREATE_ACCOUNT_ADVANCED - 6 | Enum( - | ^^^^^^^^^^^^^^^^^^^ unknown enum discriminator - 7 | Enum() - 8 | ) - 9 | ; -10 | TAKE_ALL_FROM_WORKTOP -11 | Address("${resource_address}") - | \ No newline at end of file +error: unknown enum discriminator found 'OwnerRol::Updatable' + | + 1 | CALL_METHOD + 2 | Address("component_sim1cptxxxxxxxxxfaucetxxxxxxxxx000527798379xxxxxxxxxhkrefh") + 3 | "lock_fee" + 4 | ; + 5 | CREATE_ACCOUNT_ADVANCED + 6 | Enum( + | ^^^^^^^^^^^^^^^^^^^ unknown enum discriminator + 7 | Enum() + 8 | ) + 9 | ; +10 | TAKE_ALL_FROM_WORKTOP +11 | Address("${resource_address}") + | \ No newline at end of file diff --git a/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_2.diag b/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_2.diag index 0edbaa17b1c..e2cbf39e2d8 100644 --- a/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_2.diag +++ b/radix-transactions/tests/assets/manifest_parser_error_unknown_enum_discriminator_2.diag @@ -1,15 +1,15 @@ -error: unknown enum discriminator found 'Metadata::StingArray' - | - 3 | "lock_fee" - 4 | ; - 5 | SET_METADATA - 6 | Address("resource_sim1n2pqvufl0fmexzpkl6rzk50n2seaz49jgfaqensv7ujxdlf6szr08f") - 7 | "field_name" - 8 | Enum( - | ^^^^^^^^^^^^^^^^^^^^ unknown enum discriminator - 9 | Array( -10 | "some_string", -11 | "another_string", -12 | "yet_another_string" -13 | ) - | \ No newline at end of file +error: unknown enum discriminator found 'Metadata::StingArray' + | + 3 | "lock_fee" + 4 | ; + 5 | SET_METADATA + 6 | Address("resource_sim1n2pqvufl0fmexzpkl6rzk50n2seaz49jgfaqensv7ujxdlf6szr08f") + 7 | "field_name" + 8 | Enum( + | ^^^^^^^^^^^^^^^^^^^^ unknown enum discriminator + 9 | Array( +10 | "some_string", +11 | "another_string", +12 | "yet_another_string" +13 | ) + | \ No newline at end of file diff --git a/radix-transactions/tests/test_manifest_compiler_error_diagnostics.rs b/radix-transactions/tests/test_manifest_compiler_error_diagnostics.rs index a9f0046baa0..12149c8fcf8 100644 --- a/radix-transactions/tests/test_manifest_compiler_error_diagnostics.rs +++ b/radix-transactions/tests/test_manifest_compiler_error_diagnostics.rs @@ -1,15 +1,19 @@ use radix_common::network::NetworkDefinition; -use radix_transactions::manifest::blob_provider::*; -use radix_transactions::manifest::compiler::*; +use radix_transactions::manifest::*; macro_rules! check_manifest { - ($manifest:expr, $blob_provider:expr, $style:expr) => {{ + ($manifest_kind:expr, $manifest:expr, $blob_provider:expr $(,)?) => {{ let manifest = include_str!(concat!("assets/", $manifest, ".rtm")); let diagnostic = include_str!(concat!("assets/", $manifest, ".diag")); - let err = compile(manifest, &NetworkDefinition::simulator(), $blob_provider).unwrap_err(); - - let x = compile_error_diagnostics(manifest, err, $style); + let x = compile_any_manifest_with_pretty_error( + manifest, + $manifest_kind, + &NetworkDefinition::simulator(), + $blob_provider, + CompileErrorDiagnosticsStyle::PlainText, + ) + .unwrap_err(); if x != diagnostic { let path = format!("tests/assets/{}.diag.res", $manifest); @@ -24,13 +28,20 @@ macro_rules! check_manifest { panic!("diagnostic reports differ"); } }}; - ($manifest:expr) => {{ - // Some instructions require valid blob in order to let - // manifest compile, eg. PUBLISH_PACKAGE_ADVANCED + ($manifest_kind:expr, $manifest:expr $(,)?) => {{ check_manifest!( + $manifest_kind, $manifest, - MockBlobProvider::default(), - CompileErrorDiagnosticsStyle::TextTerminalColors + // The MockBlobProvider pretends any blob is valid + MockBlobProvider::default() + ) + }}; + ($manifest:expr $(,)?) => {{ + check_manifest!( + ManifestKind::V1, + $manifest, + // The MockBlobProvider pretends any blob is valid + MockBlobProvider::default() ) }}; } @@ -72,6 +83,7 @@ fn test_manifest_parser_error_diagnostics_invalid_number_of_values() { // InvalidNumberOfValues check_manifest!("manifest_parser_error_invalid_number_of_values_1"); check_manifest!("manifest_parser_error_invalid_number_of_values_2"); + check_manifest!("manifest_parser_error_invalid_number_of_values_3"); } #[test] @@ -212,9 +224,9 @@ fn test_manifest_generator_error_invalid_blob_hash() { fn test_manifest_generator_error_blob_not_found() { // BlobNotFound check_manifest!( + ManifestKind::V1, "manifest_generator_error_blob_not_found_1", - BlobProvider::default(), - CompileErrorDiagnosticsStyle::TextTerminalColors + BlobProvider::default() ); } @@ -302,11 +314,118 @@ fn test_manifest_generator_error_proof_not_found() { check_manifest!("manifest_generator_error_proof_not_found_2"); } +#[test] +fn test_manifest_generator_error_invalid_sub_transaction_id() { + // InvalidSubTransactionId(String) + check_manifest!( + ManifestKind::V2, + "manifest_generator_error_invalid_sub_transaction_id_1" + ); +} + +#[test] +fn test_manifest_generator_error_instruction_not_supported_in_manifest_version() { + // InstructionNotSupportedInManifestVersion + check_manifest!( + ManifestKind::V1, + "manifest_generator_error_instruction_not_supported_in_manifest_version_1" + ); +} + +#[test] +fn test_manifest_generator_error_duplicate_subintent_hash() { + // ManifestBuildError(ManifestBuildError::DuplicateChildSubintentHash) + check_manifest!( + ManifestKind::V2, + "manifest_generator_error_duplicate_subintent_hash_1" + ); +} + +#[test] +fn test_manifest_generator_error_child_subintents_unsupported_by_manifest_type() { + // ManifestBuildError(ManifestBuildError::ChildSubintentsUnsupportedByManifestType) + check_manifest!( + ManifestKind::V1, + "manifest_generator_error_child_subintents_unsupported_by_manifest_type_1" + ); +} + +#[test] +fn test_manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type() { + // ManifestBuildError(ManifestBuildError::PreallocatedAddressesUnsupportedByManifestType) + check_manifest!( + ManifestKind::V2, + "manifest_generator_error_preallocated_addresses_unsupported_by_manifest_type_1" + ); +} + +#[test] +fn test_manifest_generator_error_header_instruction_must_come_first() { + // HeaderInstructionMustComeFirst + check_manifest!( + ManifestKind::SubintentV2, + "manifest_generator_error_header_instruction_must_come_first_1" + ); +} + +#[test] +fn test_manifest_generator_error_intent_cannot_be_used_in_value() { + // IntentCannotBeUsedInValue + check_manifest!( + ManifestKind::SubintentV2, + "manifest_generator_error_intent_cannot_be_used_in_value_1" + ); +} + +#[test] +fn test_manifest_generator_error_intent_cannot_be_used_as_value_kind() { + // IntentCannotBeUsedAsValueKind + check_manifest!( + ManifestKind::SubintentV2, + "manifest_generator_error_intent_cannot_be_used_as_value_kind_1" + ); +} + +#[test] +fn test_manifest_generator_error_named_intent_cannot_be_used_in_value() { + // NamedIntentCannotBeUsedInValue + check_manifest!( + ManifestKind::SubintentV2, + "manifest_generator_error_named_intent_cannot_be_used_in_value_1" + ); +} + +#[test] +fn test_manifest_generator_error_named_intent_cannot_be_used_as_value_kind() { + // NamedIntentCannotBeUsedAsValueKind + check_manifest!( + ManifestKind::SubintentV2, + "manifest_generator_error_named_intent_cannot_be_used_as_value_kind_1" + ); +} + +#[test] +fn test_manifest_generator_error_argument_could_not_be_read_as_expected_type() { + // ArgumentCouldNotBeReadAsExpectedType { type_name: String, error_message: String, }, + check_manifest!( + ManifestKind::V2, + "manifest_generator_error_argument_could_not_be_read_as_expected_type_1" + ); + check_manifest!( + ManifestKind::V2, + "manifest_generator_error_argument_could_not_be_read_as_expected_type_2" + ); + check_manifest!( + ManifestKind::V2, + "manifest_generator_error_argument_could_not_be_read_as_expected_type_3" + ); +} + #[test] fn test_manifest_compiler_error_plain_text() { check_manifest!( + ManifestKind::V1, "manifest_compiler_error_plain_text_1", BlobProvider::default(), - CompileErrorDiagnosticsStyle::PlainText ); } diff --git a/sbor-derive-common/Cargo.toml b/sbor-derive-common/Cargo.toml index 552a4e8e707..37ba06eb2a9 100644 --- a/sbor-derive-common/Cargo.toml +++ b/sbor-derive-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library for implementing SBOR derives, from the Radix DLT project." readme = "README.md" diff --git a/sbor-derive/Cargo.toml b/sbor-derive/Cargo.toml index cfdd06b124f..b5d75b92228 100644 --- a/sbor-derive/Cargo.toml +++ b/sbor-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A collection of macros for deriving SBOR implementations, from the Radix DLT project." readme = "README.md" diff --git a/sbor-tests/Cargo.toml b/sbor-tests/Cargo.toml index 34e25c8c5a7..9329601cb88 100644 --- a/sbor-tests/Cargo.toml +++ b/sbor-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sbor-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" [dependencies] diff --git a/sbor/Cargo.toml b/sbor/Cargo.toml index 8adbbc103d7..dd972256295 100644 --- a/sbor/Cargo.toml +++ b/sbor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "Reference implementation of the SBOR binary data format, from the Radix DLT project." readme = "README.md" diff --git a/sbor/src/lib.rs b/sbor/src/lib.rs index f2bd9fab953..c23f2ad2683 100644 --- a/sbor/src/lib.rs +++ b/sbor/src/lib.rs @@ -122,5 +122,10 @@ pub(crate) mod internal_prelude { pub use crate::prelude::*; // These are mostly used for more advanced use cases, // so aren't included in the general prelude + pub use crate::basic::*; + pub use crate::decoder::*; + pub use crate::encoder::*; + pub use crate::payload_validation::*; + pub use crate::schema::*; pub use crate::vec_traits::*; } diff --git a/sbor/src/payload_validation/payload_validator.rs b/sbor/src/payload_validation/payload_validator.rs index 78a46bafc7b..c1e5cd41693 100644 --- a/sbor/src/payload_validation/payload_validator.rs +++ b/sbor/src/payload_validation/payload_validator.rs @@ -780,7 +780,7 @@ mod tests { ]; check_location_path::>( payload, - "", // container state not established due to failing to read size + "[Root]", // container state not established due to failing to read size "DecodeError(InvalidSize)", ); } diff --git a/sbor/src/traversal/path_formatting.rs b/sbor/src/traversal/path_formatting.rs index 644560a5683..63ecd5ae962 100644 --- a/sbor/src/traversal/path_formatting.rs +++ b/sbor/src/traversal/path_formatting.rs @@ -233,7 +233,11 @@ pub trait PathAnnotate { if let Some(leaf) = self.annotated_leaf() { leaf.write(f, is_start_of_path)?; - }; + } else { + if is_start_of_path { + write!(f, "[Root]")?; + } + } Ok(()) } diff --git a/sbor/src/value.rs b/sbor/src/value.rs index fca666c449c..0237798a93f 100644 --- a/sbor/src/value.rs +++ b/sbor/src/value.rs @@ -1,13 +1,4 @@ -use crate::decode::*; -use crate::decoder::*; -use crate::encode::*; -use crate::encoder::*; -use crate::path::SborPathBuf; -use crate::rust::fmt::Debug; -use crate::rust::string::String; -use crate::rust::vec::Vec; -use crate::value_kind::*; -use crate::*; +use crate::internal_prelude::*; #[cfg(feature = "fuzzing")] use arbitrary::Arbitrary; @@ -77,6 +68,25 @@ pub enum Value> { }, } +impl> Value { + pub fn unit() -> Self { + Value::Tuple { fields: vec![] } + } + + pub fn tuple(fields: impl IntoIterator) -> Self { + Value::Tuple { + fields: fields.into_iter().collect(), + } + } + + pub fn enum_variant(discriminator: u8, fields: impl IntoIterator) -> Self { + Value::Enum { + discriminator, + fields: fields.into_iter().collect(), + } + } +} + /// Represents a custom SBOR value. pub trait CustomValue { fn get_custom_value_kind(&self) -> X; @@ -520,7 +530,6 @@ pub trait ValueVisitor> { #[cfg(test)] mod tests { - use super::*; use crate::internal_prelude::*; #[derive(Categorize, Encode)] diff --git a/sbor/src/vec_traits.rs b/sbor/src/vec_traits.rs index 47559947b58..8b1ca011acf 100644 --- a/sbor/src/vec_traits.rs +++ b/sbor/src/vec_traits.rs @@ -1,7 +1,4 @@ -use crate::{ - internal_prelude::*, validate_payload_against_schema, CustomExtension, CustomSchema, - Decoder as _, Describe, Encoder as _, ValidatableCustomExtension, VecDecoder, VecEncoder, -}; +use crate::internal_prelude::*; pub trait VecEncode: for<'a> Encode> {} impl Encode> + ?Sized> VecEncode for T {} diff --git a/scrypto-bindgen/Cargo.toml b/scrypto-bindgen/Cargo.toml index 212955cc6df..94191229be2 100644 --- a/scrypto-bindgen/Cargo.toml +++ b/scrypto-bindgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrypto-bindgen" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library for generating Scrypto bindings." readme = "README.md" diff --git a/scrypto-compiler/Cargo.toml b/scrypto-compiler/Cargo.toml index b37ec6d1fd3..549eaf3a80c 100644 --- a/scrypto-compiler/Cargo.toml +++ b/scrypto-compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrypto-compiler" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library for compiling Scrypto code, from the Radix DLT project." readme = "README.md" diff --git a/scrypto-compiler/tests/assets/scenario_1/Cargo.lock b/scrypto-compiler/tests/assets/scenario_1/Cargo.lock index 57d49cb4707..321ebfe867b 100644 --- a/scrypto-compiler/tests/assets/scenario_1/Cargo.lock +++ b/scrypto-compiler/tests/assets/scenario_1/Cargo.lock @@ -385,7 +385,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "radix-common", @@ -395,7 +395,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -420,7 +420,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -431,7 +431,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "const-sha1", @@ -451,7 +451,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "indexmap", "serde", @@ -459,7 +459,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -524,7 +524,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -537,7 +537,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -546,7 +546,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap", @@ -558,7 +558,7 @@ dependencies = [ [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -577,7 +577,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", diff --git a/scrypto-derive-tests/Cargo.toml b/scrypto-derive-tests/Cargo.toml index 360e2ddca36..a49ab8c8cb9 100644 --- a/scrypto-derive-tests/Cargo.toml +++ b/scrypto-derive-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrypto-derive-tests" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" [dev-dependencies] diff --git a/scrypto-derive/Cargo.toml b/scrypto-derive/Cargo.toml index 5a36be7cb9b..6af069c1e4a 100644 --- a/scrypto-derive/Cargo.toml +++ b/scrypto-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A collection of macros for writing Scrypto blueprints, from the Radix DLT project." readme = "README.md" diff --git a/scrypto-test/Cargo.toml b/scrypto-test/Cargo.toml index 63c5e150168..14b3323efb9 100644 --- a/scrypto-test/Cargo.toml +++ b/scrypto-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrypto-test" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "A library for testing Scrypto blueprints, from the Radix DLT project." readme = "README.md" @@ -77,4 +77,4 @@ post_run_db_check = [] coverage = ["radix-common/coverage", "radix-engine/coverage"] [lib] -bench = false \ No newline at end of file +bench = false diff --git a/scrypto-test/tests/blueprints/Cargo.lock b/scrypto-test/tests/blueprints/Cargo.lock index 644a5f85fa8..87a267dc980 100644 --- a/scrypto-test/tests/blueprints/Cargo.lock +++ b/scrypto-test/tests/blueprints/Cargo.lock @@ -415,7 +415,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "radix-common", @@ -425,7 +425,7 @@ dependencies = [ [[package]] name = "radix-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "blake2", @@ -450,7 +450,7 @@ dependencies = [ [[package]] name = "radix-common-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "paste", "proc-macro2", @@ -461,7 +461,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bitflags", "const-sha1", @@ -481,7 +481,7 @@ dependencies = [ [[package]] name = "radix-rust" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "hashbrown 0.13.2", "indexmap", @@ -490,7 +490,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", @@ -555,7 +555,7 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "sbor" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "hex", @@ -568,7 +568,7 @@ dependencies = [ [[package]] name = "sbor-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -577,7 +577,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "const-sha1", "indexmap", @@ -589,7 +589,7 @@ dependencies = [ [[package]] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "bech32", "const-sha1", @@ -608,7 +608,7 @@ dependencies = [ [[package]] name = "scrypto-derive" -version = "1.3.0-dev" +version = "1.3.0-rc.2" dependencies = [ "proc-macro2", "quote", diff --git a/scrypto/Cargo.toml b/scrypto/Cargo.toml index 1e90833ebe2..0eefc77fb83 100644 --- a/scrypto/Cargo.toml +++ b/scrypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "scrypto" -version = "1.3.0-dev" +version = "1.3.0-rc.2" edition = "2021" description = "The Scrypto standard library, from the Radix DLT project." readme = "README.md" diff --git a/test-cargo-crates-release.sh b/test-cargo-crates-release.sh deleted file mode 100755 index bfff6c21bf2..00000000000 --- a/test-cargo-crates-release.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -e -set -u - -echo "Publishing all cargo crates" -CARGO_FILES=("./scrypto-derive/Cargo.toml" "./radix-substate-store-queries/Cargo.toml" "./radix-common-derive/Cargo.toml" "./radix-engine-profiling/Cargo.toml" "./radix-substate-store-impls/Cargo.toml" "./radix-clis/Cargo.toml" "./radix-sbor-derive/Cargo.toml" "./sbor-derive/Cargo.toml" "./scrypto/Cargo.toml" "./scrypto-test/Cargo.toml" "./radix-transactions/Cargo.toml" "./radix-native-sdk/Cargo.toml" "./radix-blueprint-schema-init/Cargo.toml" "./scrypto-compiler/Cargo.toml" "./sbor-tests/Cargo.toml" "./radix-engine-interface/Cargo.toml" "./radix-rust/Cargo.toml" "./sbor-derive-common/Cargo.toml" "./radix-engine-profiling-derive/Cargo.toml" "./radix-common/Cargo.toml" "./sbor/Cargo.toml" "./scrypto-bindgen/Cargo.toml" "./scrypto-derive-tests/Cargo.toml" "./radix-engine/Cargo.toml" "./radix-transaction-scenarios/Cargo.toml" "./radix-engine-monkey-tests/Cargo.toml" "./radix-engine-tests/Cargo.toml" "./radix-substate-store-interface/Cargo.toml") -for toml_file_dir in ${CARGO_FILES[@]}; do - echo "Publishing crate in directory ${toml_file_dir}" - echo "cargo publish --dry-run --token "${CRATES_TOKEN}" --manifest-path ${toml_file_dir}" - cargo publish --dry-run --token "${CRATES_TOKEN}" --manifest-path ${toml_file_dir} - cargo package --list --manifest-path "${toml_file_dir}" -done diff --git a/update-cargo-toml-versions.sh b/update-cargo-toml-versions.sh index 5253cbc6d17..2e771deb201 100755 --- a/update-cargo-toml-versions.sh +++ b/update-cargo-toml-versions.sh @@ -6,8 +6,8 @@ export VERSION=$1 echo "Updating to version ${VERSION}" cargo install toml-cli -RADIX_CARGO_FILES=("./scrypto-derive/Cargo.toml" "./radix-substate-store-queries/Cargo.toml" "./radix-common-derive/Cargo.toml" "./radix-engine-profiling/Cargo.toml" "./radix-substate-store-impls/Cargo.toml" "./radix-clis/Cargo.toml" "./radix-sbor-derive/Cargo.toml" "./sbor-derive/Cargo.toml" "./scrypto/Cargo.toml" "./scrypto-test/Cargo.toml" "./radix-transactions/Cargo.toml" "./radix-native-sdk/Cargo.toml" "./radix-blueprint-schema-init/Cargo.toml" "./scrypto-bindgen/Cargo.toml" "./scrypto-compiler/Cargo.toml" "./sbor-tests/Cargo.toml" "./radix-engine-interface/Cargo.toml" "./radix-rust/Cargo.toml" "./sbor-derive-common/Cargo.toml" "./radix-engine-profiling-derive/Cargo.toml" "./radix-common/Cargo.toml" "./sbor/Cargo.toml" "./scrypto-derive-tests/Cargo.toml" "./radix-engine/Cargo.toml" "./radix-transaction-scenarios/Cargo.toml" "./radix-engine-monkey-tests/Cargo.toml" "./radix-engine-tests/Cargo.toml" "./radix-substate-store-interface/Cargo.toml" "./radix-engine-toolkit/Cargo.toml") -INTERNAL_PROJECT_LIST=("radix-blueprint-schema-init" "radix-common" "radix-common-derive" "radix-engine" "radix-engine-toolkit" "radix-engine-interface" "radix-engine-profiling" "radix-engine-profiling-derive" "radix-native-sdk" "radix-rust" "radix-sbor-derive" "radix-substate-store-impls" "radix-substate-store-interface" "radix-substate-store-queries" "radix-transaction-scenarios" "radix-transactions" "sbor" "sbor-derive" "sbor-derive-common" "scrypto" "scrypto-bindgen" "scrypto-compiler" "scrypto-derive" "scrypto-test") +RADIX_CARGO_FILES=("./scrypto-derive/Cargo.toml" "./radix-substate-store-queries/Cargo.toml" "./radix-common-derive/Cargo.toml" "./radix-engine-profiling/Cargo.toml" "./radix-substate-store-impls/Cargo.toml" "./radix-clis/Cargo.toml" "./radix-sbor-derive/Cargo.toml" "./sbor-derive/Cargo.toml" "./scrypto/Cargo.toml" "./scrypto-test/Cargo.toml" "./radix-transactions/Cargo.toml" "./radix-native-sdk/Cargo.toml" "./radix-blueprint-schema-init/Cargo.toml" "./scrypto-bindgen/Cargo.toml" "./scrypto-compiler/Cargo.toml" "./sbor-tests/Cargo.toml" "./radix-engine-interface/Cargo.toml" "./radix-rust/Cargo.toml" "./sbor-derive-common/Cargo.toml" "./radix-engine-profiling-derive/Cargo.toml" "./radix-common/Cargo.toml" "./sbor/Cargo.toml" "./scrypto-derive-tests/Cargo.toml" "./radix-engine/Cargo.toml" "./radix-transaction-scenarios/Cargo.toml" "./radix-engine-monkey-tests/Cargo.toml" "./radix-engine-tests/Cargo.toml" "./radix-substate-store-interface/Cargo.toml" "./radix-engine-toolkit-common/Cargo.toml") +INTERNAL_PROJECT_LIST=("radix-blueprint-schema-init" "radix-common" "radix-common-derive" "radix-engine" "radix-engine-toolkit-common" "radix-engine-interface" "radix-engine-profiling" "radix-engine-profiling-derive" "radix-native-sdk" "radix-rust" "radix-sbor-derive" "radix-substate-store-impls" "radix-substate-store-interface" "radix-substate-store-queries" "radix-transaction-scenarios" "radix-transactions" "sbor" "sbor-derive" "sbor-derive-common" "scrypto" "scrypto-bindgen" "scrypto-compiler" "scrypto-derive" "scrypto-test") NUMBER_OF_PROJECTS=${#INTERNAL_PROJECT_LIST[@]} @@ -33,22 +33,6 @@ for toml_file in ${RADIX_CARGO_FILES[@]}; do mv "${FILENAME}.new" "${FILENAME}" done -for toml_file in ${RADIX_CARGO_FILES[@]}; do - echo "Update dependencies of $toml_file" - for (( i=0; i<$NUMBER_OF_PROJECTS; i++ )) - do - set +e - value=$(toml get $toml_file "dependencies.${INTERNAL_PROJECT_LIST[$i]}" -r); - ret=$? - set -e - if [ $ret -eq 0 ]; then - echo "Setting ${INTERNAL_PROJECT_LIST[$i]} version dependency from $value to ${VERSION}" - toml set $toml_file "dependencies.${INTERNAL_PROJECT_LIST[$i]}.version" "${VERSION}" > $toml_file.new - mv $toml_file.new $toml_file - fi - done -done - ./update-cargo-locks-minimally.sh echo "Done" \ No newline at end of file