diff --git a/.deny.toml b/.deny.toml index 5f3a9161..4d69e70b 100644 --- a/.deny.toml +++ b/.deny.toml @@ -60,7 +60,7 @@ allow-git = [ "https://github.com/logos-blockchain/logos-blockchain.git", "https://github.com/logos-blockchain/logos-blockchain-circuits.git", "https://github.com/logos-blockchain/logos-blockchain-rust-rapidsnark.git", - "https://github.com/arkworks-rs/spongefish.git" + "https://github.com/arkworks-rs/spongefish.git", ] unknown-git = "deny" unknown-registry = "deny" diff --git a/.github/actions/install-logos-blockchain-circuits/action.yaml b/.github/actions/install-logos-blockchain-circuits/action.yaml deleted file mode 100644 index 361ace65..00000000 --- a/.github/actions/install-logos-blockchain-circuits/action.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: Setup Logos Blockchain Circuits - -description: Set up Logos Blockchain Circom Circuits, Rapidsnark prover and Rapidsnark verifier using the setup-logos-blockchain-circuits.sh script. - -inputs: - github-token: - description: GitHub token for downloading releases - required: true - -runs: - using: "composite" - steps: - - name: Setup logos-blockchain-circuits - shell: bash - working-directory: ${{ github.workspace }} - env: - GITHUB_TOKEN: ${{ inputs.github-token }} - run: | - curl -sSL https://raw.githubusercontent.com/logos-blockchain/logos-blockchain/6ac348bea4160ca708b70a86b3964e9f1ce82fff/scripts/setup-logos-blockchain-circuits.sh | bash \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40a0a84c..a8c588b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Lint programs env: RISC0_SKIP_BUILD: "1" - run: cargo clippy -p "*programs" -- -D warnings + run: cargo clippy -p "*program" -- -D warnings unit-tests: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-review-board.yml b/.github/workflows/pr-review-board.yml new file mode 100644 index 00000000..be19e43d --- /dev/null +++ b/.github/workflows/pr-review-board.yml @@ -0,0 +1,118 @@ +name: PR review board + +# Keeps the org "LEZ PR Review Queue" board in sync for this repo. +# Adds each PR to the board and sets its Status column, enforcing the +# team rule that Approved means 2+ approvals. + +on: + pull_request: + types: [opened, reopened, ready_for_review, converted_to_draft, closed, labeled, unlabeled] + pull_request_review: + types: [submitted, dismissed] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: pr-review-board-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + sync: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.PROJECT_PAT }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + PROJECT_ID: PVT_kwDODrxXXM4BbXZ_ + STATUS_FIELD: PVTSSF_lADODrxXXM4BbXZ_zhWIZnU + OPT_DRAFT: f735bdac + OPT_NEEDS: cbc7321d + OPT_CHANGES: 302bbbd6 + OPT_APPROVED: c9e0743e + PRIORITY_FIELD: PVTSSF_lADODrxXXM4BbXZ_zhWIyPA + PRIO_URGENT: b72854fd + PRIO_HIGH: 5e87ebf9 + PRIO_MEDIUM: 22367611 + PRIO_LOW: 41f60140 + steps: + - name: Sync PR status to board + run: | + set -euo pipefail + + DATA=$(gh pr view "$PR" --repo "$REPO" --json id,isDraft,mergedAt,state,latestReviews,labels) + PR_NODE=$(echo "$DATA" | jq -r '.id') + IS_DRAFT=$(echo "$DATA" | jq -r '.isDraft') + MERGED=$(echo "$DATA" | jq -r '(.mergedAt != null)') + STATE=$(echo "$DATA" | jq -r '.state') + NAP=$(echo "$DATA" | jq '[.latestReviews[] | select(.state=="APPROVED")] | length') + NCH=$(echo "$DATA" | jq '[.latestReviews[] | select(.state=="CHANGES_REQUESTED")] | length') + + if [ "$STATE" = "CLOSED" ] && [ "$MERGED" != "true" ]; then + echo "PR #$PR closed unmerged; leaving board entry untouched." + exit 0 + fi + + # Add to the project (idempotent: returns existing item if already present) + ITEM=$(gh api graphql -f query=' + mutation($p:ID!, $c:ID!) { + addProjectV2ItemById(input:{projectId:$p, contentId:$c}) { item { id } } + }' -f p="$PROJECT_ID" -f c="$PR_NODE" --jq '.data.addProjectV2ItemById.item.id') + + # Merged PRs are archived off the active board + if [ "$MERGED" = "true" ]; then + gh api graphql -f query=' + mutation($p:ID!, $i:ID!) { + archiveProjectV2Item(input:{projectId:$p, itemId:$i}) { item { id } } + }' -f p="$PROJECT_ID" -f i="$ITEM" + echo "$REPO#$PR merged -> archived" + exit 0 + fi + + # Status + if [ "$IS_DRAFT" = "true" ]; then + OPT=$OPT_DRAFT; LABEL="Draft" + elif [ "$NCH" -gt 0 ]; then + OPT=$OPT_CHANGES; LABEL="Changes requested" + elif [ "$NAP" -ge 2 ]; then + OPT=$OPT_APPROVED; LABEL="Approved" + else + OPT=$OPT_NEEDS; LABEL="Needs review" + fi + + gh api graphql -f query=' + mutation($p:ID!, $i:ID!, $f:ID!, $o:String!) { + updateProjectV2ItemFieldValue(input:{ + projectId:$p, itemId:$i, fieldId:$f, + value:{ singleSelectOptionId:$o } + }) { projectV2Item { id } } + }' -f p="$PROJECT_ID" -f i="$ITEM" -f f="$STATUS_FIELD" -f o="$OPT" + + # Set Priority from a priority:* label (clear it when none is present) + LABELS=$(echo "$DATA" | jq -r '.labels[].name') + case "$LABELS" in + *priority:urgent*) POPT=$PRIO_URGENT ;; + *priority:high*) POPT=$PRIO_HIGH ;; + *priority:medium*) POPT=$PRIO_MEDIUM ;; + *priority:low*) POPT=$PRIO_LOW ;; + *) POPT="" ;; + esac + if [ -n "$POPT" ]; then + gh api graphql -f query=' + mutation($p:ID!, $i:ID!, $f:ID!, $o:String!) { + updateProjectV2ItemFieldValue(input:{ + projectId:$p, itemId:$i, fieldId:$f, + value:{ singleSelectOptionId:$o } + }) { projectV2Item { id } } + }' -f p="$PROJECT_ID" -f i="$ITEM" -f f="$PRIORITY_FIELD" -f o="$POPT" + else + gh api graphql -f query=' + mutation($p:ID!, $i:ID!, $f:ID!) { + clearProjectV2ItemFieldValue(input:{ + projectId:$p, itemId:$i, fieldId:$f + }) { projectV2Item { id } } + }' -f p="$PROJECT_ID" -f i="$ITEM" -f f="$PRIORITY_FIELD" + fi + + echo "$REPO#$PR (approvals=$NAP changes=$NCH) -> $LABEL" diff --git a/.github/workflows/publish_images.yml b/.github/workflows/publish_images.yml index bfddda6b..28abb67c 100644 --- a/.github/workflows/publish_images.yml +++ b/.github/workflows/publish_images.yml @@ -7,7 +7,38 @@ on: - "v*" jobs: + # Shared base (toolchain + r0vm), single source of truth in + # lez/docker/risc0-base.Dockerfile. Built and pushed once so the service + # builds below can pull it as the `risc0_base` named context. The + # docker-container builder resolves named contexts from the registry (not the + # host image store), so the base must be pushed, not just loaded. + risc0_base: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push risc0 base image + uses: docker/build-push-action@v5 + with: + context: . + file: ./lez/docker/risc0-base.Dockerfile + push: true + tags: ${{ secrets.DOCKER_REGISTRY }}/${{ github.repository }}/risc0_base:sha-${{ github.sha }} + cache-from: type=gha,scope=risc0-base + cache-to: type=gha,mode=max,scope=risc0-base + publish: + needs: risc0_base runs-on: ubuntu-latest strategy: matrix: @@ -62,5 +93,6 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: ${{ matrix.build_args }} + build-contexts: risc0_base=docker-image://${{ secrets.DOCKER_REGISTRY }}/${{ github.repository }}/risc0_base:sha-${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 4605856c..4c1a18c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,25 @@ .gitconfig + res/ target/ deps/ data/ + .idea/ .vscode/ -rocksdb + +rocksdb* sequencer/service/data/ storage.json + result + wallet-ffi/wallet_ffi.h bedrock_signing_key integration_tests/configs/debug/ venv/ + keycard_wallet/python/__pycache__/ keycard_wallet/python/keycard-py/ + +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 6dd4dce9..21aaaafd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,6 +123,7 @@ dependencies = [ "amm_core", "lee", "lee_core", + "programs", "token_core", ] @@ -513,6 +514,24 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" +[[package]] +name = "associated_token_account_core" +version = "0.1.0" +dependencies = [ + "lee_core", + "risc0-zkvm", + "serde", +] + +[[package]] +name = "associated_token_account_program" +version = "0.1.0" +dependencies = [ + "associated_token_account_core", + "lee_core", + "token_core", +] + [[package]] name = "astral-tokio-tar" version = "0.6.2" @@ -665,24 +684,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "ata_core" -version = "0.1.0" -dependencies = [ - "lee_core", - "risc0-zkvm", - "serde", -] - -[[package]] -name = "ata_program" -version = "0.1.0" -dependencies = [ - "ata_core", - "lee_core", - "token_core", -] - [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -758,6 +759,14 @@ dependencies = [ "serde", ] +[[package]] +name = "authenticated_transfer_program" +version = "0.1.0" +dependencies = [ + "authenticated_transfer_core", + "lee_core", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -975,9 +984,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "4ed83caece3afc59919481b33b472e1432d1abc4641ed9100be142ef5110b406" dependencies = [ "hex-conservative", ] @@ -1149,11 +1158,31 @@ dependencies = [ name = "bridge_lock_core" version = "0.1.0" dependencies = [ - "lee", "lee_core", "serde", ] +[[package]] +name = "bridge_lock_program" +version = "0.1.0" +dependencies = [ + "bridge_lock_core", + "cross_zone_outbox_core", + "lee_core", + "risc0-zkvm", + "wrapped_token_core", +] + +[[package]] +name = "bridge_program" +version = "0.1.0" +dependencies = [ + "authenticated_transfer_core", + "bridge_core", + "lee_core", + "vault_core", +] + [[package]] name = "bs58" version = "0.5.1" @@ -1163,6 +1192,14 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "build_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "risc0-binfmt", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -1461,6 +1498,14 @@ dependencies = [ "lee_core", ] +[[package]] +name = "clock_program" +version = "0.1.0" +dependencies = [ + "clock_core", + "lee_core", +] + [[package]] name = "cmov" version = "0.5.4" @@ -1523,9 +1568,11 @@ dependencies = [ "lee_core", "log", "logos-blockchain-common-http-client", + "programs", "serde", "serde_with", "sha2", + "system_accounts", "thiserror 2.0.18", ] @@ -1805,19 +1852,37 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "cross_zone" +version = "0.1.0" +dependencies = [ + "bridge_lock_core", + "cross_zone_inbox_core", + "lee", + "lee_core", + "ping_core", + "programs", + "risc0-zkvm", +] + [[package]] name = "cross_zone_inbox_core" version = "0.1.0" dependencies = [ "borsh", - "bridge_lock_core", - "lee", "lee_core", - "ping_core", "risc0-zkvm", "serde", ] +[[package]] +name = "cross_zone_inbox_program" +version = "0.1.0" +dependencies = [ + "cross_zone_inbox_core", + "lee_core", +] + [[package]] name = "cross_zone_outbox_core" version = "0.1.0" @@ -1828,6 +1893,14 @@ dependencies = [ "serde", ] +[[package]] +name = "cross_zone_outbox_program" +version = "0.1.0" +dependencies = [ + "cross_zone_outbox_core", + "lee_core", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1964,7 +2037,7 @@ version = "0.1.0" dependencies = [ "amm_core", "anyhow", - "ata_core", + "associated_token_account_core", "authenticated_transfer_core", "borsh", "clap", @@ -1972,9 +2045,11 @@ dependencies = [ "criterion", "lee", "lee_core", + "programs", "risc0-zkvm", "serde", "serde_json", + "test_programs", "token_core", ] @@ -2070,7 +2145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 2.0.117", + "syn 1.0.109", ] [[package]] @@ -2672,6 +2747,16 @@ dependencies = [ "serde", ] +[[package]] +name = "faucet_program" +version = "0.1.0" +dependencies = [ + "authenticated_transfer_core", + "faucet_core", + "lee_core", + "vault_core", +] + [[package]] name = "fd-lock" version = "4.0.4" @@ -3831,11 +3916,13 @@ name = "indexer_core" version = "0.1.0" dependencies = [ "anyhow", + "arc-swap", "async-stream", "authenticated_transfer_core", "borsh", "bridge_lock_core", "common", + "cross_zone", "cross_zone_inbox_core", "futures", "hex", @@ -3846,6 +3933,7 @@ dependencies = [ "logos-blockchain-core", "logos-blockchain-zone-sdk", "ping_core", + "programs", "risc0-zkvm", "serde", "serde_json", @@ -3860,16 +3948,15 @@ dependencies = [ name = "indexer_ffi" version = "0.1.0" dependencies = [ - "anyhow", "cbindgen", - "indexer_service", + "env_logger", + "futures", + "indexer_core", "indexer_service_protocol", - "indexer_service_rpc", - "jsonrpsee", "lee", "log", + "serde_json", "tokio", - "url", ] [[package]] @@ -3996,7 +4083,7 @@ name = "integration_tests" version = "0.1.0" dependencies = [ "anyhow", - "ata_core", + "associated_token_account_core", "authenticated_transfer_core", "borsh", "bridge_core", @@ -4021,13 +4108,16 @@ dependencies = [ "logos-blockchain-zone-sdk", "num-bigint 0.4.6", "ping_core", + "programs", "reqwest", "risc0-zkvm", "sequencer_core", "sequencer_service_rpc", "serde_json", + "system_accounts", "tempfile", "test_fixtures", + "test_programs", "token_core", "tokio", "vault_core", @@ -4542,33 +4632,24 @@ name = "lee" version = "0.1.0" dependencies = [ "anyhow", - "authenticated_transfer_core", "borsh", - "bridge_core", - "bridge_lock_core", - "clock_core", - "cross_zone_inbox_core", - "cross_zone_outbox_core", + "build_utils", "env_logger", - "faucet_core", "hex", "hex-literal 1.1.0", "k256", "lee_core", "log", - "ping_core", "rand 0.8.6", "risc0-binfmt", - "risc0-build", "risc0-zkvm", "serde", "serde_with", "sha2", "test-case", - "test_program_methods", + "test_methods", "thiserror 2.0.18", "token_core", - "wrapped_token_core", ] [[package]] @@ -7160,6 +7241,23 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pinata_program" +version = "0.1.0" +dependencies = [ + "lee_core", + "risc0-zkvm", +] + +[[package]] +name = "pinata_token_program" +version = "0.1.0" +dependencies = [ + "lee_core", + "risc0-zkvm", + "token_core", +] + [[package]] name = "ping_core" version = "0.1.0" @@ -7168,6 +7266,23 @@ dependencies = [ "serde", ] +[[package]] +name = "ping_receiver_program" +version = "0.1.0" +dependencies = [ + "lee_core", + "ping_core", +] + +[[package]] +name = "ping_sender_program" +version = "0.1.0" +dependencies = [ + "cross_zone_outbox_core", + "lee_core", + "ping_core", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -7321,6 +7436,14 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "privacy_preserving_circuit_program" +version = "0.1.0" +dependencies = [ + "lee_core", + "risc0-zkvm", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -7422,32 +7545,26 @@ dependencies = [ "wallet", ] -[[package]] -name = "program_methods" -version = "0.1.0" -dependencies = [ - "risc0-build", -] - [[package]] name = "programs" version = "0.1.0" dependencies = [ "amm_core", "amm_program", - "ata_core", - "ata_program", + "associated_token_account_core", + "associated_token_account_program", "authenticated_transfer_core", "bridge_core", "bridge_lock_core", + "build_utils", "clock_core", "cross_zone_inbox_core", "cross_zone_outbox_core", "faucet_core", + "lee", "lee_core", "ping_core", "risc0-zkvm", - "serde", "token_core", "token_program", "vault_core", @@ -7671,9 +7788,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "bytes", "getrandom 0.3.4", @@ -8887,10 +9004,10 @@ dependencies = [ "anyhow", "borsh", "bridge_core", - "bridge_lock_core", "bytesize", "chrono", "common", + "cross_zone", "cross_zone_inbox_core", "faucet_core", "futures", @@ -8905,14 +9022,17 @@ dependencies = [ "logos-blockchain-zone-sdk", "mempool", "num-bigint 0.4.6", + "programs", "rand 0.8.6", "risc0-zkvm", "serde", "serde_json", "storage", + "system_accounts", "tempfile", - "test_program_methods", + "test_programs", "testnet_initial_state", + "token_core", "tokio", "url", "vault_core", @@ -8933,6 +9053,7 @@ dependencies = [ "lee", "log", "mempool", + "programs", "sequencer_core", "sequencer_service_protocol", "sequencer_service_rpc", @@ -9451,7 +9572,9 @@ dependencies = [ "borsh", "common", "lee", + "programs", "rocksdb", + "system_accounts", "tempfile", "thiserror 2.0.18", ] @@ -9604,6 +9727,17 @@ dependencies = [ "libc", ] +[[package]] +name = "system_accounts" +version = "0.1.0" +dependencies = [ + "bridge_core", + "clock_core", + "faucet_core", + "lee_core", + "programs", +] + [[package]] name = "tachys" version = "0.2.15" @@ -9726,6 +9860,7 @@ dependencies = [ "lee", "lee_core", "log", + "programs", "sequencer_core", "sequencer_service", "sequencer_service_rpc", @@ -9739,14 +9874,23 @@ dependencies = [ ] [[package]] -name = "test_program_methods" +name = "test_methods" version = "0.1.0" dependencies = [ "risc0-build", ] [[package]] -name = "test_programs" +name = "test_methods_guests" +version = "0.1.0" +dependencies = [ + "lee_core", + "risc0-zkvm", + "serde", +] + +[[package]] +name = "test_program_guests" version = "0.1.0" dependencies = [ "authenticated_transfer_core", @@ -9754,7 +9898,14 @@ dependencies = [ "faucet_core", "lee_core", "risc0-zkvm", - "serde", +] + +[[package]] +name = "test_programs" +version = "0.1.0" +dependencies = [ + "lee", + "risc0-build", ] [[package]] @@ -9794,11 +9945,13 @@ dependencies = [ name = "testnet_initial_state" version = "0.1.0" dependencies = [ - "common", "key_protocol", "lee", "lee_core", + "programs", "serde", + "system_accounts", + "wrapped_token_core", ] [[package]] @@ -10756,6 +10909,15 @@ dependencies = [ "serde", ] +[[package]] +name = "vault_program" +version = "0.1.0" +dependencies = [ + "authenticated_transfer_core", + "lee_core", + "vault_core", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -10784,8 +10946,8 @@ version = "0.1.0" dependencies = [ "amm_core", "anyhow", + "associated_token_account_core", "async-stream", - "ata_core", "authenticated_transfer_core", "base58", "bincode", @@ -10807,6 +10969,7 @@ dependencies = [ "lee_core", "log", "optfield", + "programs", "pyo3", "rand 0.8.6", "rpassword", @@ -10814,6 +10977,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "system_accounts", "tempfile", "testnet_initial_state", "thiserror 2.0.18", @@ -10828,16 +10992,19 @@ dependencies = [ name = "wallet-ffi" version = "0.1.0" dependencies = [ + "bip39", "cbindgen", "common", "key_protocol", "lee", "lee_core", + "programs", "risc0-zkvm", "sequencer_service_rpc", "serde_json", "tempfile", "tokio", + "vault_core", "wallet", ] @@ -11504,6 +11671,14 @@ dependencies = [ "serde", ] +[[package]] +name = "wrapped_token_program" +version = "0.1.0" +dependencies = [ + "lee_core", + "wrapped_token_core", +] + [[package]] name = "writeable" version = "0.6.3" diff --git a/Cargo.toml b/Cargo.toml index f1410330..932325b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,30 +5,17 @@ license = "MIT or Apache-2.0" resolver = "3" members = [ "integration_tests", + "lez/storage", "lee/key_protocol", "lee/state_machine", "lee/state_machine/core", - "lez/mempool", - "lez/wallet", - "lez/wallet-ffi", - "lez/common", - "programs/amm/core", - "programs/amm", - "programs/clock/core", - "programs/token/core", - "programs/token", - "programs/associated_token_account/core", - "programs/associated_token_account", - "programs/authenticated_transfer/core", - "programs/faucet/core", - "programs/bridge/core", - "programs/vault/core", - "programs/cross_zone_outbox/core", - "programs/cross_zone_inbox/core", - "programs/ping/core", - "programs/wrapped_token/core", - "programs/bridge_lock/core", + "lee/privacy_preserving_circuit", + "lee/state_machine/test_methods", + "lee/state_machine/test_methods/guest", + + "lez", + "lez/system_accounts", "lez/sequencer/core", "lez/sequencer/service", "lez/sequencer/service/protocol", @@ -38,18 +25,43 @@ members = [ "lez/indexer/service/protocol", "lez/indexer/service/rpc", "lez/explorer_service", - "program_methods", - "program_methods/guest", - "test_program_methods", - "test_program_methods/guest", + "lez/testnet_initial_state", + "lez/indexer/ffi", + "lez/keycard_wallet", + "lez/mempool", + "lez/wallet", + "lez/wallet-ffi", + "lez/common", + "lez/programs", + "lez/programs/amm", + "lez/programs/associated_token_account", + "lez/programs/authenticated_transfer", + "lez/programs/bridge", + "lez/programs/clock", + "lez/programs/faucet", + "lez/programs/pinata", + "lez/programs/pinata_token", + "lez/programs/token", + "lez/programs/vault", + "lez/programs/cross_zone_inbox", + "lez/programs/cross_zone_outbox", + "lez/programs/bridge_lock", + "lez/programs/wrapped_token", + "lez/programs/ping_sender", + "lez/programs/ping_receiver", + "lez/cross_zone", + + "test_programs", + "test_programs/guest", + "examples/program_deployment", "examples/program_deployment/methods", "examples/program_deployment/methods/guest", - "lez/testnet_initial_state", - "lez/indexer/ffi", - "lez", - "lez/keycard_wallet", + + "build_utils", + "test_fixtures", + "tools/cycle_bench", "tools/crypto_primitives_bench", "tools/integration_bench", @@ -74,23 +86,29 @@ wallet = { path = "lez/wallet" } wallet-ffi = { path = "lez/wallet-ffi", default-features = false } indexer_ffi = { path = "lez/indexer/ffi" } lez = { path = "lez" } -clock_core = { path = "programs/clock/core" } -token_core = { path = "programs/token/core" } -token_program = { path = "programs/token" } -amm_core = { path = "programs/amm/core" } -amm_program = { path = "programs/amm" } -ata_core = { path = "programs/associated_token_account/core" } -ata_program = { path = "programs/associated_token_account" } -authenticated_transfer_core = { path = "programs/authenticated_transfer/core" } -faucet_core = { path = "programs/faucet/core" } -bridge_core = { path = "programs/bridge/core" } -vault_core = { path = "programs/vault/core" } -cross_zone_outbox_core = { path = "programs/cross_zone_outbox/core" } -cross_zone_inbox_core = { path = "programs/cross_zone_inbox/core" } -ping_core = { path = "programs/ping/core" } -wrapped_token_core = { path = "programs/wrapped_token/core" } -bridge_lock_core = { path = "programs/bridge_lock/core" } -test_program_methods = { path = "test_program_methods" } +programs = { path = "lez/programs", default-features = false, features = [ + "artifacts", +] } +system_accounts = { path = "lez/system_accounts" } +clock_core = { path = "lez/programs/clock/core" } +token_core = { path = "lez/programs/token/core" } +token_program = { path = "lez/programs/token" } +amm_core = { path = "lez/programs/amm/core" } +amm_program = { path = "lez/programs/amm" } +associated_token_account_core = { path = "lez/programs/associated_token_account/core" } +ata_program = { path = "lez/programs/associated_token_account" } +authenticated_transfer_core = { path = "lez/programs/authenticated_transfer/core" } +faucet_core = { path = "lez/programs/faucet/core" } +bridge_core = { path = "lez/programs/bridge/core" } +vault_core = { path = "lez/programs/vault/core" } +cross_zone_inbox_core = { path = "lez/programs/cross_zone_inbox/core" } +cross_zone_outbox_core = { path = "lez/programs/cross_zone_outbox/core" } +bridge_lock_core = { path = "lez/programs/bridge_lock/core" } +wrapped_token_core = { path = "lez/programs/wrapped_token/core" } +ping_core = { path = "lez/programs/ping_core" } +cross_zone = { path = "lez/cross_zone" } +build_utils = { path = "build_utils" } +test_programs = { path = "test_programs" } testnet_initial_state = { path = "lez/testnet_initial_state" } keycard_wallet = { path = "lez/keycard_wallet" } test_fixtures = { path = "test_fixtures" } @@ -127,6 +145,7 @@ hex = "0.4.3" bytemuck = "1.24.0" bytesize = { version = "2.3.1", features = ["serde"] } humantime-serde = "1.1" +arc-swap = "1.7" humantime = "2.1" aes-gcm = "0.10.3" toml = "0.9.8" diff --git a/Justfile b/Justfile index 9553c71a..86ba677c 100644 --- a/Justfile +++ b/Justfile @@ -4,8 +4,6 @@ default: @just --list # ---- Configuration ---- -METHODS_PATH := "program_methods" -TEST_METHODS_PATH := "test_program_methods" ARTIFACTS := "artifacts" # On macOS the integration-test binary links pyo3 against the CommandLineTools @@ -16,12 +14,20 @@ DEMO_ENV := if os() == "macos" { "DYLD_FALLBACK_FRAMEWORK_PATH=/Library/Develope # Build risc0 program artifacts. build-artifacts: @echo "๐Ÿ”จ Building artifacts" - @for methods_path in {{METHODS_PATH}} {{TEST_METHODS_PATH}}; do \ - echo "Building artifacts for $methods_path"; \ - CARGO_TARGET_DIR=target/$methods_path cargo risczero build --manifest-path $methods_path/guest/Cargo.toml; \ - mkdir -p {{ARTIFACTS}}/$methods_path; \ - cp target/$methods_path/riscv32im-risc0-zkvm-elf/docker/*.bin {{ARTIFACTS}}/$methods_path; \ - done + @rm -rf {{ARTIFACTS}} + @just build-artifact lee/privacy_preserving_circuit + @just build-artifact lez/programs programs + +build-artifact methods_path features="": + @echo "Building artifacts for {{methods_path}}" + @rm -rf target/{{methods_path}}/riscv32im-risc0-zkvm-elf/docker/*.bin + @if [ "{{features}}" = "" ]; then \ + CARGO_TARGET_DIR=target/{{methods_path}} cargo risczero build --manifest-path {{methods_path}}/Cargo.toml; \ + else \ + CARGO_TARGET_DIR=target/{{methods_path}} cargo risczero build --no-default-features --features {{features}} --manifest-path {{methods_path}}/Cargo.toml; \ + fi + @mkdir -p {{ARTIFACTS}}/{{methods_path}} + @cp target/{{methods_path}}/riscv32im-risc0-zkvm-elf/docker/*.bin {{ARTIFACTS}}/{{methods_path}} # Format codebase. fmt: @@ -64,10 +70,10 @@ run-indexer mock="": @echo "๐Ÿ” Running indexer" @if [ "{{mock}}" = "mock" ]; then \ echo "๐Ÿงช Using mock data"; \ - RUST_LOG=info cargo run --release --features mock-responses -p indexer_service configs/indexer_config.json; \ + RUST_LOG=info cargo run --release --features mock-responses -p indexer_service configs/debug/indexer_config.json; \ else \ echo "๐Ÿš€ Using real data"; \ - RUST_LOG=info cargo run --release -p indexer_service configs/indexer_config.json; \ + RUST_LOG=info cargo run --release -p indexer_service configs/debug/indexer_config.json; \ fi # Run Explorer. @@ -111,7 +117,7 @@ clean: @echo "๐Ÿงน Cleaning run artifacts" rm -rf lez/sequencer/service/bedrock_signing_key rm -rf lez/sequencer/service/rocksdb - rm -rf lez/indexer/service/rocksdb + rm -rf lez/indexer/service/rocksdb* rm -rf lez/wallet/configs/debug/storage.json - rm -rf rocksdb + rm -rf rocksdb* cd bedrock && docker compose down -v diff --git a/README.md b/README.md index e0de266f..401fff15 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ The sequencer and logos blockchain node can be run locally: - `docker compose up` 2. On another terminal go to the `logos-blockchain/logos-execution-zone` repo and run indexer service: - - `RUST_LOG=info cargo run -p indexer_service lez/indexer/service/configs/indexer_config.json` + - `RUST_LOG=info cargo run -p indexer_service lez/indexer/service/configs/debug/indexer_config.json` 3. On another terminal go to the `logos-blockchain/logos-execution-zone` repo and run the sequencer: - `RUST_LOG=info cargo run -p sequencer_service lez/sequencer/service/configs/debug/sequencer_config.json` diff --git a/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin b/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin new file mode 100644 index 00000000..e0a383b5 Binary files /dev/null and b/artifacts/lee/privacy_preserving_circuit/privacy_preserving_circuit.bin differ diff --git a/artifacts/lez/programs/amm.bin b/artifacts/lez/programs/amm.bin new file mode 100644 index 00000000..c40ea0a7 Binary files /dev/null and b/artifacts/lez/programs/amm.bin differ diff --git a/artifacts/lez/programs/associated_token_account.bin b/artifacts/lez/programs/associated_token_account.bin new file mode 100644 index 00000000..ce8b928f Binary files /dev/null and b/artifacts/lez/programs/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/lez/programs/authenticated_transfer.bin similarity index 58% rename from artifacts/program_methods/authenticated_transfer.bin rename to artifacts/lez/programs/authenticated_transfer.bin index 481baa83..6fbc157c 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/lez/programs/authenticated_transfer.bin differ diff --git a/artifacts/lez/programs/bridge.bin b/artifacts/lez/programs/bridge.bin new file mode 100644 index 00000000..8a0d78c3 Binary files /dev/null and b/artifacts/lez/programs/bridge.bin differ diff --git a/artifacts/program_methods/bridge_lock.bin b/artifacts/lez/programs/bridge_lock.bin similarity index 50% rename from artifacts/program_methods/bridge_lock.bin rename to artifacts/lez/programs/bridge_lock.bin index d4aa0cd1..dfd97730 100644 Binary files a/artifacts/program_methods/bridge_lock.bin and b/artifacts/lez/programs/bridge_lock.bin differ diff --git a/artifacts/program_methods/clock.bin b/artifacts/lez/programs/clock.bin similarity index 75% rename from artifacts/program_methods/clock.bin rename to artifacts/lez/programs/clock.bin index 151a72d7..ef31eccd 100644 Binary files a/artifacts/program_methods/clock.bin and b/artifacts/lez/programs/clock.bin differ diff --git a/artifacts/lez/programs/cross_zone_inbox.bin b/artifacts/lez/programs/cross_zone_inbox.bin new file mode 100644 index 00000000..933a4ba1 Binary files /dev/null and b/artifacts/lez/programs/cross_zone_inbox.bin differ diff --git a/artifacts/lez/programs/cross_zone_outbox.bin b/artifacts/lez/programs/cross_zone_outbox.bin new file mode 100644 index 00000000..4babdf90 Binary files /dev/null and b/artifacts/lez/programs/cross_zone_outbox.bin differ diff --git a/artifacts/program_methods/faucet.bin b/artifacts/lez/programs/faucet.bin similarity index 58% rename from artifacts/program_methods/faucet.bin rename to artifacts/lez/programs/faucet.bin index 1385c2e1..662394ea 100644 Binary files a/artifacts/program_methods/faucet.bin and b/artifacts/lez/programs/faucet.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/lez/programs/pinata.bin similarity index 58% rename from artifacts/program_methods/pinata.bin rename to artifacts/lez/programs/pinata.bin index 7ae7c005..93b5f35f 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/lez/programs/pinata.bin differ diff --git a/artifacts/lez/programs/pinata_token.bin b/artifacts/lez/programs/pinata_token.bin new file mode 100644 index 00000000..48e1c529 Binary files /dev/null and b/artifacts/lez/programs/pinata_token.bin differ diff --git a/artifacts/program_methods/ping_receiver.bin b/artifacts/lez/programs/ping_receiver.bin similarity index 69% rename from artifacts/program_methods/ping_receiver.bin rename to artifacts/lez/programs/ping_receiver.bin index e5c1680e..fd5efe3b 100644 Binary files a/artifacts/program_methods/ping_receiver.bin and b/artifacts/lez/programs/ping_receiver.bin differ diff --git a/artifacts/program_methods/ping_sender.bin b/artifacts/lez/programs/ping_sender.bin similarity index 59% rename from artifacts/program_methods/ping_sender.bin rename to artifacts/lez/programs/ping_sender.bin index c2480bc3..395241ac 100644 Binary files a/artifacts/program_methods/ping_sender.bin and b/artifacts/lez/programs/ping_sender.bin differ diff --git a/artifacts/lez/programs/token.bin b/artifacts/lez/programs/token.bin new file mode 100644 index 00000000..043e9c06 Binary files /dev/null and b/artifacts/lez/programs/token.bin differ diff --git a/artifacts/program_methods/vault.bin b/artifacts/lez/programs/vault.bin similarity index 56% rename from artifacts/program_methods/vault.bin rename to artifacts/lez/programs/vault.bin index 57bba45e..df4b2b16 100644 Binary files a/artifacts/program_methods/vault.bin and b/artifacts/lez/programs/vault.bin differ diff --git a/artifacts/lez/programs/wrapped_token.bin b/artifacts/lez/programs/wrapped_token.bin new file mode 100644 index 00000000..2744771f Binary files /dev/null and b/artifacts/lez/programs/wrapped_token.bin differ diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin deleted file mode 100644 index e8eab0e7..00000000 Binary files a/artifacts/program_methods/amm.bin and /dev/null differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin deleted file mode 100644 index ed23efb4..00000000 Binary files a/artifacts/program_methods/associated_token_account.bin and /dev/null differ diff --git a/artifacts/program_methods/bridge.bin b/artifacts/program_methods/bridge.bin deleted file mode 100644 index c55b6f94..00000000 Binary files a/artifacts/program_methods/bridge.bin and /dev/null differ diff --git a/artifacts/program_methods/cross_zone_inbox.bin b/artifacts/program_methods/cross_zone_inbox.bin deleted file mode 100644 index 3595d60d..00000000 Binary files a/artifacts/program_methods/cross_zone_inbox.bin and /dev/null differ diff --git a/artifacts/program_methods/cross_zone_outbox.bin b/artifacts/program_methods/cross_zone_outbox.bin deleted file mode 100644 index e87cd663..00000000 Binary files a/artifacts/program_methods/cross_zone_outbox.bin and /dev/null differ diff --git a/artifacts/program_methods/genesis_supply_account.bin b/artifacts/program_methods/genesis_supply_account.bin deleted file mode 100644 index c377a1e6..00000000 Binary files a/artifacts/program_methods/genesis_supply_account.bin and /dev/null differ diff --git a/artifacts/program_methods/genesis_supply_private_account.bin b/artifacts/program_methods/genesis_supply_private_account.bin deleted file mode 100644 index 9d6aa313..00000000 Binary files a/artifacts/program_methods/genesis_supply_private_account.bin and /dev/null differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin deleted file mode 100644 index a2581702..00000000 Binary files a/artifacts/program_methods/pinata_token.bin and /dev/null differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin deleted file mode 100644 index ebd4c38f..00000000 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and /dev/null differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin deleted file mode 100644 index 430122b1..00000000 Binary files a/artifacts/program_methods/token.bin and /dev/null differ diff --git a/artifacts/program_methods/wrapped_token.bin b/artifacts/program_methods/wrapped_token.bin deleted file mode 100644 index 09f69b1e..00000000 Binary files a/artifacts/program_methods/wrapped_token.bin and /dev/null differ diff --git a/artifacts/test_program_methods/auth_asserting_noop.bin b/artifacts/test_program_methods/auth_asserting_noop.bin deleted file mode 100644 index 2507eacb..00000000 Binary files a/artifacts/test_program_methods/auth_asserting_noop.bin and /dev/null differ diff --git a/artifacts/test_program_methods/auth_transfer_proxy.bin b/artifacts/test_program_methods/auth_transfer_proxy.bin deleted file mode 100644 index 2672cd29..00000000 Binary files a/artifacts/test_program_methods/auth_transfer_proxy.bin and /dev/null differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin deleted file mode 100644 index d0d9893a..00000000 Binary files a/artifacts/test_program_methods/burner.bin and /dev/null differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin deleted file mode 100644 index 4511b516..00000000 Binary files a/artifacts/test_program_methods/chain_caller.bin and /dev/null differ diff --git a/artifacts/test_program_methods/chain_caller_pda_drop.bin b/artifacts/test_program_methods/chain_caller_pda_drop.bin deleted file mode 100644 index 91b42aa2..00000000 Binary files a/artifacts/test_program_methods/chain_caller_pda_drop.bin and /dev/null differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin deleted file mode 100644 index c657a86d..00000000 Binary files a/artifacts/test_program_methods/changer_claimer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin deleted file mode 100644 index 47b451ec..00000000 Binary files a/artifacts/test_program_methods/claimer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/clock_chain_caller.bin b/artifacts/test_program_methods/clock_chain_caller.bin deleted file mode 100644 index b6e5b7f6..00000000 Binary files a/artifacts/test_program_methods/clock_chain_caller.bin and /dev/null differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin deleted file mode 100644 index 4cae9b77..00000000 Binary files a/artifacts/test_program_methods/data_changer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin deleted file mode 100644 index d93baede..00000000 Binary files a/artifacts/test_program_methods/extra_output.bin and /dev/null differ diff --git a/artifacts/test_program_methods/faucet_chain_caller.bin b/artifacts/test_program_methods/faucet_chain_caller.bin deleted file mode 100644 index 01e32aa1..00000000 Binary files a/artifacts/test_program_methods/faucet_chain_caller.bin and /dev/null differ diff --git a/artifacts/test_program_methods/flash_swap_callback.bin b/artifacts/test_program_methods/flash_swap_callback.bin deleted file mode 100644 index 028b64df..00000000 Binary files a/artifacts/test_program_methods/flash_swap_callback.bin and /dev/null differ diff --git a/artifacts/test_program_methods/flash_swap_initiator.bin b/artifacts/test_program_methods/flash_swap_initiator.bin deleted file mode 100644 index f18afc5f..00000000 Binary files a/artifacts/test_program_methods/flash_swap_initiator.bin and /dev/null differ diff --git a/artifacts/test_program_methods/group_pda_spender.bin b/artifacts/test_program_methods/group_pda_spender.bin deleted file mode 100644 index 16efb8a4..00000000 Binary files a/artifacts/test_program_methods/group_pda_spender.bin and /dev/null differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin deleted file mode 100644 index 99359e22..00000000 Binary files a/artifacts/test_program_methods/malicious_authorization_changer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/malicious_caller_program_id.bin b/artifacts/test_program_methods/malicious_caller_program_id.bin deleted file mode 100644 index fe8f5511..00000000 Binary files a/artifacts/test_program_methods/malicious_caller_program_id.bin and /dev/null differ diff --git a/artifacts/test_program_methods/malicious_injector.bin b/artifacts/test_program_methods/malicious_injector.bin deleted file mode 100644 index 9cadd3b3..00000000 Binary files a/artifacts/test_program_methods/malicious_injector.bin and /dev/null differ diff --git a/artifacts/test_program_methods/malicious_launderer.bin b/artifacts/test_program_methods/malicious_launderer.bin deleted file mode 100644 index 9686a8f4..00000000 Binary files a/artifacts/test_program_methods/malicious_launderer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/malicious_self_program_id.bin b/artifacts/test_program_methods/malicious_self_program_id.bin deleted file mode 100644 index 853740ba..00000000 Binary files a/artifacts/test_program_methods/malicious_self_program_id.bin and /dev/null differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin deleted file mode 100644 index a012dd94..00000000 Binary files a/artifacts/test_program_methods/minter.bin and /dev/null differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin deleted file mode 100644 index 4a056f6d..00000000 Binary files a/artifacts/test_program_methods/missing_output.bin and /dev/null differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin deleted file mode 100644 index 65f77b77..00000000 Binary files a/artifacts/test_program_methods/modified_transfer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin deleted file mode 100644 index e1cb3175..00000000 Binary files a/artifacts/test_program_methods/nonce_changer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin deleted file mode 100644 index 7575a2c3..00000000 Binary files a/artifacts/test_program_methods/noop.bin and /dev/null differ diff --git a/artifacts/test_program_methods/pda_claimer.bin b/artifacts/test_program_methods/pda_claimer.bin deleted file mode 100644 index 55a934b1..00000000 Binary files a/artifacts/test_program_methods/pda_claimer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/pda_fund_spend_proxy.bin b/artifacts/test_program_methods/pda_fund_spend_proxy.bin deleted file mode 100644 index 9a04a29c..00000000 Binary files a/artifacts/test_program_methods/pda_fund_spend_proxy.bin and /dev/null differ diff --git a/artifacts/test_program_methods/pda_spend_proxy.bin b/artifacts/test_program_methods/pda_spend_proxy.bin deleted file mode 100644 index 8f7e8977..00000000 Binary files a/artifacts/test_program_methods/pda_spend_proxy.bin and /dev/null differ diff --git a/artifacts/test_program_methods/pinata_cooldown.bin b/artifacts/test_program_methods/pinata_cooldown.bin deleted file mode 100644 index 5655e958..00000000 Binary files a/artifacts/test_program_methods/pinata_cooldown.bin and /dev/null differ diff --git a/artifacts/test_program_methods/private_pda_claimer.bin b/artifacts/test_program_methods/private_pda_claimer.bin deleted file mode 100644 index 5a64c66d..00000000 Binary files a/artifacts/test_program_methods/private_pda_claimer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/private_pda_delegator.bin b/artifacts/test_program_methods/private_pda_delegator.bin deleted file mode 100644 index 163135b6..00000000 Binary files a/artifacts/test_program_methods/private_pda_delegator.bin and /dev/null differ diff --git a/artifacts/test_program_methods/private_pda_spender.bin b/artifacts/test_program_methods/private_pda_spender.bin deleted file mode 100644 index b9848210..00000000 Binary files a/artifacts/test_program_methods/private_pda_spender.bin and /dev/null differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin deleted file mode 100644 index 185e521e..00000000 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin deleted file mode 100644 index 57510322..00000000 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/time_locked_transfer.bin b/artifacts/test_program_methods/time_locked_transfer.bin deleted file mode 100644 index f762398b..00000000 Binary files a/artifacts/test_program_methods/time_locked_transfer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/two_pda_claimer.bin b/artifacts/test_program_methods/two_pda_claimer.bin deleted file mode 100644 index 33995cb7..00000000 Binary files a/artifacts/test_program_methods/two_pda_claimer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/validity_window.bin b/artifacts/test_program_methods/validity_window.bin deleted file mode 100644 index 88cbf9c3..00000000 Binary files a/artifacts/test_program_methods/validity_window.bin and /dev/null differ diff --git a/artifacts/test_program_methods/validity_window_chain_caller.bin b/artifacts/test_program_methods/validity_window_chain_caller.bin deleted file mode 100644 index a8cb5663..00000000 Binary files a/artifacts/test_program_methods/validity_window_chain_caller.bin and /dev/null differ diff --git a/bedrock/docker-compose.yml b/bedrock/docker-compose.yml index a7a0d7f6..e476a8ef 100644 --- a/bedrock/docker-compose.yml +++ b/bedrock/docker-compose.yml @@ -3,7 +3,7 @@ services: logos-blockchain-node-0: image: ghcr.io/logos-blockchain/logos-blockchain@sha256:91d6c5bf07e07fcfba5e7cf07d21ee686a6bc4b9f6210f2d28bffbcad9a3729f ports: - - "${PORT:-8080}:18080/tcp" + - "${PORT:-18080}:18080/tcp" volumes: - ./scripts:/etc/logos-blockchain/scripts - ./kzgrs_test_params:/kzgrs_test_params:z diff --git a/build_utils/Cargo.toml b/build_utils/Cargo.toml new file mode 100644 index 00000000..63ba15bf --- /dev/null +++ b/build_utils/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "build_utils" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] + +[dependencies] +anyhow.workspace = true +risc0-binfmt = "3.0.2" diff --git a/build_utils/src/lib.rs b/build_utils/src/lib.rs new file mode 100644 index 00000000..1323d830 --- /dev/null +++ b/build_utils/src/lib.rs @@ -0,0 +1,69 @@ +//! Utilities for build-scripts. + +use std::{env, fmt::Write as _, fs, path::PathBuf}; + +use anyhow::{Context as _, Result, bail}; + +/// Include artifact binaries as byte arrays and their corresponding image IDs as u32 arrays in a +/// generated Rust module. +/// +/// The `artifacts_sub_dir` parameter specifies the subdirectory under `artifacts/`. +/// +/// Caller should include resulting module as follows: +/// +/// ``` +/// mod guests { +/// include!(concat!(env!("OUT_DIR"), "/artifacts_sub_dir/mod.rs")); +/// } +/// ``` +pub fn include_artifacts(artifacts_sub_dir: &str) -> Result<()> { + let manifest_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")); + let out_dir = PathBuf::from(env::var("OUT_DIR")?); + let mod_dir = out_dir.join(artifacts_sub_dir); + let mod_file = mod_dir.join("mod.rs"); + let artifacts_dir = manifest_dir.join(format!("../artifacts/{artifacts_sub_dir}/")); + + println!("cargo:rerun-if-changed={}", artifacts_dir.display()); + + let bins = fs::read_dir(&artifacts_dir) + .with_context(|| { + format!( + "Failed to read {} artifacts directory", + artifacts_dir.display() + ) + })? + .filter_map(Result::ok) + .filter(|e| e.path().extension().is_some_and(|ext| ext == "bin")) + .collect::>(); + + if bins.is_empty() { + bail!("No .bin files found in {}", artifacts_dir.display()); + } + + fs::create_dir_all(&mod_dir) + .with_context(|| format!("Failed to create directory {}", mod_dir.display()))?; + let mut src = String::new(); + for entry in bins { + let path = entry.path(); + let name = path.file_stem().unwrap().to_string_lossy(); + let bytecode = + fs::read(&path).with_context(|| format!("Failed to read {}", path.display()))?; + let image_id: [u32; 8] = risc0_binfmt::compute_image_id(&bytecode) + .with_context(|| format!("Failed to compute image ID for {}", path.display()))? + .into(); + write!( + src, + "pub const {}_ELF: &[u8] = include_bytes!(r#\"{}\"#);\n\ + #[expect(clippy::unreadable_literal, reason = \"Generated image IDs from risc0 are cryptographic hashes represented as u32 arrays\")]\n\ + pub const {}_ID: [u32; 8] = {:?};\n", + name.to_uppercase(), + path.display(), + name.to_uppercase(), + image_id + )?; + } + fs::write(&mod_file, src).with_context(|| format!("Failed to write {}", mod_file.display()))?; + println!("cargo:warning=Generated module at {}", mod_file.display()); + + Ok(()) +} diff --git a/docker-compose.override.yml b/docker-compose.override.yml index a7fddca6..4cdb486f 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -2,8 +2,6 @@ services: logos-blockchain-node-0: - ports: !override - - "18080:18080/tcp" environment: - RUST_LOG=error diff --git a/examples/program_deployment/README.md b/examples/program_deployment/README.md index 6d1bde25..240079a5 100644 --- a/examples/program_deployment/README.md +++ b/examples/program_deployment/README.md @@ -46,7 +46,7 @@ export EXAMPLE_PROGRAMS_BUILD_DIR=$(pwd)/target/riscv32im-risc0-zkvm-elf/docker > [!IMPORTANT] > **All remaining commands must be run from the `examples/program_deployment` directory.** -# 3. Hello world example +# 3. Hello world example The Hello world program reads an arbitrary sequence of bytes from its instruction and appends them to the data field of the input account. Execution succeeds only if the account is: @@ -211,7 +211,7 @@ This is the account that the program will claim and write data into. ### 3. Loading the program bytecode ```rust let bytecode: Vec = std::fs::read(program_path).unwrap(); -let program = Program::new(bytecode).unwrap(); +let program = Program::new(bytecode.into()).unwrap(); ``` The Risc0 ELF is read from disk and wrapped in a Program object, which can be used to compute the program ID. The ID is used by the node to identify which program is invoked by the transaction. @@ -266,7 +266,7 @@ The relevant part for this tutorial is the account id `7EDHyxejuynBpmbLuiEym9HMU > [!NOTE] > As with public accounts, you can use the `--label` option to assign a label: `wallet account new private --label "my-private-account"`. -You can check it's uninitialized with +You can check it's uninitialized with ```bash wallet account get --account-id Private/7EDHyxejuynBpmbLuiEym9HMUyCYxZDuF8X3B89ADeMr @@ -452,7 +452,7 @@ Because these operations may involve multiple accounts, we'll see how public and > See `methods/guest/src/bin/hello_world_with_move_function.rs`. The program just reads the instruction bytes and updates the accounts state. > All privacy handling happens on the runner side. When constructing the transaction, the runner decides which accounts are public or private and prepares the appropriate proofs. The program itself can't differentiate between privacy modes. -Let's start by deploying the program +Let's start by deploying the program ```bash wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/hello_world_with_move_function.bin ``` @@ -486,7 +486,7 @@ Output: Generated new account with account_id Private/8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU at path /1 ``` -Let's execute the write function +Let's execute the write function ```bash cargo run --bin run_hello_world_with_move_function \ diff --git a/examples/program_deployment/src/bin/run_hello_world.rs b/examples/program_deployment/src/bin/run_hello_world.rs index ef5545f7..3f2223a1 100644 --- a/examples/program_deployment/src/bin/run_hello_world.rs +++ b/examples/program_deployment/src/bin/run_hello_world.rs @@ -43,7 +43,7 @@ async fn main() { // Load the program let bytecode: Vec = std::fs::read(program_path).unwrap(); - let program = Program::new(bytecode).unwrap(); + let program = Program::new(bytecode.into()).unwrap(); // Define the desired greeting in ASCII let greeting: Vec = vec![72, 111, 108, 97, 32, 109, 117, 110, 100, 111, 33]; diff --git a/examples/program_deployment/src/bin/run_hello_world_private.rs b/examples/program_deployment/src/bin/run_hello_world_private.rs index 088eb19a..f6202433 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -39,7 +39,7 @@ async fn main() { // Load the program let bytecode: Vec = std::fs::read(program_path).unwrap(); - let program = Program::new(bytecode).unwrap(); + let program = Program::new(bytecode.into()).unwrap(); // Define the desired greeting in ASCII let greeting: Vec = vec![72, 111, 108, 97, 32, 109, 117, 110, 100, 111, 33]; diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs index a5f449e1..6ebba70f 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call.rs @@ -43,7 +43,7 @@ async fn main() { // Load the program let bytecode: Vec = std::fs::read(program_path).unwrap(); - let program = Program::new(bytecode).unwrap(); + let program = Program::new(bytecode.into()).unwrap(); let instruction_data = (); let nonces = vec![]; diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs index 4983afdb..d35e6521 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -44,9 +44,9 @@ async fn main() { // Load the program and its dependencies (the hellow world program) let simple_tail_call_bytecode: Vec = std::fs::read(simple_tail_call_path).unwrap(); - let simple_tail_call = Program::new(simple_tail_call_bytecode).unwrap(); + let simple_tail_call = Program::new(simple_tail_call_bytecode.into()).unwrap(); let hello_world_bytecode: Vec = std::fs::read(hello_world_path).unwrap(); - let hello_world = Program::new(hello_world_bytecode).unwrap(); + let hello_world = Program::new(hello_world_bytecode.into()).unwrap(); let dependencies: HashMap = std::iter::once((hello_world.id(), hello_world)).collect(); let program_with_dependencies = ProgramWithDependencies::new(simple_tail_call, dependencies); diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs index 15fc028a..0d257db2 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization.rs @@ -45,7 +45,7 @@ async fn main() { // Load the program let bytecode: Vec = std::fs::read(program_path).unwrap(); - let program = Program::new(bytecode).unwrap(); + let program = Program::new(bytecode.into()).unwrap(); // Load signing keys to provide authorization let signing_key = wallet_core diff --git a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs index 2b0e05ed..70688cfd 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs @@ -43,7 +43,7 @@ async fn main() { // Load the program let bytecode: Vec = std::fs::read(program_path).unwrap(); - let program = Program::new(bytecode).unwrap(); + let program = Program::new(bytecode.into()).unwrap(); // Compute the PDA to pass it as input account to the public execution let pda = AccountId::for_public_pda(&program.id(), &PDA_SEED); diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index 3b175bda..a77fe2e6 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -63,7 +63,7 @@ async fn main() { // Load the program let bytecode: Vec = std::fs::read(cli.program_path).unwrap(); - let program = Program::new(bytecode).unwrap(); + let program = Program::new(bytecode.into()).unwrap(); // Initialize wallet let wallet_core = WalletCore::from_env().unwrap(); diff --git a/flake.lock b/flake.lock index 44b0300f..912fd769 100644 --- a/flake.lock +++ b/flake.lock @@ -15,6 +15,61 @@ "type": "github" } }, + "default-container": { + "inputs": { + "logos-container": "logos-container", + "logos-nix": "logos-nix_2", + "nixpkgs": [ + "logos-liblogos", + "default-container", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781741375, + "narHash": "sha256-PP/2tFfwrbqTzbZKVl46I96xz/ZZj7hi9C3/QeLHtQE=", + "owner": "logos-co", + "repo": "logos-container-subprocess", + "rev": "f4860708a982b361a2e25c5260e13800bcee126e", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-container-subprocess", + "type": "github" + } + }, + "default-module-loader": { + "inputs": { + "logos-container": "logos-container_2", + "logos-cpp-sdk": "logos-cpp-sdk", + "logos-module": "logos-module", + "logos-module-loader": "logos-module-loader", + "logos-nix": "logos-nix_8", + "logos-protocol": "logos-protocol", + "logos-qt-sdk": "logos-qt-sdk", + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781734000, + "narHash": "sha256-TyrUHKnb5llx+Z5GN+bMAbjkxON6w7aj4yN6gokq2eQ=", + "owner": "logos-co", + "repo": "logos-module-loader-qt", + "rev": "08213eb216ae9b33c73830d3cf2d9111d97cbbcb", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module-loader-qt", + "type": "github" + } + }, "logos-blockchain-circuits": { "inputs": { "nixpkgs": "nixpkgs" @@ -82,8 +137,8 @@ "logos-standalone-app", "logos-cpp-sdk" ], - "logos-module": "logos-module_7", - "logos-nix": "logos-nix_16", + "logos-module": "logos-module_8", + "logos-nix": "logos-nix_26", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -112,9 +167,9 @@ }, "logos-capability-module_4": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_4", - "logos-module": "logos-module_8", - "logos-nix": "logos-nix_21", + "logos-cpp-sdk": "logos-cpp-sdk_5", + "logos-module": "logos-module_9", + "logos-nix": "logos-nix_31", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -174,8 +229,8 @@ "logos-standalone-app", "logos-cpp-sdk" ], - "logos-module": "logos-module_13", - "logos-nix": "logos-nix_60", + "logos-module": "logos-module_14", + "logos-nix": "logos-nix_70", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -205,9 +260,9 @@ }, "logos-capability-module_7": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_9", - "logos-module": "logos-module_14", - "logos-nix": "logos-nix_65", + "logos-cpp-sdk": "logos-cpp-sdk_10", + "logos-module": "logos-module_15", + "logos-nix": "logos-nix_75", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -237,24 +292,154 @@ "type": "github" } }, - "logos-cpp-sdk": { + "logos-container": { "inputs": { "logos-nix": "logos-nix", "nixpkgs": [ "logos-liblogos", - "logos-capability-module", - "logos-module-builder", + "default-container", + "logos-container", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781732360, + "narHash": "sha256-RJjquPiZz3/LkJaFUGpDxFbt1+paBiVtbbxjVhDMR3o=", + "owner": "logos-co", + "repo": "logos-container", + "rev": "15adc74f829f0cc662bce4f0bfae917cd50e7db4", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-container", + "type": "github" + } + }, + "logos-container_2": { + "inputs": { + "logos-nix": "logos-nix_3", + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", + "logos-container", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781732360, + "narHash": "sha256-RJjquPiZz3/LkJaFUGpDxFbt1+paBiVtbbxjVhDMR3o=", + "owner": "logos-co", + "repo": "logos-container", + "rev": "15adc74f829f0cc662bce4f0bfae917cd50e7db4", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-container", + "type": "github" + } + }, + "logos-container_3": { + "inputs": { + "logos-nix": "logos-nix_6", + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", + "logos-module-loader", + "logos-container", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781732360, + "narHash": "sha256-RJjquPiZz3/LkJaFUGpDxFbt1+paBiVtbbxjVhDMR3o=", + "owner": "logos-co", + "repo": "logos-container", + "rev": "15adc74f829f0cc662bce4f0bfae917cd50e7db4", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-container", + "type": "github" + } + }, + "logos-container_4": { + "inputs": { + "logos-nix": "logos-nix_132", + "nixpkgs": [ + "logos-liblogos", + "logos-container", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781732360, + "narHash": "sha256-RJjquPiZz3/LkJaFUGpDxFbt1+paBiVtbbxjVhDMR3o=", + "owner": "logos-co", + "repo": "logos-container", + "rev": "15adc74f829f0cc662bce4f0bfae917cd50e7db4", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-container", + "type": "github" + } + }, + "logos-container_5": { + "inputs": { + "logos-nix": "logos-nix_135", + "nixpkgs": [ + "logos-liblogos", + "logos-module-loader", + "logos-container", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781732360, + "narHash": "sha256-RJjquPiZz3/LkJaFUGpDxFbt1+paBiVtbbxjVhDMR3o=", + "owner": "logos-co", + "repo": "logos-container", + "rev": "15adc74f829f0cc662bce4f0bfae917cd50e7db4", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-container", + "type": "github" + } + }, + "logos-cpp-sdk": { + "inputs": { + "logos-lidl": "logos-lidl", + "logos-nix": "logos-nix_4", + "logos-protocol": [ + "logos-liblogos", + "default-module-loader", + "logos-protocol" + ], + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", "logos-cpp-sdk", "logos-nix", "nixpkgs" ] }, "locked": { - "lastModified": 1781207077, - "narHash": "sha256-ZamVbPJxW364RFOa0WnYTJV7AiEjnUcOgfxIbfNgTO0=", + "lastModified": 1781663756, + "narHash": "sha256-q1Q2MCax3L1RLfcS40sDStVipK6dhfiPHYk31YeGyUI=", "owner": "logos-co", "repo": "logos-cpp-sdk", - "rev": "f5a127dd11589c8fc6db0a1bd4332d09ac080bb6", + "rev": "182d850e722c820b3640a09541d7c469ef79f0e6", "type": "github" }, "original": { @@ -265,7 +450,40 @@ }, "logos-cpp-sdk_10": { "inputs": { - "logos-nix": "logos-nix_66", + "logos-nix": "logos-nix_73", + "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", + "logos-liblogos", + "logos-capability-module", + "logos-cpp-sdk", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1773956385, + "narHash": "sha256-CV0Lo1FrosBt/MSP+GWQGWXnYobxRGXGOREylNuwZ58=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "4b66dac015e4b977d33cfae80a4c8e1d518679f3", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_11": { + "inputs": { + "logos-nix": "logos-nix_76", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -295,9 +513,9 @@ "type": "github" } }, - "logos-cpp-sdk_11": { + "logos-cpp-sdk_12": { "inputs": { - "logos-nix": "logos-nix_94", + "logos-nix": "logos-nix_104", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -323,7 +541,7 @@ "type": "github" } }, - "logos-cpp-sdk_12": { + "logos-cpp-sdk_13": { "inputs": { "logos-nix": [ "logos-liblogos", @@ -358,9 +576,14 @@ "type": "github" } }, - "logos-cpp-sdk_13": { + "logos-cpp-sdk_14": { "inputs": { - "logos-nix": "logos-nix_122", + "logos-lidl": "logos-lidl_3", + "logos-nix": "logos-nix_133", + "logos-protocol": [ + "logos-liblogos", + "logos-protocol" + ], "nixpkgs": [ "logos-liblogos", "logos-cpp-sdk", @@ -368,6 +591,32 @@ "nixpkgs" ] }, + "locked": { + "lastModified": 1781663756, + "narHash": "sha256-q1Q2MCax3L1RLfcS40sDStVipK6dhfiPHYk31YeGyUI=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "182d850e722c820b3640a09541d7c469ef79f0e6", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_2": { + "inputs": { + "logos-nix": "logos-nix_11", + "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-cpp-sdk", + "logos-nix", + "nixpkgs" + ] + }, "locked": { "lastModified": 1781207077, "narHash": "sha256-ZamVbPJxW364RFOa0WnYTJV7AiEjnUcOgfxIbfNgTO0=", @@ -382,38 +631,9 @@ "type": "github" } }, - "logos-cpp-sdk_2": { - "inputs": { - "logos-nix": "logos-nix_8", - "nixpkgs": [ - "logos-liblogos", - "logos-capability-module", - "logos-module-builder", - "logos-standalone-app", - "logos-capability-module", - "logos-module-builder", - "logos-cpp-sdk", - "logos-nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1778167634, - "narHash": "sha256-gbBYvyEDxBHF8iACAE/dFcljBkIUTWr1rP3gBLj62JI=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "25c88f4d48fa95ea4437194bcf60bd8d0cf84a74", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, "logos-cpp-sdk_3": { "inputs": { - "logos-nix": "logos-nix_17", + "logos-nix": "logos-nix_18", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -421,7 +641,6 @@ "logos-standalone-app", "logos-capability-module", "logos-module-builder", - "logos-standalone-app", "logos-cpp-sdk", "logos-nix", "nixpkgs" @@ -443,7 +662,37 @@ }, "logos-cpp-sdk_4": { "inputs": { - "logos-nix": "logos-nix_19", + "logos-nix": "logos-nix_27", + "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", + "logos-cpp-sdk", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778167634, + "narHash": "sha256-gbBYvyEDxBHF8iACAE/dFcljBkIUTWr1rP3gBLj62JI=", + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "rev": "25c88f4d48fa95ea4437194bcf60bd8d0cf84a74", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-cpp-sdk", + "type": "github" + } + }, + "logos-cpp-sdk_5": { + "inputs": { + "logos-nix": "logos-nix_29", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -473,9 +722,9 @@ "type": "github" } }, - "logos-cpp-sdk_5": { + "logos-cpp-sdk_6": { "inputs": { - "logos-nix": "logos-nix_22", + "logos-nix": "logos-nix_32", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -504,9 +753,9 @@ "type": "github" } }, - "logos-cpp-sdk_6": { + "logos-cpp-sdk_7": { "inputs": { - "logos-nix": "logos-nix_50", + "logos-nix": "logos-nix_60", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -531,39 +780,9 @@ "type": "github" } }, - "logos-cpp-sdk_7": { - "inputs": { - "logos-nix": "logos-nix_52", - "nixpkgs": [ - "logos-liblogos", - "logos-capability-module", - "logos-module-builder", - "logos-standalone-app", - "logos-liblogos", - "logos-capability-module", - "logos-module-builder", - "logos-cpp-sdk", - "logos-nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1778167634, - "narHash": "sha256-gbBYvyEDxBHF8iACAE/dFcljBkIUTWr1rP3gBLj62JI=", - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "rev": "25c88f4d48fa95ea4437194bcf60bd8d0cf84a74", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-cpp-sdk", - "type": "github" - } - }, "logos-cpp-sdk_8": { "inputs": { - "logos-nix": "logos-nix_61", + "logos-nix": "logos-nix_62", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -572,7 +791,6 @@ "logos-liblogos", "logos-capability-module", "logos-module-builder", - "logos-standalone-app", "logos-cpp-sdk", "logos-nix", "nixpkgs" @@ -594,7 +812,7 @@ }, "logos-cpp-sdk_9": { "inputs": { - "logos-nix": "logos-nix_63", + "logos-nix": "logos-nix_71", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -604,19 +822,17 @@ "logos-capability-module", "logos-module-builder", "logos-standalone-app", - "logos-liblogos", - "logos-capability-module", "logos-cpp-sdk", "logos-nix", "nixpkgs" ] }, "locked": { - "lastModified": 1773956385, - "narHash": "sha256-CV0Lo1FrosBt/MSP+GWQGWXnYobxRGXGOREylNuwZ58=", + "lastModified": 1778167634, + "narHash": "sha256-gbBYvyEDxBHF8iACAE/dFcljBkIUTWr1rP3gBLj62JI=", "owner": "logos-co", "repo": "logos-cpp-sdk", - "rev": "4b66dac015e4b977d33cfae80a4c8e1d518679f3", + "rev": "25c88f4d48fa95ea4437194bcf60bd8d0cf84a74", "type": "github" }, "original": { @@ -627,7 +843,7 @@ }, "logos-design-system": { "inputs": { - "logos-nix": "logos-nix_18", + "logos-nix": "logos-nix_28", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -657,7 +873,7 @@ }, "logos-design-system_2": { "inputs": { - "logos-nix": "logos-nix_51", + "logos-nix": "logos-nix_61", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -684,7 +900,7 @@ }, "logos-design-system_3": { "inputs": { - "logos-nix": "logos-nix_62", + "logos-nix": "logos-nix_72", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -715,11 +931,17 @@ }, "logos-liblogos": { "inputs": { + "default-container": "default-container", + "default-module-loader": "default-module-loader", "logos-capability-module": "logos-capability-module", - "logos-cpp-sdk": "logos-cpp-sdk_13", - "logos-module": "logos-module_17", - "logos-nix": "logos-nix_124", + "logos-container": "logos-container_4", + "logos-cpp-sdk": "logos-cpp-sdk_14", + "logos-module": "logos-module_18", + "logos-module-loader": "logos-module-loader_2", + "logos-nix": "logos-nix_137", "logos-package-manager": "logos-package-manager_7", + "logos-protocol": "logos-protocol_2", + "logos-qt-sdk": "logos-qt-sdk_2", "nixpkgs": [ "logos-liblogos", "logos-nix", @@ -728,11 +950,11 @@ "process-stats": "process-stats_4" }, "locked": { - "lastModified": 1781211239, - "narHash": "sha256-qRWrWyxpry5cO03rG1cXYPtujQON6A/0Z0Z/R1NmUV8=", + "lastModified": 1781742244, + "narHash": "sha256-Vd9UevKBn0Mie5tvexaaYOSkiqDlC4G3qTFKD28S1gk=", "owner": "logos-co", "repo": "logos-liblogos", - "rev": "66256b54efb995adf977689ea63f6bfb6ccfdd81", + "rev": "dbcafea05c06e91be18763bc0c4b307222d8b853", "type": "github" }, "original": { @@ -744,9 +966,9 @@ "logos-liblogos_2": { "inputs": { "logos-capability-module": "logos-capability-module_4", - "logos-cpp-sdk": "logos-cpp-sdk_5", - "logos-module": "logos-module_9", - "logos-nix": "logos-nix_24", + "logos-cpp-sdk": "logos-cpp-sdk_6", + "logos-module": "logos-module_10", + "logos-nix": "logos-nix_34", "logos-package-manager": "logos-package-manager", "nixpkgs": [ "logos-liblogos", @@ -778,9 +1000,9 @@ "logos-liblogos_3": { "inputs": { "logos-capability-module": "logos-capability-module_5", - "logos-cpp-sdk": "logos-cpp-sdk_11", - "logos-module": "logos-module_16", - "logos-nix": "logos-nix_96", + "logos-cpp-sdk": "logos-cpp-sdk_12", + "logos-module": "logos-module_17", + "logos-nix": "logos-nix_106", "logos-package-manager": "logos-package-manager_5", "nixpkgs": [ "logos-liblogos", @@ -810,9 +1032,9 @@ "logos-liblogos_4": { "inputs": { "logos-capability-module": "logos-capability-module_7", - "logos-cpp-sdk": "logos-cpp-sdk_10", - "logos-module": "logos-module_15", - "logos-nix": "logos-nix_68", + "logos-cpp-sdk": "logos-cpp-sdk_11", + "logos-module": "logos-module_16", + "logos-nix": "logos-nix_78", "logos-package-manager": "logos-package-manager_3", "nixpkgs": [ "logos-liblogos", @@ -842,24 +1064,143 @@ "type": "github" } }, - "logos-module": { + "logos-lidl": { "inputs": { - "logos-nix": "logos-nix_2", + "logos-nix": [ + "logos-liblogos", + "default-module-loader", + "logos-cpp-sdk", + "logos-nix" + ], "nixpkgs": [ "logos-liblogos", - "logos-capability-module", - "logos-module-builder", + "default-module-loader", + "logos-cpp-sdk", + "logos-lidl", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781653473, + "narHash": "sha256-8l2tE2K5nY1grcFROQHC1By1jVI0NrfkS3k2v2y6R2I=", + "owner": "logos-co", + "repo": "logos-lidl", + "rev": "8c95d4f0cc6a10195c70ed71e85b7a4cddca02f8", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-lidl", + "type": "github" + } + }, + "logos-lidl_2": { + "inputs": { + "logos-nix": [ + "logos-liblogos", + "default-module-loader", + "logos-qt-sdk", + "logos-nix" + ], + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", + "logos-qt-sdk", + "logos-lidl", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781653473, + "narHash": "sha256-8l2tE2K5nY1grcFROQHC1By1jVI0NrfkS3k2v2y6R2I=", + "owner": "logos-co", + "repo": "logos-lidl", + "rev": "8c95d4f0cc6a10195c70ed71e85b7a4cddca02f8", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-lidl", + "type": "github" + } + }, + "logos-lidl_3": { + "inputs": { + "logos-nix": [ + "logos-liblogos", + "logos-cpp-sdk", + "logos-nix" + ], + "nixpkgs": [ + "logos-liblogos", + "logos-cpp-sdk", + "logos-lidl", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781653473, + "narHash": "sha256-8l2tE2K5nY1grcFROQHC1By1jVI0NrfkS3k2v2y6R2I=", + "owner": "logos-co", + "repo": "logos-lidl", + "rev": "8c95d4f0cc6a10195c70ed71e85b7a4cddca02f8", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-lidl", + "type": "github" + } + }, + "logos-lidl_4": { + "inputs": { + "logos-nix": [ + "logos-liblogos", + "logos-qt-sdk", + "logos-nix" + ], + "nixpkgs": [ + "logos-liblogos", + "logos-qt-sdk", + "logos-lidl", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781653473, + "narHash": "sha256-8l2tE2K5nY1grcFROQHC1By1jVI0NrfkS3k2v2y6R2I=", + "owner": "logos-co", + "repo": "logos-lidl", + "rev": "8c95d4f0cc6a10195c70ed71e85b7a4cddca02f8", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-lidl", + "type": "github" + } + }, + "logos-module": { + "inputs": { + "logos-nix": "logos-nix_5", + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", "logos-module", "logos-nix", "nixpkgs" ] }, "locked": { - "lastModified": 1780603603, - "narHash": "sha256-CV7R+lwNIP0baXZtE9uNjqu7qDLiANwyGfMkHfTjcl0=", + "lastModified": 1781311603, + "narHash": "sha256-Ym3i52f5MWuZy8Qas3+zfoxz+mxQUsyW+Qol1no6kDs=", "owner": "logos-co", "repo": "logos-module", - "rev": "780894dd40ebc8eded0fa97b1729286f31571cfb", + "rev": "a3e288a71d6f79445db598b0ffcca2ee435596b9", "type": "github" }, "original": { @@ -870,9 +1211,9 @@ }, "logos-module-builder": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk", - "logos-module": "logos-module", - "logos-nix": "logos-nix_3", + "logos-cpp-sdk": "logos-cpp-sdk_2", + "logos-module": "logos-module_2", + "logos-nix": "logos-nix_13", "logos-plugin-core": "logos-plugin-core", "logos-plugin-qt": "logos-plugin-qt", "logos-standalone-app": "logos-standalone-app", @@ -903,9 +1244,9 @@ }, "logos-module-builder_2": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_2", - "logos-module": "logos-module_4", - "logos-nix": "logos-nix_10", + "logos-cpp-sdk": "logos-cpp-sdk_3", + "logos-module": "logos-module_5", + "logos-nix": "logos-nix_20", "logos-plugin-core": "logos-plugin-core_2", "logos-plugin-qt": "logos-plugin-qt_2", "logos-standalone-app": "logos-standalone-app_2", @@ -939,9 +1280,9 @@ }, "logos-module-builder_3": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_7", - "logos-module": "logos-module_10", - "logos-nix": "logos-nix_54", + "logos-cpp-sdk": "logos-cpp-sdk_8", + "logos-module": "logos-module_11", + "logos-nix": "logos-nix_64", "logos-plugin-core": "logos-plugin-core_3", "logos-plugin-qt": "logos-plugin-qt_3", "logos-standalone-app": "logos-standalone-app_3", @@ -974,9 +1315,91 @@ "type": "github" } }, + "logos-module-loader": { + "inputs": { + "logos-container": "logos-container_3", + "logos-nix": "logos-nix_7", + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", + "logos-module-loader", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781732584, + "narHash": "sha256-v1eIGPXTnW0oCwEvEjEoBLeoidUh3ZomDGQY9m/ucNw=", + "owner": "logos-co", + "repo": "logos-module-loader", + "rev": "7ed7a5a97e1ad9142187d755c4ce56ed17d69f18", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module-loader", + "type": "github" + } + }, + "logos-module-loader_2": { + "inputs": { + "logos-container": "logos-container_5", + "logos-nix": "logos-nix_136", + "nixpkgs": [ + "logos-liblogos", + "logos-module-loader", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781732584, + "narHash": "sha256-v1eIGPXTnW0oCwEvEjEoBLeoidUh3ZomDGQY9m/ucNw=", + "owner": "logos-co", + "repo": "logos-module-loader", + "rev": "7ed7a5a97e1ad9142187d755c4ce56ed17d69f18", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module-loader", + "type": "github" + } + }, "logos-module_10": { "inputs": { - "logos-nix": "logos-nix_53", + "logos-nix": "logos-nix_33", + "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", + "logos-liblogos", + "logos-module", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1776369033, + "narHash": "sha256-ehePoUEd/u3Ng0TvCmjocXYJWWH6P61PA7tNpgV59lo=", + "owner": "logos-co", + "repo": "logos-module", + "rev": "194778491ef4dd36967ac40cc2fec6b8a8b1d660", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module", + "type": "github" + } + }, + "logos-module_11": { + "inputs": { + "logos-nix": "logos-nix_63", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1004,9 +1427,9 @@ "type": "github" } }, - "logos-module_11": { + "logos-module_12": { "inputs": { - "logos-nix": "logos-nix_55", + "logos-nix": "logos-nix_65", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1035,9 +1458,9 @@ "type": "github" } }, - "logos-module_12": { + "logos-module_13": { "inputs": { - "logos-nix": "logos-nix_57", + "logos-nix": "logos-nix_67", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1066,41 +1489,9 @@ "type": "github" } }, - "logos-module_13": { - "inputs": { - "logos-nix": "logos-nix_59", - "nixpkgs": [ - "logos-liblogos", - "logos-capability-module", - "logos-module-builder", - "logos-standalone-app", - "logos-liblogos", - "logos-capability-module", - "logos-module-builder", - "logos-standalone-app", - "logos-capability-module", - "logos-module", - "logos-nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1773963329, - "narHash": "sha256-zdvDHoYWQDse0eJ/UCKIJcfuYJ8NMgl6QfxRcyDEovI=", - "owner": "logos-co", - "repo": "logos-module", - "rev": "ac5a4f06ea94b01dd9c5fbb9ed4f20620beab88d", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-module", - "type": "github" - } - }, "logos-module_14": { "inputs": { - "logos-nix": "logos-nix_64", + "logos-nix": "logos-nix_69", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1110,7 +1501,6 @@ "logos-capability-module", "logos-module-builder", "logos-standalone-app", - "logos-liblogos", "logos-capability-module", "logos-module", "logos-nix", @@ -1133,7 +1523,7 @@ }, "logos-module_15": { "inputs": { - "logos-nix": "logos-nix_67", + "logos-nix": "logos-nix_74", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1144,17 +1534,18 @@ "logos-module-builder", "logos-standalone-app", "logos-liblogos", + "logos-capability-module", "logos-module", "logos-nix", "nixpkgs" ] }, "locked": { - "lastModified": 1776369033, - "narHash": "sha256-ehePoUEd/u3Ng0TvCmjocXYJWWH6P61PA7tNpgV59lo=", + "lastModified": 1773963329, + "narHash": "sha256-zdvDHoYWQDse0eJ/UCKIJcfuYJ8NMgl6QfxRcyDEovI=", "owner": "logos-co", "repo": "logos-module", - "rev": "194778491ef4dd36967ac40cc2fec6b8a8b1d660", + "rev": "ac5a4f06ea94b01dd9c5fbb9ed4f20620beab88d", "type": "github" }, "original": { @@ -1165,8 +1556,12 @@ }, "logos-module_16": { "inputs": { - "logos-nix": "logos-nix_95", + "logos-nix": "logos-nix_77", "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", "logos-liblogos", "logos-capability-module", "logos-module-builder", @@ -1193,8 +1588,12 @@ }, "logos-module_17": { "inputs": { - "logos-nix": "logos-nix_123", + "logos-nix": "logos-nix_105", "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-standalone-app", "logos-liblogos", "logos-module", "logos-nix", @@ -1215,25 +1614,48 @@ "type": "github" } }, + "logos-module_18": { + "inputs": { + "logos-nix": "logos-nix_134", + "nixpkgs": [ + "logos-liblogos", + "logos-module", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781311603, + "narHash": "sha256-Ym3i52f5MWuZy8Qas3+zfoxz+mxQUsyW+Qol1no6kDs=", + "owner": "logos-co", + "repo": "logos-module", + "rev": "a3e288a71d6f79445db598b0ffcca2ee435596b9", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module", + "type": "github" + } + }, "logos-module_2": { "inputs": { - "logos-nix": "logos-nix_4", + "logos-nix": "logos-nix_12", "nixpkgs": [ "logos-liblogos", "logos-capability-module", "logos-module-builder", - "logos-plugin-core", "logos-module", "logos-nix", "nixpkgs" ] }, "locked": { - "lastModified": 1776369033, - "narHash": "sha256-ehePoUEd/u3Ng0TvCmjocXYJWWH6P61PA7tNpgV59lo=", + "lastModified": 1780603603, + "narHash": "sha256-CV7R+lwNIP0baXZtE9uNjqu7qDLiANwyGfMkHfTjcl0=", "owner": "logos-co", "repo": "logos-module", - "rev": "194778491ef4dd36967ac40cc2fec6b8a8b1d660", + "rev": "780894dd40ebc8eded0fa97b1729286f31571cfb", "type": "github" }, "original": { @@ -1244,12 +1666,12 @@ }, "logos-module_3": { "inputs": { - "logos-nix": "logos-nix_6", + "logos-nix": "logos-nix_14", "nixpkgs": [ "logos-liblogos", "logos-capability-module", "logos-module-builder", - "logos-plugin-qt", + "logos-plugin-core", "logos-module", "logos-nix", "nixpkgs" @@ -1271,7 +1693,34 @@ }, "logos-module_4": { "inputs": { - "logos-nix": "logos-nix_9", + "logos-nix": "logos-nix_16", + "nixpkgs": [ + "logos-liblogos", + "logos-capability-module", + "logos-module-builder", + "logos-plugin-qt", + "logos-module", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1776369033, + "narHash": "sha256-ehePoUEd/u3Ng0TvCmjocXYJWWH6P61PA7tNpgV59lo=", + "owner": "logos-co", + "repo": "logos-module", + "rev": "194778491ef4dd36967ac40cc2fec6b8a8b1d660", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-module", + "type": "github" + } + }, + "logos-module_5": { + "inputs": { + "logos-nix": "logos-nix_19", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1298,9 +1747,9 @@ "type": "github" } }, - "logos-module_5": { + "logos-module_6": { "inputs": { - "logos-nix": "logos-nix_11", + "logos-nix": "logos-nix_21", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1328,9 +1777,9 @@ "type": "github" } }, - "logos-module_6": { + "logos-module_7": { "inputs": { - "logos-nix": "logos-nix_13", + "logos-nix": "logos-nix_23", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1358,40 +1807,9 @@ "type": "github" } }, - "logos-module_7": { - "inputs": { - "logos-nix": "logos-nix_15", - "nixpkgs": [ - "logos-liblogos", - "logos-capability-module", - "logos-module-builder", - "logos-standalone-app", - "logos-capability-module", - "logos-module-builder", - "logos-standalone-app", - "logos-capability-module", - "logos-module", - "logos-nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1773963329, - "narHash": "sha256-zdvDHoYWQDse0eJ/UCKIJcfuYJ8NMgl6QfxRcyDEovI=", - "owner": "logos-co", - "repo": "logos-module", - "rev": "ac5a4f06ea94b01dd9c5fbb9ed4f20620beab88d", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-module", - "type": "github" - } - }, "logos-module_8": { "inputs": { - "logos-nix": "logos-nix_20", + "logos-nix": "logos-nix_25", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1400,7 +1818,6 @@ "logos-capability-module", "logos-module-builder", "logos-standalone-app", - "logos-liblogos", "logos-capability-module", "logos-module", "logos-nix", @@ -1423,7 +1840,7 @@ }, "logos-module_9": { "inputs": { - "logos-nix": "logos-nix_23", + "logos-nix": "logos-nix_30", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -1433,17 +1850,18 @@ "logos-module-builder", "logos-standalone-app", "logos-liblogos", + "logos-capability-module", "logos-module", "logos-nix", "nixpkgs" ] }, "locked": { - "lastModified": 1776369033, - "narHash": "sha256-ehePoUEd/u3Ng0TvCmjocXYJWWH6P61PA7tNpgV59lo=", + "lastModified": 1773963329, + "narHash": "sha256-zdvDHoYWQDse0eJ/UCKIJcfuYJ8NMgl6QfxRcyDEovI=", "owner": "logos-co", "repo": "logos-module", - "rev": "194778491ef4dd36967ac40cc2fec6b8a8b1d660", + "rev": "ac5a4f06ea94b01dd9c5fbb9ed4f20620beab88d", "type": "github" }, "original": { @@ -1511,11 +1929,11 @@ "nixpkgs": "nixpkgs_102" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -1546,24 +1964,6 @@ "inputs": { "nixpkgs": "nixpkgs_104" }, - "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", - "owner": "logos-co", - "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-nix", - "type": "github" - } - }, - "logos-nix_104": { - "inputs": { - "nixpkgs": "nixpkgs_105" - }, "locked": { "lastModified": 1773955630, "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", @@ -1578,9 +1978,9 @@ "type": "github" } }, - "logos-nix_105": { + "logos-nix_104": { "inputs": { - "nixpkgs": "nixpkgs_106" + "nixpkgs": "nixpkgs_105" }, "locked": { "lastModified": 1774455309, @@ -1596,6 +1996,24 @@ "type": "github" } }, + "logos-nix_105": { + "inputs": { + "nixpkgs": "nixpkgs_106" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-nix_106": { "inputs": { "nixpkgs": "nixpkgs_107" @@ -1619,11 +2037,11 @@ "nixpkgs": "nixpkgs_108" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -1654,24 +2072,6 @@ "inputs": { "nixpkgs": "nixpkgs_110" }, - "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", - "owner": "logos-co", - "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-nix", - "type": "github" - } - }, - "logos-nix_11": { - "inputs": { - "nixpkgs": "nixpkgs_12" - }, "locked": { "lastModified": 1773955630, "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", @@ -1686,9 +2086,9 @@ "type": "github" } }, - "logos-nix_110": { + "logos-nix_11": { "inputs": { - "nixpkgs": "nixpkgs_111" + "nixpkgs": "nixpkgs_12" }, "locked": { "lastModified": 1774455309, @@ -1704,6 +2104,24 @@ "type": "github" } }, + "logos-nix_110": { + "inputs": { + "nixpkgs": "nixpkgs_111" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-nix_111": { "inputs": { "nixpkgs": "nixpkgs_112" @@ -1763,11 +2181,11 @@ "nixpkgs": "nixpkgs_115" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -1781,11 +2199,11 @@ "nixpkgs": "nixpkgs_116" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -1799,11 +2217,11 @@ "nixpkgs": "nixpkgs_117" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -1871,11 +2289,11 @@ "nixpkgs": "nixpkgs_13" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -1889,11 +2307,11 @@ "nixpkgs": "nixpkgs_121" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -1925,11 +2343,11 @@ "nixpkgs": "nixpkgs_123" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -1943,11 +2361,11 @@ "nixpkgs": "nixpkgs_124" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -1979,11 +2397,11 @@ "nixpkgs": "nixpkgs_126" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2051,11 +2469,11 @@ "nixpkgs": "nixpkgs_130" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2069,11 +2487,11 @@ "nixpkgs": "nixpkgs_14" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2100,9 +2518,27 @@ "type": "github" } }, - "logos-nix_14": { + "logos-nix_131": { "inputs": { - "nixpkgs": "nixpkgs_15" + "nixpkgs": "nixpkgs_132" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_132": { + "inputs": { + "nixpkgs": "nixpkgs_133" }, "locked": { "lastModified": 1774455309, @@ -2118,9 +2554,27 @@ "type": "github" } }, - "logos-nix_15": { + "logos-nix_133": { "inputs": { - "nixpkgs": "nixpkgs_16" + "nixpkgs": "nixpkgs_134" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_134": { + "inputs": { + "nixpkgs": "nixpkgs_135" }, "locked": { "lastModified": 1773955630, @@ -2136,6 +2590,240 @@ "type": "github" } }, + "logos-nix_135": { + "inputs": { + "nixpkgs": "nixpkgs_136" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_136": { + "inputs": { + "nixpkgs": "nixpkgs_137" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_137": { + "inputs": { + "nixpkgs": "nixpkgs_138" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_138": { + "inputs": { + "nixpkgs": "nixpkgs_139" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_139": { + "inputs": { + "nixpkgs": "nixpkgs_140" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_14": { + "inputs": { + "nixpkgs": "nixpkgs_15" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_140": { + "inputs": { + "nixpkgs": "nixpkgs_141" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_141": { + "inputs": { + "nixpkgs": "nixpkgs_142" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_142": { + "inputs": { + "nixpkgs": "nixpkgs_143" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_143": { + "inputs": { + "nixpkgs": "nixpkgs_144" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_144": { + "inputs": { + "nixpkgs": "nixpkgs_145" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_145": { + "inputs": { + "nixpkgs": "nixpkgs_146" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, + "logos-nix_15": { + "inputs": { + "nixpkgs": "nixpkgs_16" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-nix_16": { "inputs": { "nixpkgs": "nixpkgs_17" @@ -2177,11 +2865,11 @@ "nixpkgs": "nixpkgs_19" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2213,11 +2901,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2231,11 +2919,11 @@ "nixpkgs": "nixpkgs_21" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2321,11 +3009,11 @@ "nixpkgs": "nixpkgs_26" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2357,11 +3045,11 @@ "nixpkgs": "nixpkgs_28" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2446,24 +3134,6 @@ "inputs": { "nixpkgs": "nixpkgs_32" }, - "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", - "owner": "logos-co", - "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-nix", - "type": "github" - } - }, - "logos-nix_32": { - "inputs": { - "nixpkgs": "nixpkgs_33" - }, "locked": { "lastModified": 1773955630, "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", @@ -2478,9 +3148,9 @@ "type": "github" } }, - "logos-nix_33": { + "logos-nix_32": { "inputs": { - "nixpkgs": "nixpkgs_34" + "nixpkgs": "nixpkgs_33" }, "locked": { "lastModified": 1774455309, @@ -2496,6 +3166,24 @@ "type": "github" } }, + "logos-nix_33": { + "inputs": { + "nixpkgs": "nixpkgs_34" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-nix_34": { "inputs": { "nixpkgs": "nixpkgs_35" @@ -2519,11 +3207,11 @@ "nixpkgs": "nixpkgs_36" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2555,11 +3243,11 @@ "nixpkgs": "nixpkgs_38" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2573,11 +3261,11 @@ "nixpkgs": "nixpkgs_39" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2609,11 +3297,11 @@ "nixpkgs": "nixpkgs_5" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2663,11 +3351,11 @@ "nixpkgs": "nixpkgs_43" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2681,11 +3369,11 @@ "nixpkgs": "nixpkgs_44" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2699,11 +3387,11 @@ "nixpkgs": "nixpkgs_45" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2771,11 +3459,11 @@ "nixpkgs": "nixpkgs_49" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2807,11 +3495,11 @@ "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2825,11 +3513,11 @@ "nixpkgs": "nixpkgs_51" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2843,11 +3531,11 @@ "nixpkgs": "nixpkgs_52" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -2897,11 +3585,11 @@ "nixpkgs": "nixpkgs_55" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -2932,24 +3620,6 @@ "inputs": { "nixpkgs": "nixpkgs_57" }, - "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", - "owner": "logos-co", - "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-nix", - "type": "github" - } - }, - "logos-nix_57": { - "inputs": { - "nixpkgs": "nixpkgs_58" - }, "locked": { "lastModified": 1773955630, "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", @@ -2964,9 +3634,9 @@ "type": "github" } }, - "logos-nix_58": { + "logos-nix_57": { "inputs": { - "nixpkgs": "nixpkgs_59" + "nixpkgs": "nixpkgs_58" }, "locked": { "lastModified": 1774455309, @@ -2982,6 +3652,24 @@ "type": "github" } }, + "logos-nix_58": { + "inputs": { + "nixpkgs": "nixpkgs_59" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-nix_59": { "inputs": { "nixpkgs": "nixpkgs_60" @@ -3005,11 +3693,11 @@ "nixpkgs": "nixpkgs_7" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3022,24 +3710,6 @@ "inputs": { "nixpkgs": "nixpkgs_61" }, - "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", - "owner": "logos-co", - "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-nix", - "type": "github" - } - }, - "logos-nix_61": { - "inputs": { - "nixpkgs": "nixpkgs_62" - }, "locked": { "lastModified": 1774455309, "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", @@ -3054,9 +3724,9 @@ "type": "github" } }, - "logos-nix_62": { + "logos-nix_61": { "inputs": { - "nixpkgs": "nixpkgs_63" + "nixpkgs": "nixpkgs_62" }, "locked": { "lastModified": 1773955630, @@ -3072,6 +3742,24 @@ "type": "github" } }, + "logos-nix_62": { + "inputs": { + "nixpkgs": "nixpkgs_63" + }, + "locked": { + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-nix_63": { "inputs": { "nixpkgs": "nixpkgs_64" @@ -3095,11 +3783,11 @@ "nixpkgs": "nixpkgs_65" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3185,11 +3873,11 @@ "nixpkgs": "nixpkgs_70" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -3239,11 +3927,11 @@ "nixpkgs": "nixpkgs_72" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3310,24 +3998,6 @@ "inputs": { "nixpkgs": "nixpkgs_76" }, - "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", - "owner": "logos-co", - "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", - "type": "github" - }, - "original": { - "owner": "logos-co", - "repo": "logos-nix", - "type": "github" - } - }, - "logos-nix_76": { - "inputs": { - "nixpkgs": "nixpkgs_77" - }, "locked": { "lastModified": 1773955630, "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", @@ -3342,9 +4012,9 @@ "type": "github" } }, - "logos-nix_77": { + "logos-nix_76": { "inputs": { - "nixpkgs": "nixpkgs_78" + "nixpkgs": "nixpkgs_77" }, "locked": { "lastModified": 1774455309, @@ -3360,6 +4030,24 @@ "type": "github" } }, + "logos-nix_77": { + "inputs": { + "nixpkgs": "nixpkgs_78" + }, + "locked": { + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "owner": "logos-co", + "repo": "logos-nix", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-nix", + "type": "github" + } + }, "logos-nix_78": { "inputs": { "nixpkgs": "nixpkgs_79" @@ -3383,11 +4071,11 @@ "nixpkgs": "nixpkgs_80" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3437,11 +4125,11 @@ "nixpkgs": "nixpkgs_82" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -3455,11 +4143,11 @@ "nixpkgs": "nixpkgs_83" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -3527,11 +4215,11 @@ "nixpkgs": "nixpkgs_87" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -3545,11 +4233,11 @@ "nixpkgs": "nixpkgs_88" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3563,11 +4251,11 @@ "nixpkgs": "nixpkgs_89" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3599,11 +4287,11 @@ "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3653,11 +4341,11 @@ "nixpkgs": "nixpkgs_93" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3689,11 +4377,11 @@ "nixpkgs": "nixpkgs_95" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -3707,11 +4395,11 @@ "nixpkgs": "nixpkgs_96" }, "locked": { - "lastModified": 1773955630, - "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", + "lastModified": 1774455309, + "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", "owner": "logos-co", "repo": "logos-nix", - "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", + "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", "type": "github" }, "original": { @@ -3743,11 +4431,11 @@ "nixpkgs": "nixpkgs_98" }, "locked": { - "lastModified": 1774455309, - "narHash": "sha256-3AN7aFnArdysrbQQ2UskWzjNSFADb4hDCsnx69Fa0ng=", + "lastModified": 1773955630, + "narHash": "sha256-KqzMoWYIVp2xMgphs7v02T/BE54RKMFxpdC2duhJKG0=", "owner": "logos-co", "repo": "logos-nix", - "rev": "e637a1f5e871244d1c2df1e3c52a067f2eb406f2", + "rev": "0e9e6d66ab8eb34f59e45ed448f7dc29130feb88", "type": "github" }, "original": { @@ -3794,7 +4482,7 @@ }, "logos-package": { "inputs": { - "logos-nix": "logos-nix_26", + "logos-nix": "logos-nix_36", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -3826,7 +4514,7 @@ }, "logos-package-manager": { "inputs": { - "logos-nix": "logos-nix_25", + "logos-nix": "logos-nix_35", "logos-package": "logos-package", "nix-bundle-appimage": "nix-bundle-appimage", "nix-bundle-dir": "nix-bundle-dir_2", @@ -3860,7 +4548,7 @@ }, "logos-package-manager_2": { "inputs": { - "logos-nix": "logos-nix_42", + "logos-nix": "logos-nix_52", "logos-package": "logos-package_4", "nix-bundle-appimage": "nix-bundle-appimage_2", "nix-bundle-dir": "nix-bundle-dir_6", @@ -3893,7 +4581,7 @@ }, "logos-package-manager_3": { "inputs": { - "logos-nix": "logos-nix_69", + "logos-nix": "logos-nix_79", "logos-package": "logos-package_6", "nix-bundle-appimage": "nix-bundle-appimage_3", "nix-bundle-dir": "nix-bundle-dir_9", @@ -3928,7 +4616,7 @@ }, "logos-package-manager_4": { "inputs": { - "logos-nix": "logos-nix_86", + "logos-nix": "logos-nix_96", "logos-package": "logos-package_9", "nix-bundle-appimage": "nix-bundle-appimage_4", "nix-bundle-dir": "nix-bundle-dir_13", @@ -3962,7 +4650,7 @@ }, "logos-package-manager_5": { "inputs": { - "logos-nix": "logos-nix_97", + "logos-nix": "logos-nix_107", "logos-package": "logos-package_11", "nix-bundle-appimage": "nix-bundle-appimage_5", "nix-bundle-dir": "nix-bundle-dir_16", @@ -3993,7 +4681,7 @@ }, "logos-package-manager_6": { "inputs": { - "logos-nix": "logos-nix_114", + "logos-nix": "logos-nix_124", "logos-package": "logos-package_14", "nix-bundle-appimage": "nix-bundle-appimage_6", "nix-bundle-dir": "nix-bundle-dir_20", @@ -4023,7 +4711,7 @@ }, "logos-package-manager_7": { "inputs": { - "logos-nix": "logos-nix_125", + "logos-nix": "logos-nix_138", "logos-package": "logos-package_16", "nix-bundle-appimage": "nix-bundle-appimage_7", "nix-bundle-dir": "nix-bundle-dir_23", @@ -4035,11 +4723,11 @@ ] }, "locked": { - "lastModified": 1776374462, - "narHash": "sha256-HMkuqSLdScAWTwXEWjhqx9Yk82GiPzPIfRaHTvjG730=", + "lastModified": 1781106836, + "narHash": "sha256-vHxdyGdW3Ai3/Omspw5drtJXzXyN0TWuCYet95A5h6E=", "owner": "logos-co", "repo": "logos-package-manager", - "rev": "9101875bc103214855bc6217834e22e66802ed86", + "rev": "8a6b72f28b1e73b6d753eaabe7eba13bf425f04e", "type": "github" }, "original": { @@ -4050,7 +4738,7 @@ }, "logos-package_10": { "inputs": { - "logos-nix": "logos-nix_92", + "logos-nix": "logos-nix_102", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4082,7 +4770,7 @@ }, "logos-package_11": { "inputs": { - "logos-nix": "logos-nix_98", + "logos-nix": "logos-nix_108", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4111,7 +4799,7 @@ }, "logos-package_12": { "inputs": { - "logos-nix": "logos-nix_107", + "logos-nix": "logos-nix_117", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4139,7 +4827,7 @@ }, "logos-package_13": { "inputs": { - "logos-nix": "logos-nix_111", + "logos-nix": "logos-nix_121", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4166,7 +4854,7 @@ }, "logos-package_14": { "inputs": { - "logos-nix": "logos-nix_115", + "logos-nix": "logos-nix_125", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4194,7 +4882,7 @@ }, "logos-package_15": { "inputs": { - "logos-nix": "logos-nix_120", + "logos-nix": "logos-nix_130", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4222,7 +4910,7 @@ }, "logos-package_16": { "inputs": { - "logos-nix": "logos-nix_126", + "logos-nix": "logos-nix_139", "nixpkgs": [ "logos-liblogos", "logos-package-manager", @@ -4247,7 +4935,7 @@ }, "logos-package_2": { "inputs": { - "logos-nix": "logos-nix_35", + "logos-nix": "logos-nix_45", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4278,7 +4966,7 @@ }, "logos-package_3": { "inputs": { - "logos-nix": "logos-nix_39", + "logos-nix": "logos-nix_49", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4308,7 +4996,7 @@ }, "logos-package_4": { "inputs": { - "logos-nix": "logos-nix_43", + "logos-nix": "logos-nix_53", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4339,7 +5027,7 @@ }, "logos-package_5": { "inputs": { - "logos-nix": "logos-nix_48", + "logos-nix": "logos-nix_58", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4370,7 +5058,7 @@ }, "logos-package_6": { "inputs": { - "logos-nix": "logos-nix_70", + "logos-nix": "logos-nix_80", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4403,7 +5091,7 @@ }, "logos-package_7": { "inputs": { - "logos-nix": "logos-nix_79", + "logos-nix": "logos-nix_89", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4435,7 +5123,7 @@ }, "logos-package_8": { "inputs": { - "logos-nix": "logos-nix_83", + "logos-nix": "logos-nix_93", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4466,7 +5154,7 @@ }, "logos-package_9": { "inputs": { - "logos-nix": "logos-nix_87", + "logos-nix": "logos-nix_97", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4498,8 +5186,8 @@ }, "logos-plugin-core": { "inputs": { - "logos-module": "logos-module_2", - "logos-nix": "logos-nix_5", + "logos-module": "logos-module_3", + "logos-nix": "logos-nix_15", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4525,8 +5213,8 @@ }, "logos-plugin-core_2": { "inputs": { - "logos-module": "logos-module_5", - "logos-nix": "logos-nix_12", + "logos-module": "logos-module_6", + "logos-nix": "logos-nix_22", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4555,8 +5243,8 @@ }, "logos-plugin-core_3": { "inputs": { - "logos-module": "logos-module_11", - "logos-nix": "logos-nix_56", + "logos-module": "logos-module_12", + "logos-nix": "logos-nix_66", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4586,8 +5274,8 @@ }, "logos-plugin-qt": { "inputs": { - "logos-module": "logos-module_3", - "logos-nix": "logos-nix_7", + "logos-module": "logos-module_4", + "logos-nix": "logos-nix_17", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4613,8 +5301,8 @@ }, "logos-plugin-qt_2": { "inputs": { - "logos-module": "logos-module_6", - "logos-nix": "logos-nix_14", + "logos-module": "logos-module_7", + "logos-nix": "logos-nix_24", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4643,8 +5331,8 @@ }, "logos-plugin-qt_3": { "inputs": { - "logos-module": "logos-module_12", - "logos-nix": "logos-nix_58", + "logos-module": "logos-module_13", + "logos-nix": "logos-nix_68", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4672,9 +5360,58 @@ "type": "github" } }, + "logos-protocol": { + "inputs": { + "logos-nix": "logos-nix_9", + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", + "logos-protocol", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781303997, + "narHash": "sha256-j9U4wPlHdN5oA+65h1SAo/s2YUHvNYKxij4/pCqEtAs=", + "owner": "logos-co", + "repo": "logos-protocol", + "rev": "9de4165ab68ece4071647026cb2596bed8a8d7e2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-protocol", + "type": "github" + } + }, + "logos-protocol_2": { + "inputs": { + "logos-nix": "logos-nix_143", + "nixpkgs": [ + "logos-liblogos", + "logos-protocol", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781303997, + "narHash": "sha256-j9U4wPlHdN5oA+65h1SAo/s2YUHvNYKxij4/pCqEtAs=", + "owner": "logos-co", + "repo": "logos-protocol", + "rev": "9de4165ab68ece4071647026cb2596bed8a8d7e2", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-protocol", + "type": "github" + } + }, "logos-qt-mcp": { "inputs": { - "logos-nix": "logos-nix_32", + "logos-nix": "logos-nix_42", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4703,7 +5440,7 @@ }, "logos-qt-mcp_2": { "inputs": { - "logos-nix": "logos-nix_76", + "logos-nix": "logos-nix_86", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4733,7 +5470,7 @@ }, "logos-qt-mcp_3": { "inputs": { - "logos-nix": "logos-nix_104", + "logos-nix": "logos-nix_114", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4758,13 +5495,82 @@ "type": "github" } }, + "logos-qt-sdk": { + "inputs": { + "logos-cpp-sdk": [ + "logos-liblogos", + "default-module-loader", + "logos-cpp-sdk" + ], + "logos-lidl": "logos-lidl_2", + "logos-nix": "logos-nix_10", + "logos-protocol": [ + "logos-liblogos", + "default-module-loader", + "logos-protocol" + ], + "nixpkgs": [ + "logos-liblogos", + "default-module-loader", + "logos-qt-sdk", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781655169, + "narHash": "sha256-wjbHmczSG8VUMlRkIp3pviVTLPTzReDbIRfBpqnlQr4=", + "owner": "logos-co", + "repo": "logos-qt-sdk", + "rev": "812369213719773f6486755a30c476a699469b37", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-qt-sdk", + "type": "github" + } + }, + "logos-qt-sdk_2": { + "inputs": { + "logos-cpp-sdk": [ + "logos-liblogos", + "logos-cpp-sdk" + ], + "logos-lidl": "logos-lidl_4", + "logos-nix": "logos-nix_144", + "logos-protocol": [ + "logos-liblogos", + "logos-protocol" + ], + "nixpkgs": [ + "logos-liblogos", + "logos-qt-sdk", + "logos-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1781655169, + "narHash": "sha256-wjbHmczSG8VUMlRkIp3pviVTLPTzReDbIRfBpqnlQr4=", + "owner": "logos-co", + "repo": "logos-qt-sdk", + "rev": "812369213719773f6486755a30c476a699469b37", + "type": "github" + }, + "original": { + "owner": "logos-co", + "repo": "logos-qt-sdk", + "type": "github" + } + }, "logos-standalone-app": { "inputs": { "logos-capability-module": "logos-capability-module_2", - "logos-cpp-sdk": "logos-cpp-sdk_6", + "logos-cpp-sdk": "logos-cpp-sdk_7", "logos-design-system": "logos-design-system_2", "logos-liblogos": "logos-liblogos_3", - "logos-nix": "logos-nix_103", + "logos-nix": "logos-nix_113", "logos-qt-mcp": "logos-qt-mcp_3", "logos-view-module-runtime": "logos-view-module-runtime_3", "nix-bundle-lgx": "nix-bundle-lgx_7", @@ -4794,10 +5600,10 @@ "logos-standalone-app_2": { "inputs": { "logos-capability-module": "logos-capability-module_3", - "logos-cpp-sdk": "logos-cpp-sdk_3", + "logos-cpp-sdk": "logos-cpp-sdk_4", "logos-design-system": "logos-design-system", "logos-liblogos": "logos-liblogos_2", - "logos-nix": "logos-nix_31", + "logos-nix": "logos-nix_41", "logos-qt-mcp": "logos-qt-mcp", "logos-view-module-runtime": "logos-view-module-runtime", "nix-bundle-lgx": "nix-bundle-lgx", @@ -4830,10 +5636,10 @@ "logos-standalone-app_3": { "inputs": { "logos-capability-module": "logos-capability-module_6", - "logos-cpp-sdk": "logos-cpp-sdk_8", + "logos-cpp-sdk": "logos-cpp-sdk_9", "logos-design-system": "logos-design-system_3", "logos-liblogos": "logos-liblogos_4", - "logos-nix": "logos-nix_75", + "logos-nix": "logos-nix_85", "logos-qt-mcp": "logos-qt-mcp_2", "logos-view-module-runtime": "logos-view-module-runtime_2", "nix-bundle-lgx": "nix-bundle-lgx_4", @@ -4875,7 +5681,7 @@ "logos-module-builder", "logos-cpp-sdk" ], - "logos-nix": "logos-nix_37", + "logos-nix": "logos-nix_47", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4914,7 +5720,7 @@ "logos-module-builder", "logos-cpp-sdk" ], - "logos-nix": "logos-nix_81", + "logos-nix": "logos-nix_91", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4950,7 +5756,7 @@ "logos-module-builder", "logos-cpp-sdk" ], - "logos-nix": "logos-nix_109", + "logos-nix": "logos-nix_119", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -4986,7 +5792,7 @@ "logos-standalone-app", "logos-cpp-sdk" ], - "logos-nix": "logos-nix_33", + "logos-nix": "logos-nix_43", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5026,7 +5832,7 @@ "logos-standalone-app", "logos-cpp-sdk" ], - "logos-nix": "logos-nix_77", + "logos-nix": "logos-nix_87", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5056,8 +5862,8 @@ }, "logos-view-module-runtime_3": { "inputs": { - "logos-cpp-sdk": "logos-cpp-sdk_12", - "logos-nix": "logos-nix_105", + "logos-cpp-sdk": "logos-cpp-sdk_13", + "logos-nix": "logos-nix_115", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5084,7 +5890,7 @@ }, "nix-bundle-appimage": { "inputs": { - "logos-nix": "logos-nix_27", + "logos-nix": "logos-nix_37", "nix-bundle-dir": "nix-bundle-dir", "nixpkgs": [ "logos-liblogos", @@ -5117,7 +5923,7 @@ }, "nix-bundle-appimage_2": { "inputs": { - "logos-nix": "logos-nix_44", + "logos-nix": "logos-nix_54", "nix-bundle-dir": "nix-bundle-dir_5", "nixpkgs": [ "logos-liblogos", @@ -5149,7 +5955,7 @@ }, "nix-bundle-appimage_3": { "inputs": { - "logos-nix": "logos-nix_71", + "logos-nix": "logos-nix_81", "nix-bundle-dir": "nix-bundle-dir_8", "nixpkgs": [ "logos-liblogos", @@ -5183,7 +5989,7 @@ }, "nix-bundle-appimage_4": { "inputs": { - "logos-nix": "logos-nix_88", + "logos-nix": "logos-nix_98", "nix-bundle-dir": "nix-bundle-dir_12", "nixpkgs": [ "logos-liblogos", @@ -5216,7 +6022,7 @@ }, "nix-bundle-appimage_5": { "inputs": { - "logos-nix": "logos-nix_99", + "logos-nix": "logos-nix_109", "nix-bundle-dir": "nix-bundle-dir_15", "nixpkgs": [ "logos-liblogos", @@ -5246,7 +6052,7 @@ }, "nix-bundle-appimage_6": { "inputs": { - "logos-nix": "logos-nix_116", + "logos-nix": "logos-nix_126", "nix-bundle-dir": "nix-bundle-dir_19", "nixpkgs": [ "logos-liblogos", @@ -5275,7 +6081,7 @@ }, "nix-bundle-appimage_7": { "inputs": { - "logos-nix": "logos-nix_127", + "logos-nix": "logos-nix_140", "nix-bundle-dir": "nix-bundle-dir_22", "nixpkgs": [ "logos-liblogos", @@ -5301,7 +6107,7 @@ }, "nix-bundle-dir": { "inputs": { - "logos-nix": "logos-nix_28", + "logos-nix": "logos-nix_38", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5332,7 +6138,7 @@ }, "nix-bundle-dir_10": { "inputs": { - "logos-nix": "logos-nix_80", + "logos-nix": "logos-nix_90", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5364,7 +6170,7 @@ }, "nix-bundle-dir_11": { "inputs": { - "logos-nix": "logos-nix_84", + "logos-nix": "logos-nix_94", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5395,7 +6201,7 @@ }, "nix-bundle-dir_12": { "inputs": { - "logos-nix": "logos-nix_89", + "logos-nix": "logos-nix_99", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5426,7 +6232,7 @@ }, "nix-bundle-dir_13": { "inputs": { - "logos-nix": "logos-nix_90", + "logos-nix": "logos-nix_100", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5458,7 +6264,7 @@ }, "nix-bundle-dir_14": { "inputs": { - "logos-nix": "logos-nix_93", + "logos-nix": "logos-nix_103", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5490,7 +6296,7 @@ }, "nix-bundle-dir_15": { "inputs": { - "logos-nix": "logos-nix_100", + "logos-nix": "logos-nix_110", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5518,7 +6324,7 @@ }, "nix-bundle-dir_16": { "inputs": { - "logos-nix": "logos-nix_101", + "logos-nix": "logos-nix_111", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5547,7 +6353,7 @@ }, "nix-bundle-dir_17": { "inputs": { - "logos-nix": "logos-nix_108", + "logos-nix": "logos-nix_118", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5575,7 +6381,7 @@ }, "nix-bundle-dir_18": { "inputs": { - "logos-nix": "logos-nix_112", + "logos-nix": "logos-nix_122", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5602,7 +6408,7 @@ }, "nix-bundle-dir_19": { "inputs": { - "logos-nix": "logos-nix_117", + "logos-nix": "logos-nix_127", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5629,7 +6435,7 @@ }, "nix-bundle-dir_2": { "inputs": { - "logos-nix": "logos-nix_29", + "logos-nix": "logos-nix_39", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5661,7 +6467,7 @@ }, "nix-bundle-dir_20": { "inputs": { - "logos-nix": "logos-nix_118", + "logos-nix": "logos-nix_128", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5689,7 +6495,7 @@ }, "nix-bundle-dir_21": { "inputs": { - "logos-nix": "logos-nix_121", + "logos-nix": "logos-nix_131", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5717,7 +6523,7 @@ }, "nix-bundle-dir_22": { "inputs": { - "logos-nix": "logos-nix_128", + "logos-nix": "logos-nix_141", "nixpkgs": [ "logos-liblogos", "logos-package-manager", @@ -5741,7 +6547,7 @@ }, "nix-bundle-dir_23": { "inputs": { - "logos-nix": "logos-nix_129", + "logos-nix": "logos-nix_142", "nixpkgs": [ "logos-liblogos", "logos-package-manager", @@ -5766,7 +6572,7 @@ }, "nix-bundle-dir_3": { "inputs": { - "logos-nix": "logos-nix_36", + "logos-nix": "logos-nix_46", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5797,7 +6603,7 @@ }, "nix-bundle-dir_4": { "inputs": { - "logos-nix": "logos-nix_40", + "logos-nix": "logos-nix_50", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5827,7 +6633,7 @@ }, "nix-bundle-dir_5": { "inputs": { - "logos-nix": "logos-nix_45", + "logos-nix": "logos-nix_55", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5857,7 +6663,7 @@ }, "nix-bundle-dir_6": { "inputs": { - "logos-nix": "logos-nix_46", + "logos-nix": "logos-nix_56", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5888,7 +6694,7 @@ }, "nix-bundle-dir_7": { "inputs": { - "logos-nix": "logos-nix_49", + "logos-nix": "logos-nix_59", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5919,7 +6725,7 @@ }, "nix-bundle-dir_8": { "inputs": { - "logos-nix": "logos-nix_72", + "logos-nix": "logos-nix_82", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5951,7 +6757,7 @@ }, "nix-bundle-dir_9": { "inputs": { - "logos-nix": "logos-nix_73", + "logos-nix": "logos-nix_83", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -5984,7 +6790,7 @@ }, "nix-bundle-lgx": { "inputs": { - "logos-nix": "logos-nix_34", + "logos-nix": "logos-nix_44", "logos-package": "logos-package_2", "nix-bundle-dir": "nix-bundle-dir_3", "nixpkgs": [ @@ -6015,7 +6821,7 @@ }, "nix-bundle-lgx_2": { "inputs": { - "logos-nix": "logos-nix_38", + "logos-nix": "logos-nix_48", "logos-package": "logos-package_3", "nix-bundle-dir": "nix-bundle-dir_4", "nixpkgs": [ @@ -6046,7 +6852,7 @@ }, "nix-bundle-lgx_3": { "inputs": { - "logos-nix": "logos-nix_47", + "logos-nix": "logos-nix_57", "logos-package": "logos-package_5", "nix-bundle-dir": "nix-bundle-dir_7", "nixpkgs": [ @@ -6078,7 +6884,7 @@ }, "nix-bundle-lgx_4": { "inputs": { - "logos-nix": "logos-nix_78", + "logos-nix": "logos-nix_88", "logos-package": "logos-package_7", "nix-bundle-dir": "nix-bundle-dir_10", "nixpkgs": [ @@ -6110,7 +6916,7 @@ }, "nix-bundle-lgx_5": { "inputs": { - "logos-nix": "logos-nix_82", + "logos-nix": "logos-nix_92", "logos-package": "logos-package_8", "nix-bundle-dir": "nix-bundle-dir_11", "nixpkgs": [ @@ -6142,7 +6948,7 @@ }, "nix-bundle-lgx_6": { "inputs": { - "logos-nix": "logos-nix_91", + "logos-nix": "logos-nix_101", "logos-package": "logos-package_10", "nix-bundle-dir": "nix-bundle-dir_14", "nixpkgs": [ @@ -6175,7 +6981,7 @@ }, "nix-bundle-lgx_7": { "inputs": { - "logos-nix": "logos-nix_106", + "logos-nix": "logos-nix_116", "logos-package": "logos-package_12", "nix-bundle-dir": "nix-bundle-dir_17", "nixpkgs": [ @@ -6204,7 +7010,7 @@ }, "nix-bundle-lgx_8": { "inputs": { - "logos-nix": "logos-nix_110", + "logos-nix": "logos-nix_120", "logos-package": "logos-package_13", "nix-bundle-dir": "nix-bundle-dir_18", "nixpkgs": [ @@ -6232,7 +7038,7 @@ }, "nix-bundle-lgx_9": { "inputs": { - "logos-nix": "logos-nix_119", + "logos-nix": "logos-nix_129", "logos-package": "logos-package_15", "nix-bundle-dir": "nix-bundle-dir_21", "nixpkgs": [ @@ -6261,7 +7067,7 @@ }, "nix-bundle-logos-module-install": { "inputs": { - "logos-nix": "logos-nix_41", + "logos-nix": "logos-nix_51", "logos-package-manager": "logos-package-manager_2", "nix-bundle-lgx": "nix-bundle-lgx_3", "nixpkgs": [ @@ -6292,7 +7098,7 @@ }, "nix-bundle-logos-module-install_2": { "inputs": { - "logos-nix": "logos-nix_85", + "logos-nix": "logos-nix_95", "logos-package-manager": "logos-package-manager_4", "nix-bundle-lgx": "nix-bundle-lgx_6", "nixpkgs": [ @@ -6324,7 +7130,7 @@ }, "nix-bundle-logos-module-install_3": { "inputs": { - "logos-nix": "logos-nix_113", + "logos-nix": "logos-nix_123", "logos-package-manager": "logos-package-manager_6", "nix-bundle-lgx": "nix-bundle-lgx_9", "nixpkgs": [ @@ -6944,16 +7750,128 @@ }, "nixpkgs_132": { "locked": { - "lastModified": 1767313136, - "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.05", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_133": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_134": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_135": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_136": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_137": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_138": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_139": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -6974,6 +7892,134 @@ "type": "github" } }, + "nixpkgs_140": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_141": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_142": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_143": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_144": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_145": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_146": { + "locked": { + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_147": { + "locked": { + "lastModified": 1767313136, + "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs_15": { "locked": { "lastModified": 1759036355, @@ -8464,7 +9510,7 @@ }, "process-stats": { "inputs": { - "logos-nix": "logos-nix_30", + "logos-nix": "logos-nix_40", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -8495,7 +9541,7 @@ }, "process-stats_2": { "inputs": { - "logos-nix": "logos-nix_74", + "logos-nix": "logos-nix_84", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -8527,7 +9573,7 @@ }, "process-stats_3": { "inputs": { - "logos-nix": "logos-nix_102", + "logos-nix": "logos-nix_112", "nixpkgs": [ "logos-liblogos", "logos-capability-module", @@ -8555,7 +9601,7 @@ }, "process-stats_4": { "inputs": { - "logos-nix": "logos-nix_130", + "logos-nix": "logos-nix_145", "nixpkgs": [ "logos-liblogos", "process-stats", @@ -8597,11 +9643,11 @@ ] }, "locked": { - "lastModified": 1781234414, - "narHash": "sha256-HdA+P4fKRGOomkewnI/Tww5Wz4xK1O7+hDO90YAsPB4=", + "lastModified": 1781752752, + "narHash": "sha256-kVG5tV9hddPviGAgqf9sGSuStvv+HAB9onfKqGptV0k=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "1d18bfe3de6244c641ca4e8011186d0981b81d76", + "rev": "c06d86dabe5b92982b9d67acccb9990d58da3a0e", "type": "github" }, "original": { @@ -8612,7 +9658,7 @@ }, "rust-rapidsnark": { "inputs": { - "nixpkgs": "nixpkgs_132" + "nixpkgs": "nixpkgs_147" }, "locked": { "lastModified": 1781090841, diff --git a/flake.nix b/flake.nix index 3babf94c..0aaad40c 100644 --- a/flake.nix +++ b/flake.nix @@ -99,6 +99,28 @@ sha256 = recursionZkrHash; }; + # risc0 compiles its Metal (GPU) prover kernels by invoking + # `xcrun metal` / `xcrun metallib`. Under nix, the darwin stdenv sets + # DEVELOPER_DIR/SDKROOT to its own SDK, which makes `xcrun` look for + # the `metal` tool in the wrong place and fail with + # error: cannot execute tool 'metal' due to missing Metal Toolchain + # even when a working Metal Toolchain is installed. This wrapper, put + # first in PATH, clears those two vars for metal/metallib invocations + # only โ€” so they resolve the real system Xcode Metal Toolchain โ€” while + # every other xcrun call passes through with the nix environment + # intact. (On recent macOS the Metal Toolchain is a per-user component; + # `xcodebuild -downloadComponent MetalToolchain` must have been run.) + metalStub = pkgs.writeShellScriptBin "xcrun" '' + tool= + for a in "$@"; do + case "$a" in metal|metallib) tool=1 ;; esac + done + if [ -n "$tool" ]; then + unset DEVELOPER_DIR SDKROOT + fi + exec /usr/bin/xcrun "$@" + ''; + commonArgs = { inherit src; buildInputs = [ pkgs.openssl ]; @@ -118,13 +140,14 @@ RECURSION_SRC_PATH = "${recursionZkr}"; # Provide a writable HOME so risc0-build-kernel can use its cache directory # (needed on macOS for Metal kernel compilation cache). - # On macOS, append /usr/bin to PATH so xcrun (Metal compiler) can be found, - # while keeping Nix tools (like gnutar) first in PATH. + # On macOS, put the metalStub xcrun wrapper first so `xcrun metal` / + # `metallib` resolve the system Metal Toolchain (see metalStub above), + # and append /usr/bin for the real xcrun it execs. # This requires running with --option sandbox false for Metal GPU support. preBuild = '' export HOME=$(mktemp -d) '' + pkgs.lib.optionalString pkgs.stdenv.isDarwin '' - export PATH="$PATH:/usr/bin" + export PATH="${metalStub}/bin:$PATH:/usr/bin" ''; }; diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 11cff7bc..a225d657 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -9,7 +9,6 @@ workspace = true [dependencies] test_fixtures.workspace = true - lee_core = { workspace = true, features = ["host"] } lee.workspace = true authenticated_transfer_core.workspace = true @@ -19,7 +18,7 @@ common.workspace = true key_protocol.workspace = true serde_json.workspace = true token_core.workspace = true -ata_core.workspace = true +associated_token_account_core.workspace = true vault_core.workspace = true faucet_core.workspace = true bridge_core.workspace = true @@ -34,6 +33,9 @@ sequencer_service_rpc = { workspace = true, features = ["client"] } wallet-ffi.workspace = true indexer_ffi.workspace = true indexer_service_protocol.workspace = true +system_accounts.workspace = true +programs.workspace = true +test_programs.workspace = true logos-blockchain-http-api-common.workspace = true logos-blockchain-core.workspace = true diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 48f69559..07212251 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -10,7 +10,7 @@ use log::info; pub use test_fixtures::*; /// Maximum time to wait for the indexer to catch up to the sequencer. -pub const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(6); +pub const L2_TO_L1_TIMEOUT: Duration = Duration::from_mins(7); /// Poll the indexer until its last finalized block id reaches the sequencer's /// current last block id or until [`L2_TO_L1_TIMEOUT`] elapses. diff --git a/integration_tests/tests/account.rs b/integration_tests/tests/account.rs index 3d5854cf..0de8a9e2 100644 --- a/integration_tests/tests/account.rs +++ b/integration_tests/tests/account.rs @@ -6,7 +6,7 @@ use anyhow::{Context as _, Result}; use integration_tests::{TestContext, private_mention}; use key_protocol::key_management::KeyChain; -use lee::{Data, program::Program}; +use lee::Data; use lee_core::account::Nonce; use log::info; use sequencer_service_rpc::RpcClient as _; @@ -31,7 +31,7 @@ async fn get_existing_account() -> Result<()> { assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); assert_eq!(account.balance, 10000); assert!(account.data.is_empty()); @@ -158,7 +158,7 @@ async fn import_private_account() -> Result<()> { let key_chain = KeyChain::new_os_random(); let account_id = lee::AccountId::from((&key_chain.nullifier_public_key, 0)); let account = lee::Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: programs::authenticated_transfer().id(), balance: 777, data: Data::default(), nonce: Nonce::default(), @@ -218,7 +218,7 @@ async fn import_private_account_second_time_overrides_account_data() -> Result<( serde_json::to_string(&key_chain).context("Failed to serialize key chain")?; let initial_account = lee::Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: programs::authenticated_transfer().id(), balance: 100, data: Data::default(), nonce: Nonce::default(), @@ -237,7 +237,7 @@ async fn import_private_account_second_time_overrides_account_data() -> Result<( .await?; let updated_account = lee::Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: programs::authenticated_transfer().id(), balance: 999, data: Data::default(), nonce: Nonce::default(), diff --git a/integration_tests/tests/ata.rs b/integration_tests/tests/ata.rs index 9e37061b..7faac67e 100644 --- a/integration_tests/tests/ata.rs +++ b/integration_tests/tests/ata.rs @@ -7,12 +7,11 @@ use std::time::Duration; use anyhow::{Context as _, Result}; -use ata_core::{compute_ata_seed, get_associated_token_account_id}; +use associated_token_account_core::{compute_ata_seed, get_associated_token_account_id}; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention, verify_commitment_is_in_state, }; -use lee::program::Program; use log::info; use sequencer_service_rpc::RpcClient as _; use token_core::{TokenDefinition, TokenHolding}; @@ -93,7 +92,7 @@ async fn create_ata_initializes_holding_account() -> Result<()> { tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; // Derive expected ATA address and check on-chain state - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_account_id, definition_account_id), @@ -105,7 +104,7 @@ async fn create_ata_initializes_holding_account() -> Result<()> { .await .context("ATA account not found")?; - assert_eq!(ata_acc.program_owner, Program::token().id()); + assert_eq!(ata_acc.program_owner, programs::token().id()); let holding = TokenHolding::try_from(&ata_acc.data)?; assert_eq!( holding, @@ -168,7 +167,7 @@ async fn create_ata_is_idempotent() -> Result<()> { tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; // State must be unchanged - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_account_id, definition_account_id), @@ -180,7 +179,7 @@ async fn create_ata_is_idempotent() -> Result<()> { .await .context("ATA account not found")?; - assert_eq!(ata_acc.program_owner, Program::token().id()); + assert_eq!(ata_acc.program_owner, programs::token().id()); let holding = TokenHolding::try_from(&ata_acc.data)?; assert_eq!( holding, @@ -220,7 +219,7 @@ async fn transfer_and_burn_via_ata() -> Result<()> { tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; // Derive ATA addresses - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let sender_ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(sender_account_id, definition_account_id), @@ -389,7 +388,7 @@ async fn create_ata_with_private_owner() -> Result<()> { tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; // Derive expected ATA address and check on-chain state - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_account_id, definition_account_id), @@ -401,7 +400,7 @@ async fn create_ata_with_private_owner() -> Result<()> { .await .context("ATA account not found")?; - assert_eq!(ata_acc.program_owner, Program::token().id()); + assert_eq!(ata_acc.program_owner, programs::token().id()); let holding = TokenHolding::try_from(&ata_acc.data)?; assert_eq!( holding, @@ -448,7 +447,7 @@ async fn transfer_via_ata_private_owner() -> Result<()> { tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; // Derive ATA addresses - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let sender_ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(sender_account_id, definition_account_id), @@ -572,7 +571,7 @@ async fn burn_via_ata_private_owner() -> Result<()> { tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; // Derive holder's ATA address - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let holder_ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(holder_account_id, definition_account_id), diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index 45a1b085..30f0cfdd 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -429,7 +429,7 @@ async fn initialize_private_account() -> Result<()> { assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); assert_eq!(account.balance, 0); assert!(account.data.is_empty()); @@ -526,7 +526,7 @@ async fn initialize_private_account_using_label() -> Result<()> { assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); info!("Successfully initialized private account using label"); @@ -646,23 +646,20 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> { async fn ppt_cant_chain_call_faucet() -> Result<()> { let ctx = TestContext::new().await?; - let binary = std::fs::read( - std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../artifacts/test_program_methods/faucet_chain_caller.bin"), - )?; + let faucet_chain_caller = test_programs::faucet_chain_caller(); let deploy_tx = LeeTransaction::ProgramDeployment(lee::ProgramDeploymentTransaction::new( - lee::program_deployment_transaction::Message::new(binary.clone()), + lee::program_deployment_transaction::Message::new(faucet_chain_caller.elf().to_owned()), )); ctx.sequencer_client().send_transaction(deploy_tx).await?; info!("Waiting for deploy block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let faucet_account_id = lee::system_faucet_account_id(); + let faucet_account_id = system_accounts::faucet_account_id(); let attacker_id = ctx.existing_public_accounts()[0]; - let faucet_program_id = Program::faucet().id(); - let vault_program_id = Program::vault().id(); - let auth_transfer_program_id = Program::authenticated_transfer_program().id(); + let faucet_program_id = programs::faucet().id(); + let vault_program_id = programs::vault().id(); + let auth_transfer_program_id = programs::authenticated_transfer().id(); let nsk: lee_core::NullifierSecretKey = [3; 32]; let npk = NullifierPublicKey::from(&nsk); let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap(); @@ -689,16 +686,12 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> { attacker_vault_id, ); - let faucet_chain_caller = Program::new(binary)?; let program_with_deps = ProgramWithDependencies::new( faucet_chain_caller, [ - (faucet_program_id, Program::faucet()), - (vault_program_id, Program::vault()), - ( - auth_transfer_program_id, - Program::authenticated_transfer_program(), - ), + (faucet_program_id, programs::faucet()), + (vault_program_id, programs::vault()), + (auth_transfer_program_id, programs::authenticated_transfer()), ] .into(), ); diff --git a/integration_tests/tests/auth_transfer/public.rs b/integration_tests/tests/auth_transfer/public.rs index d00b7964..5bbf0954 100644 --- a/integration_tests/tests/auth_transfer/public.rs +++ b/integration_tests/tests/auth_transfer/public.rs @@ -1,9 +1,9 @@ -use std::{path::PathBuf, time::Duration}; +use std::time::Duration; use anyhow::Result; use common::transaction::LeeTransaction; use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, public_mention}; -use lee::{program::Program, public_transaction, system_faucet_account_id}; +use lee::public_transaction; use log::info; use sequencer_service_rpc::RpcClient as _; use tokio::test; @@ -250,7 +250,7 @@ async fn initialize_public_account() -> Result<()> { assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); assert_eq!(account.balance, 0); assert_eq!(account.nonce.0, 1); @@ -356,7 +356,7 @@ async fn successful_transfer_using_to_label() -> Result<()> { #[test] async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { let ctx = TestContext::new().await?; - let faucet_account_id = system_faucet_account_id(); + let faucet_account_id = system_accounts::faucet_account_id(); let recipient = ctx.existing_public_accounts()[0]; let recipient_balance_before = ctx @@ -370,7 +370,7 @@ async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { let amount = 1_u128; let message = public_transaction::Message::try_new( - Program::authenticated_transfer_program().id(), + programs::authenticated_transfer().id(), vec![faucet_account_id, recipient], vec![], authenticated_transfer_core::Instruction::Transfer { amount }, @@ -407,10 +407,10 @@ async fn cannot_transfer_funds_from_system_faucet_account() -> Result<()> { #[test] async fn cannot_execute_faucet_program() -> Result<()> { let ctx = TestContext::new().await?; - let faucet_account_id = system_faucet_account_id(); + let faucet_account_id = system_accounts::faucet_account_id(); let recipient = ctx.existing_public_accounts()[0]; - let vault_program_id = Program::vault().id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient); let recipient_balance_before = ctx @@ -424,7 +424,7 @@ async fn cannot_execute_faucet_program() -> Result<()> { let amount = 1_u128; let message = public_transaction::Message::try_new( - Program::faucet().id(), + programs::faucet().id(), vec![faucet_account_id, recipient_vault_id], vec![], faucet_core::Instruction::GenesisTransferVault { @@ -466,28 +466,24 @@ async fn cannot_execute_faucet_program() -> Result<()> { async fn user_tx_that_chain_calls_faucet_is_dropped() -> Result<()> { let ctx = TestContext::new().await?; - let binary = std::fs::read( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../artifacts/test_program_methods/faucet_chain_caller.bin"), - )?; - let faucet_chain_caller_id = Program::new(binary.clone())?.id(); + let faucet_chain_caller = test_programs::faucet_chain_caller(); let deploy_tx = LeeTransaction::ProgramDeployment(lee::ProgramDeploymentTransaction::new( - lee::program_deployment_transaction::Message::new(binary), + lee::program_deployment_transaction::Message::new(faucet_chain_caller.elf().to_owned()), )); ctx.sequencer_client().send_transaction(deploy_tx).await?; info!("Waiting for deploy block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - let faucet_account_id = system_faucet_account_id(); + let faucet_account_id = system_accounts::faucet_account_id(); let attacker = ctx.existing_public_accounts()[0]; - let faucet_program_id = Program::faucet().id(); - let vault_program_id = Program::vault().id(); + let faucet_program_id = programs::faucet().id(); + let vault_program_id = programs::vault().id(); let attacker_vault_id = vault_core::compute_vault_account_id(vault_program_id, attacker); let amount: u128 = 1; let message = public_transaction::Message::try_new( - faucet_chain_caller_id, + faucet_chain_caller.id(), vec![faucet_account_id, attacker_vault_id], vec![], (faucet_program_id, vault_program_id, attacker, amount), diff --git a/integration_tests/tests/block_size_limit.rs b/integration_tests/tests/block_size_limit.rs index d95150f5..d97b695d 100644 --- a/integration_tests/tests/block_size_limit.rs +++ b/integration_tests/tests/block_size_limit.rs @@ -94,16 +94,12 @@ async fn accept_transaction_within_limit() -> Result<()> { #[test] async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> { - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let artifacts_dir = - std::path::PathBuf::from(manifest_dir).join("../artifacts/test_program_methods"); - - let burner_bytecode = std::fs::read(artifacts_dir.join("burner.bin"))?; - let chain_caller_bytecode = std::fs::read(artifacts_dir.join("chain_caller.bin"))?; + let claimer = test_programs::claimer(); + let chain_caller = test_programs::chain_caller(); // Calculate block size to fit only one of the two transactions, leaving some room for headers // (e.g., 10 KiB) - let max_program_size = burner_bytecode.len().max(chain_caller_bytecode.len()); + let max_program_size = claimer.elf().len().max(chain_caller.elf().len()); let block_size = ByteSize::b((max_program_size + 10 * 1024) as u64); let ctx = TestContext::builder() @@ -116,16 +112,13 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> { .build() .await?; - let burner_id = Program::new(burner_bytecode.clone())?.id(); - let chain_caller_id = Program::new(chain_caller_bytecode.clone())?.id(); - let initial_block_height = ctx.sequencer_client().get_last_block_id().await?; // Submit both program deployments ctx.sequencer_client() .send_transaction(LeeTransaction::ProgramDeployment( lee::ProgramDeploymentTransaction::new( - lee::program_deployment_transaction::Message::new(burner_bytecode), + lee::program_deployment_transaction::Message::new(claimer.elf().to_owned()), ), )) .await?; @@ -133,7 +126,7 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> { ctx.sequencer_client() .send_transaction(LeeTransaction::ProgramDeployment( lee::ProgramDeploymentTransaction::new( - lee::program_deployment_transaction::Message::new(chain_caller_bytecode), + lee::program_deployment_transaction::Message::new(chain_caller.elf().to_owned()), ), )) .await?; @@ -156,7 +149,7 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> { .filter_map(|tx| { if let LeeTransaction::ProgramDeployment(deployment) = tx { let bytecode = deployment.message.clone().into_bytecode(); - Program::new(bytecode).ok().map(|p| p.id()) + Program::new(bytecode.into()).ok().map(|p| p.id()) } else { None } @@ -173,8 +166,9 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> { "Expected exactly one program deployment in block 1" ); assert_eq!( - block1_program_ids[0], burner_id, - "Expected burner program to be deployed in block 1" + block1_program_ids[0], + claimer.id(), + "Expected claimer program to be deployed in block 1" ); // Wait for second block @@ -194,7 +188,8 @@ async fn transaction_deferred_to_next_block_when_current_full() -> Result<()> { "Expected exactly one program deployment in block 2" ); assert_eq!( - block2_program_ids[0], chain_caller_id, + block2_program_ids[0], + chain_caller.id(), "Expected chain_caller program to be deployed in block 2" ); diff --git a/integration_tests/tests/bridge.rs b/integration_tests/tests/bridge.rs index 41781a68..9c2fa8c5 100644 --- a/integration_tests/tests/bridge.rs +++ b/integration_tests/tests/bridge.rs @@ -43,12 +43,12 @@ async fn public_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> { let ctx = TestContext::new().await?; let recipient_id = ctx.existing_public_accounts()[0]; - let bridge_account_id = lee::system_bridge_account_id(); - let vault_program_id = Program::vault().id(); + let bridge_account_id = system_accounts::bridge_account_id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id); let message = public_transaction::Message::try_new( - Program::bridge().id(), + programs::bridge().id(), vec![bridge_account_id, recipient_vault_id], vec![], bridge_core::Instruction::Deposit { @@ -103,8 +103,8 @@ async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> { let ctx = TestContext::new().await?; let recipient_id = ctx.existing_public_accounts()[0]; - let bridge_account_id = lee::system_bridge_account_id(); - let vault_program_id = Program::vault().id(); + let bridge_account_id = system_accounts::bridge_account_id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id); // Get pre-state of bridge and vault accounts @@ -126,12 +126,12 @@ async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> { // Create program with dependencies let program_with_deps = lee::privacy_preserving_transaction::circuit::ProgramWithDependencies::new( - Program::bridge(), + programs::bridge(), [ - (vault_program_id, Program::vault()), + (vault_program_id, programs::vault()), ( - Program::authenticated_transfer_program().id(), - Program::authenticated_transfer_program(), + programs::authenticated_transfer().id(), + programs::authenticated_transfer(), ), ] .into(), @@ -386,7 +386,7 @@ async fn bedrock_deposit_claim_and_withdraw_round_trip_succeeds() -> anyhow::Res let bedrock_account_pk = "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26"; let recipient_id = ctx.existing_public_accounts()[0]; let amount = 1_u64; - let vault_program_id = Program::vault().id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id); let vault_balance_before = ctx @@ -474,7 +474,7 @@ async fn bedrock_deposit_claim_and_withdraw_round_trip_succeeds() -> anyhow::Res // state as the sequencer โ€” including the bridge system account the deposit // modifies, which is the case the hot fix unblocks. wait_for_indexer_to_catch_up(&ctx).await?; - let bridge_account_id = lee::system_bridge_account_id(); + let bridge_account_id = system_accounts::bridge_account_id(); for account_id in [recipient_id, recipient_vault_id, bridge_account_id] { let indexer_account = indexer_service_rpc::RpcClient::get_account( // `deref` is needed for correct trait resolution diff --git a/integration_tests/tests/cross_zone_bridge.rs b/integration_tests/tests/cross_zone_bridge.rs index c0daf9bb..63b14c95 100644 --- a/integration_tests/tests/cross_zone_bridge.rs +++ b/integration_tests/tests/cross_zone_bridge.rs @@ -1,14 +1,13 @@ #![expect( clippy::tests_outside_test_module, - clippy::arithmetic_side_effects, - reason = "We don't care about these in tests" + reason = "top-level test functions are conventional for integration tests" )] //! Demo 2: a wrapped-token bridge over the cross-zone spine. A holder locks part //! of their bridgeable balance on zone A; the watcher carries the emitted mint to //! zone B, where the indexer re-derives and verifies it (Option B) before the //! wrapped token is minted to the recipient. Reuses the M3/M4 spine unchanged; -//! only the source caller (bridge_lock) and target (wrapped_token) are new. +//! only the source caller (`bridge_lock`) and target (`wrapped_token`) are new. use std::{net::SocketAddr, time::Duration}; @@ -22,7 +21,6 @@ use integration_tests::{ }; use lee::{ AccountId, PrivateKey, PublicKey, PublicTransaction, - program::Program, public_transaction::{Message, WitnessSet}, }; use sequencer_core::config::{CrossZoneConfig, CrossZonePeer, GenesisAction}; @@ -49,7 +47,7 @@ async fn lock_on_zone_a_mints_wrapped_token_on_zone_b() -> Result<()> { let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); - let wrapped_token_id = Program::wrapped_token().id(); + let wrapped_token_id = programs::wrapped_token().id(); let cross_zone = CrossZoneConfig { peers: vec![CrossZonePeer { channel_id: *channel_a.as_ref(), @@ -105,7 +103,7 @@ async fn lock_on_zone_a_mints_wrapped_token_on_zone_b() -> Result<()> { // has already landed (it preceded delivery), so zone A reflects the debit and // escrow now. let seq_a_client = sequencer_client(seq_a.addr())?; - let escrow_id = bridge_lock_core::escrow_account_id(Program::bridge_lock().id()); + let escrow_id = bridge_lock_core::escrow_account_id(programs::bridge_lock().id()); let escrowed = bridge_lock_core::read_balance( &seq_a_client.get_account(escrow_id).await?.data.into_inner(), ); @@ -124,16 +122,16 @@ async fn lock_on_zone_a_mints_wrapped_token_on_zone_b() -> Result<()> { Ok(()) } -/// Builds a signed bridge_lock Lock that forwards a wrapped-token Mint of the +/// Builds a signed `bridge_lock` Lock that forwards a wrapped-token Mint of the /// locked amount to the recipient on the target zone. fn build_lock_tx( holder_key: &PrivateKey, holder_id: AccountId, target_zone: [u8; 32], ) -> LeeTransaction { - let bridge_lock_id = Program::bridge_lock().id(); - let wrapped_token_id = Program::wrapped_token().id(); - let outbox_id = Program::cross_zone_outbox().id(); + let bridge_lock_id = programs::bridge_lock().id(); + let wrapped_token_id = programs::wrapped_token().id(); + let outbox_id = programs::cross_zone_outbox().id(); let ordinal = 0; let mint = wrapped_token_core::Instruction::Mint { diff --git a/integration_tests/tests/cross_zone_ingress_guard.rs b/integration_tests/tests/cross_zone_ingress_guard.rs index 89ca0d92..5f401869 100644 --- a/integration_tests/tests/cross_zone_ingress_guard.rs +++ b/integration_tests/tests/cross_zone_ingress_guard.rs @@ -22,7 +22,6 @@ use integration_tests::{ }; use lee::{ PublicTransaction, - program::Program, public_transaction::{Message, WitnessSet}, }; use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder}; @@ -40,13 +39,13 @@ async fn user_origin_inbox_call_rejected() -> Result<()> { .context("Failed to set up sequencer")?; // A user hand-builds a top-level inbox Dispatch and submits it via RPC. - let inbox_id = Program::cross_zone_inbox().id(); + let inbox_id = programs::cross_zone_inbox().id(); let msg = CrossZoneMessage { src_zone: [2; 32], src_block_id: 1, src_tx_index: 0, src_program_id: [9; 8], - target_program_id: Program::ping_receiver().id(), + target_program_id: programs::ping_receiver().id(), payload: vec![], l1_inclusion_witness: None, }; diff --git a/integration_tests/tests/cross_zone_ping.rs b/integration_tests/tests/cross_zone_ping.rs index e551386d..19223b5f 100644 --- a/integration_tests/tests/cross_zone_ping.rs +++ b/integration_tests/tests/cross_zone_ping.rs @@ -1,7 +1,6 @@ #![expect( clippy::tests_outside_test_module, - clippy::arithmetic_side_effects, - reason = "We don't care about these in tests" + reason = "top-level test functions are conventional for integration tests" )] //! End-to-end cross-zone round trip: a ping submitted on zone A is delivered by @@ -21,7 +20,7 @@ use integration_tests::{ config::{self, SequencerPartialConfig}, setup::{setup_bedrock_node, setup_sequencer}, }; -use lee::{AccountId, PublicTransaction, program::Program, public_transaction::Message}; +use lee::{AccountId, PublicTransaction, public_transaction::Message}; use lee_core::program::ProgramId; use ping_core::{ReceiverInstruction, SenderInstruction, ping_record_pda}; use sequencer_core::config::{CrossZoneConfig, CrossZonePeer}; @@ -44,7 +43,7 @@ async fn ping_crosses_from_zone_a_to_zone_b() -> Result<()> { let zone_a: [u8; 32] = *channel_a.as_ref(); let zone_b: [u8; 32] = *channel_b.as_ref(); - let receiver_id = Program::ping_receiver().id(); + let receiver_id = programs::ping_receiver().id(); // Zone B watches zone A and allows delivery only to ping_receiver. let cross_zone = CrossZoneConfig { @@ -81,10 +80,10 @@ async fn ping_crosses_from_zone_a_to_zone_b() -> Result<()> { Ok(()) } -/// Builds a top-level ping_sender transaction that chains into the outbox to emit +/// Builds a top-level `ping_sender` transaction that chains into the outbox to emit /// a message carrying a `ping_receiver::Record` instruction for the target zone. fn build_ping_tx(target_zone: [u8; 32], receiver_id: ProgramId) -> LeeTransaction { - let outbox_id = Program::cross_zone_outbox().id(); + let outbox_id = programs::cross_zone_outbox().id(); let ordinal = 0; // The payload is the ping_receiver instruction, serialized as risc0 words in @@ -106,7 +105,7 @@ fn build_ping_tx(target_zone: [u8; 32], receiver_id: ProgramId) -> LeeTransactio let outbox_account = outbox_pda(outbox_id, &target_zone, ordinal); let message = Message::try_new( - Program::ping_sender().id(), + programs::ping_sender().id(), vec![outbox_account], vec![], send, diff --git a/lee/state_machine/src/cross_zone_bridge_tests.rs b/integration_tests/tests/cross_zone_state_machine.rs similarity index 60% rename from lee/state_machine/src/cross_zone_bridge_tests.rs rename to integration_tests/tests/cross_zone_state_machine.rs index 9312fe89..1b67914a 100644 --- a/lee/state_machine/src/cross_zone_bridge_tests.rs +++ b/integration_tests/tests/cross_zone_state_machine.rs @@ -1,15 +1,14 @@ #![expect( clippy::tests_outside_test_module, - clippy::arithmetic_side_effects, - reason = "We don't care about these in tests" + reason = "top-level test functions are conventional for integration tests" )] -//! Single-zone state-machine tests for the wrapped-token bridge (Demo 2). They -//! drive the two guest hops in isolation, no watcher or Bedrock: the source -//! `bridge_lock::Lock` (which escrows and chains `outbox::Emit`), then a -//! hand-built `cross_zone_inbox::Dispatch` carrying the wrapped-token mint to -//! `wrapped_token::Mint`. Fast, so they pin guest logic before the e2e exercises -//! the plumbing. +//! Single-zone state-machine tests for cross-zone delivery (ping demo) and the +//! wrapped-token bridge (Demo 2). They drive the guests in isolation, no watcher +//! or Bedrock: a hand-built `cross_zone_inbox::Dispatch` (as the watcher would +//! inject) and the source `bridge_lock::Lock` (which escrows and chains +//! `outbox::Emit`). Fast, so they pin guest logic before the e2e exercises the +//! plumbing. Run with `RISC0_DEV_MODE=1`. use std::collections::BTreeMap; @@ -18,19 +17,74 @@ use cross_zone_inbox_core::{ inbox_config_account_id, inbox_seen_shard_account_id, message_key, }; use cross_zone_outbox_core::{OutboxRecord, outbox_pda}; -use lee_core::account::Account; - -use crate::{ - AccountId, PrivateKey, PublicKey, PublicTransaction, V03State, - program::Program, +use lee::{ + AccountId, PrivateKey, PublicKey, PublicTransaction, V03State, ValidatedStateDiff, public_transaction::{Message, WitnessSet}, - validated_state_diff::ValidatedStateDiff, }; +use lee_core::account::Account; +use ping_core::{ReceiverInstruction, ping_record_pda}; const INITIAL_BALANCE: u128 = 100; const LOCK_AMOUNT: u128 = 30; const RECIPIENT: [u8; 32] = [9; 32]; +/// State registering the cross-zone builtins these tests exercise. +fn base_state() -> V03State { + V03State::new().with_programs([ + programs::cross_zone_inbox(), + programs::cross_zone_outbox(), + programs::ping_receiver(), + programs::bridge_lock(), + programs::wrapped_token(), + ]) +} + +/// Seeds an inbox config (inbox-owned) allowing `src_zone -> target`. +fn seed_inbox_config( + state: &mut V03State, + self_zone: [u8; 32], + src_zone: [u8; 32], + target: lee_core::program::ProgramId, +) { + let inbox_id = programs::cross_zone_inbox().id(); + let mut allowed_targets = BTreeMap::new(); + allowed_targets.insert(src_zone, vec![target]); + let config = InboxConfig { + self_zone, + allowed_peers: BTreeMap::new(), + allowed_targets, + }; + state.insert_genesis_account( + inbox_config_account_id(inbox_id), + Account { + program_owner: inbox_id, + balance: 0, + data: config + .to_bytes() + .try_into() + .expect("config fits in account data"), + nonce: 0_u128.into(), + }, + ); +} + +/// Seeds the wrapped-token config account pinning the inbox as authorized minter, +/// matching what genesis seeds for a real zone. +fn seed_wrapped_config(state: &mut V03State) { + let wrapped_token_id = programs::wrapped_token().id(); + state.insert_genesis_account( + wrapped_token_core::config_account_id(wrapped_token_id), + Account { + program_owner: wrapped_token_id, + data: wrapped_token_core::minter_bytes(programs::cross_zone_inbox().id()) + .to_vec() + .try_into() + .expect("minter id fits in account data"), + ..Default::default() + }, + ); +} + /// The wrapped-token `Mint` the bridge forwards, serialized as the cross-zone /// payload (risc0 words, little-endian bytes). fn mint_payload() -> Vec { @@ -42,21 +96,81 @@ fn mint_payload() -> Vec { words.iter().flat_map(|word| word.to_le_bytes()).collect() } +/// Drives `cross_zone_inbox::Dispatch` directly through the state machine +/// (no watcher) and asserts the message is delivered to `ping_receiver`, which +/// records the payload into its own PDA. +#[test] +fn inbox_dispatch_delivers_payload_to_ping_receiver() { + let inbox_id = programs::cross_zone_inbox().id(); + let receiver_id = programs::ping_receiver().id(); + + let self_zone = [1_u8; 32]; + let src_zone = [2_u8; 32]; + let src_block_id = 5; + + let mut state = base_state(); + seed_inbox_config(&mut state, self_zone, src_zone, receiver_id); + + // The payload is the ping_receiver instruction, serialized as risc0 words in + // little-endian bytes (the contract the inbox reverses when forwarding). + let inner = b"hello-cross-zone".to_vec(); + let words = risc0_zkvm::serde::to_vec(&ReceiverInstruction::Record { + payload: inner.clone(), + }) + .expect("serialize ping instruction"); + let payload: Vec = words.iter().flat_map(|word| word.to_le_bytes()).collect(); + + let msg = CrossZoneMessage { + src_zone, + src_block_id, + src_tx_index: 0, + src_program_id: [9_u32; 8], + target_program_id: receiver_id, + payload, + l1_inclusion_witness: None, + }; + + let seen_id = inbox_seen_shard_account_id(inbox_id, &src_zone, src_block_id); + let record_id = ping_record_pda(receiver_id); + + let message = Message::try_new( + inbox_id, + vec![inbox_config_account_id(inbox_id), seen_id, record_id], + vec![], + InboxInstruction::Dispatch(msg), + ) + .expect("build dispatch message"); + let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); + + let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) + .expect("dispatch must validate and execute"); + let record = diff + .public_diff() + .get(&record_id) + .expect("ping record account must change") + .clone(); + assert_eq!( + record.data.into_inner(), + inner, + "ping_receiver must record the delivered payload" + ); +} + /// Drives `bridge_lock::Lock` and asserts it debits the holder, credits the /// escrow, and records the forwarded mint in the outbox PDA. #[test] fn lock_escrows_balance_and_emits_to_outbox() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - let bridge_lock_id = Program::bridge_lock().id(); - let wrapped_token_id = Program::wrapped_token().id(); - let outbox_id = Program::cross_zone_outbox().id(); + let bridge_lock_id = programs::bridge_lock().id(); + let wrapped_token_id = programs::wrapped_token().id(); + let outbox_id = programs::cross_zone_outbox().id(); let zone_b = [2_u8; 32]; let ordinal = 0; + let mut state = base_state(); + let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); - state.force_insert_account( + state.insert_genesis_account( holder_id, Account { program_owner: bridge_lock_id, @@ -127,36 +241,16 @@ fn lock_escrows_balance_and_emits_to_outbox() { /// and asserts it chains into `wrapped_token::Mint`, crediting the recipient. #[test] fn inbox_dispatch_mints_wrapped_token() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - let inbox_id = Program::cross_zone_inbox().id(); - let wrapped_token_id = Program::wrapped_token().id(); + let inbox_id = programs::cross_zone_inbox().id(); + let wrapped_token_id = programs::wrapped_token().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; - // Seed the inbox config allowing src_zone -> wrapped_token. - let mut allowed_targets = BTreeMap::new(); - allowed_targets.insert(src_zone, vec![wrapped_token_id]); - let config = InboxConfig { - self_zone, - allowed_peers: BTreeMap::new(), - allowed_targets, - }; - let config_id = inbox_config_account_id(inbox_id); - state.force_insert_account( - config_id, - Account { - program_owner: inbox_id, - balance: 0, - data: config - .to_bytes() - .try_into() - .expect("config fits in account data"), - nonce: 0_u128.into(), - }, - ); + let mut state = base_state(); + seed_inbox_config(&mut state, self_zone, src_zone, wrapped_token_id); + seed_wrapped_config(&mut state); let msg = CrossZoneMessage { src_zone, @@ -174,7 +268,12 @@ fn inbox_dispatch_mints_wrapped_token() { let message = Message::try_new( inbox_id, - vec![config_id, seen_id, wrapped_config_id, holding_id], + vec![ + inbox_config_account_id(inbox_id), + seen_id, + wrapped_config_id, + holding_id, + ], vec![], InboxInstruction::Dispatch(msg), ) @@ -183,10 +282,9 @@ fn inbox_dispatch_mints_wrapped_token() { let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) .expect("dispatch must validate and execute"); - let public_diff = diff.public_diff(); - - let minted = - wrapped_token_core::read_balance(&public_diff[&holding_id].data.clone().into_inner()); + let minted = wrapped_token_core::read_balance( + &diff.public_diff()[&holding_id].data.clone().into_inner(), + ); assert_eq!( minted, LOCK_AMOUNT, "recipient holding minted the locked amount" @@ -198,36 +296,17 @@ fn inbox_dispatch_mints_wrapped_token() { /// second time. This is the bridge's replay defense. #[test] fn mint_replay_rejected() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - let inbox_id = Program::cross_zone_inbox().id(); - let wrapped_token_id = Program::wrapped_token().id(); + let inbox_id = programs::cross_zone_inbox().id(); + let wrapped_token_id = programs::wrapped_token().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; let src_tx_index = 0; - let mut allowed_targets = BTreeMap::new(); - allowed_targets.insert(src_zone, vec![wrapped_token_id]); - let config = InboxConfig { - self_zone, - allowed_peers: BTreeMap::new(), - allowed_targets, - }; - let config_id = inbox_config_account_id(inbox_id); - state.force_insert_account( - config_id, - Account { - program_owner: inbox_id, - balance: 0, - data: config - .to_bytes() - .try_into() - .expect("config fits in account data"), - nonce: 0_u128.into(), - }, - ); + let mut state = base_state(); + seed_inbox_config(&mut state, self_zone, src_zone, wrapped_token_id); + seed_wrapped_config(&mut state); // Seed the seen-shard as already containing this message's key, so the inbox // takes the replay no-op branch. The shard is inbox-owned (claimed on a prior @@ -235,7 +314,7 @@ fn mint_replay_rejected() { let seen_id = inbox_seen_shard_account_id(inbox_id, &src_zone, src_block_id); let mut shard = SeenShard::default(); shard.insert(message_key(&src_zone, src_block_id, src_tx_index)); - state.force_insert_account( + state.insert_genesis_account( seen_id, Account { program_owner: inbox_id, @@ -263,7 +342,12 @@ fn mint_replay_rejected() { let message = Message::try_new( inbox_id, - vec![config_id, seen_id, wrapped_config_id, holding_id], + vec![ + inbox_config_account_id(inbox_id), + seen_id, + wrapped_config_id, + holding_id, + ], vec![], InboxInstruction::Dispatch(msg), ) diff --git a/integration_tests/tests/cross_zone_verified.rs b/integration_tests/tests/cross_zone_verified.rs index a7e06064..e2f016c0 100644 --- a/integration_tests/tests/cross_zone_verified.rs +++ b/integration_tests/tests/cross_zone_verified.rs @@ -1,7 +1,6 @@ #![expect( clippy::tests_outside_test_module, - clippy::arithmetic_side_effects, - reason = "We don't care about these in tests" + reason = "top-level test functions are conventional for integration tests" )] //! Cross-zone round trip with the indexer in the loop (Option B). A ping on zone @@ -20,7 +19,7 @@ use integration_tests::{ indexer_client::IndexerClient, setup::{setup_bedrock_node, setup_indexer, setup_sequencer}, }; -use lee::{AccountId, PublicTransaction, program::Program, public_transaction::Message}; +use lee::{AccountId, PublicTransaction, public_transaction::Message}; use lee_core::program::ProgramId; use ping_core::{ReceiverInstruction, SenderInstruction, ping_record_pda}; use sequencer_core::config::{CrossZoneConfig, CrossZonePeer}; @@ -43,7 +42,7 @@ async fn indexer_verifies_and_delivers_cross_zone_ping() -> Result<()> { let zone_a: [u8; 32] = *channel_a.as_ref(); let zone_b: [u8; 32] = *channel_b.as_ref(); - let receiver_id = Program::ping_receiver().id(); + let receiver_id = programs::ping_receiver().id(); let cross_zone = CrossZoneConfig { peers: vec![CrossZonePeer { channel_id: zone_a, @@ -98,7 +97,7 @@ async fn indexer_verifies_and_delivers_cross_zone_ping() -> Result<()> { } fn build_ping_tx(target_zone: [u8; 32], receiver_id: ProgramId) -> LeeTransaction { - let outbox_id = Program::cross_zone_outbox().id(); + let outbox_id = programs::cross_zone_outbox().id(); let ordinal = 0; let words = risc0_zkvm::serde::to_vec(&ReceiverInstruction::Record { @@ -118,7 +117,7 @@ fn build_ping_tx(target_zone: [u8; 32], receiver_id: ProgramId) -> LeeTransactio let outbox_account = outbox_pda(outbox_id, &target_zone, ordinal); let message = Message::try_new( - Program::ping_sender().id(), + programs::ping_sender().id(), vec![outbox_account], vec![], send, diff --git a/integration_tests/tests/indexer_ffi_block_batching.rs b/integration_tests/tests/indexer_ffi_block_batching.rs index eeef276d..c244fbb0 100644 --- a/integration_tests/tests/indexer_ffi_block_batching.rs +++ b/integration_tests/tests/indexer_ffi_block_batching.rs @@ -5,8 +5,7 @@ )] use anyhow::Result; -use indexer_ffi::{Runtime, api::types::FfiOption}; -use integration_tests::L2_TO_L1_TIMEOUT; +use indexer_ffi::api::types::FfiOption; use log::info; #[path = "indexer_ffi_helpers/mod.rs"] @@ -14,21 +13,15 @@ mod indexer_ffi_helpers; #[test] fn indexer_ffi_block_batching() -> Result<()> { - let (ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; + // `_ctx` keeps the bedrock/sequencer harness (and its runtime) alive for the + // duration of the test; the indexer was started on that runtime. + let (_ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; - // WAIT + // WAIT: poll until the indexer has finalized at least two blocks (so the + // chain-consistency check below verifies at least one block link), returning + // early instead of sleeping for the full timeout. info!("Waiting for indexer to parse blocks"); - std::thread::sleep(L2_TO_L1_TIMEOUT); - - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let last_block_indexer_ffi_res = unsafe { - indexer_ffi_helpers::query_last_block(&raw const runtime, &raw const indexer_ffi) - }; - - assert!(last_block_indexer_ffi_res.error.is_ok()); - - let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value }; + let last_block_indexer = indexer_ffi_helpers::wait_for_indexer_ffi_block(&indexer_ffi, 2)?; info!("Last block on indexer FFI now is {last_block_indexer}"); @@ -37,14 +30,8 @@ fn indexer_ffi_block_batching() -> Result<()> { let before_ffi = FfiOption::::from_none(); let limit = 100; - let block_batch_ffi_res = unsafe { - indexer_ffi_helpers::query_block_vec( - &raw const runtime, - &raw const indexer_ffi, - before_ffi, - limit, - ) - }; + let block_batch_ffi_res = + unsafe { indexer_ffi_helpers::query_block_vec(&raw const indexer_ffi, before_ffi, limit) }; assert!(block_batch_ffi_res.error.is_ok()); diff --git a/integration_tests/tests/indexer_ffi_helpers/mod.rs b/integration_tests/tests/indexer_ffi_helpers/mod.rs index 51320193..09e0a927 100644 --- a/integration_tests/tests/indexer_ffi_helpers/mod.rs +++ b/integration_tests/tests/indexer_ffi_helpers/mod.rs @@ -13,6 +13,7 @@ use indexer_ffi::{ api::{ PointerResult, lifecycle::InitializedIndexerServiceFFIResult, + query::LastBlockIdResult, types::{FfiAccountId, FfiOption, FfiVec, account::FfiAccount, block::FfiBlock}, }, }; @@ -20,20 +21,15 @@ use integration_tests::{BlockingTestContext, TestContext}; use tempfile::TempDir; unsafe extern "C" { - pub unsafe fn query_last_block( - runtime: *const Runtime, - indexer: *const IndexerServiceFFI, - ) -> PointerResult; + pub unsafe fn query_last_block(indexer: *const IndexerServiceFFI) -> LastBlockIdResult; pub unsafe fn query_block_vec( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, before: FfiOption, limit: u64, ) -> PointerResult, OperationStatus>; pub unsafe fn query_account( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, account_id: FfiAccountId, ) -> PointerResult; @@ -41,14 +37,11 @@ unsafe extern "C" { pub unsafe fn start_indexer( runtime: *const Runtime, config_path: *const c_char, - port: u16, + storage_dir: *const c_char, ) -> InitializedIndexerServiceFFIResult; } -pub fn setup_indexer_ffi( - runtime: &Runtime, - bedrock_addr: SocketAddr, -) -> Result<(IndexerServiceFFI, TempDir)> { +pub fn setup_indexer_ffi(bedrock_addr: SocketAddr) -> Result<(IndexerServiceFFI, TempDir)> { let temp_indexer_dir = tempfile::tempdir().context("Failed to create temp dir for indexer home")?; @@ -59,7 +52,6 @@ pub fn setup_indexer_ffi( let indexer_config = integration_tests::config::indexer_config( bedrock_addr, - temp_indexer_dir.path().to_owned(), integration_tests::config::bedrock_channel_id(), None, ) @@ -71,9 +63,13 @@ pub fn setup_indexer_ffi( file.write_all(&config_json)?; file.flush()?; + let config_path_c = CString::new(config_path.to_str().unwrap())?; + let storage_dir_c = CString::new(temp_indexer_dir.path().to_str().unwrap())?; let res = - // SAFETY: lib function ensures validity of value. - unsafe { start_indexer(std::ptr::from_ref(runtime), CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) }; + // SAFETY: null runtime โ†’ the FFI creates and owns its own tokio runtime, + // so there is no external runtime whose address we must keep stable. The + // temp dir is the indexer's storage location. + unsafe { start_indexer(std::ptr::null(), config_path_c.as_ptr(), storage_dir_c.as_ptr()) }; if res.error.is_error() { anyhow::bail!("Indexer FFI error {:?}", res.error); @@ -88,8 +84,35 @@ pub fn setup_indexer_ffi( pub fn setup() -> Result<(BlockingTestContext, IndexerServiceFFI, TempDir)> { let ctx = TestContext::builder().disable_indexer().build_blocking()?; - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let (indexer_ffi, indexer_dir) = setup_indexer_ffi(&runtime, ctx.ctx().bedrock_addr())?; + // Don't borrow `ctx.runtime()`: `ctx` (and its by-value tokio runtime) is + // moved into the returned tuple, which would leave any pointer into it + // dangling. Pass a null runtime so the FFI owns its own โ€” the same path the + // production module uses. + let (indexer_ffi, indexer_dir) = setup_indexer_ffi(ctx.ctx().bedrock_addr())?; Ok((ctx, indexer_ffi, indexer_dir)) } + +/// Poll the indexer FFI until its last finalized block id reaches `min_block_id` +/// or until [`integration_tests::L2_TO_L1_TIMEOUT`] elapses. +/// +/// This avoids blindly sleeping for the full timeout: the indexer typically +/// catches up in a fraction of that time, so we return as soon as it does and +/// only use the timeout as a ceiling. Returns the last observed block id. +pub fn wait_for_indexer_ffi_block(indexer: &IndexerServiceFFI, min_block_id: u64) -> Result { + let start = std::time::Instant::now(); + loop { + // SAFETY: `indexer` is a valid reference for the duration of the call. + let res = unsafe { query_last_block(std::ptr::from_ref(indexer)) }; + if res.error.is_ok() && res.is_some && res.block_id >= min_block_id { + return Ok(res.block_id); + } + if start.elapsed() >= integration_tests::L2_TO_L1_TIMEOUT { + anyhow::bail!( + "Indexer FFI did not reach block {min_block_id} within {:?}. Last observed block id: {}", + integration_tests::L2_TO_L1_TIMEOUT, + res.block_id + ); + } + std::thread::sleep(std::time::Duration::from_secs(2)); + } +} diff --git a/integration_tests/tests/indexer_ffi_state_consistency.rs b/integration_tests/tests/indexer_ffi_state_consistency.rs index f84a3790..0a41c68c 100644 --- a/integration_tests/tests/indexer_ffi_state_consistency.rs +++ b/integration_tests/tests/indexer_ffi_state_consistency.rs @@ -8,7 +8,6 @@ use std::time::Duration; use anyhow::{Context as _, Result}; -use indexer_ffi::Runtime; use indexer_service_protocol::Account; use integration_tests::{ L2_TO_L1_TIMEOUT, TIME_TO_WAIT_FOR_BLOCK_SECONDS, private_mention, public_mention, @@ -102,11 +101,8 @@ fn indexer_ffi_state_consistency() -> Result<()> { info!("Waiting for indexer to parse blocks"); std::thread::sleep(L2_TO_L1_TIMEOUT); - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; let acc1_ind_state_ffi = unsafe { indexer_ffi_helpers::query_account( - &raw const runtime, &raw const indexer_ffi, (&ctx.ctx().existing_public_accounts()[0]).into(), ) @@ -119,7 +115,6 @@ fn indexer_ffi_state_consistency() -> Result<()> { let acc2_ind_state_ffi = unsafe { indexer_ffi_helpers::query_account( - &raw const runtime, &raw const indexer_ffi, (&ctx.ctx().existing_public_accounts()[1]).into(), ) diff --git a/integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs b/integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs index 34d5a4d7..fbc0b422 100644 --- a/integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs +++ b/integration_tests/tests/indexer_ffi_state_consistency_with_labels.rs @@ -8,7 +8,6 @@ use std::time::Duration; use anyhow::Result; -use indexer_ffi::Runtime; use indexer_service_protocol::Account; use integration_tests::{L2_TO_L1_TIMEOUT, TIME_TO_WAIT_FOR_BLOCK_SECONDS, public_mention}; use log::info; @@ -75,11 +74,8 @@ fn indexer_ffi_state_consistency_with_labels() -> Result<()> { info!("Waiting for indexer to parse blocks"); std::thread::sleep(L2_TO_L1_TIMEOUT); - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; let acc1_ind_state_ffi = unsafe { indexer_ffi_helpers::query_account( - &raw const runtime, &raw const indexer_ffi, (&ctx.ctx().existing_public_accounts()[0]).into(), ) diff --git a/integration_tests/tests/indexer_test_run_ffi.rs b/integration_tests/tests/indexer_test_run_ffi.rs index 2c7c2103..e37b619e 100644 --- a/integration_tests/tests/indexer_test_run_ffi.rs +++ b/integration_tests/tests/indexer_test_run_ffi.rs @@ -1,12 +1,9 @@ #![expect( clippy::tests_outside_test_module, - clippy::undocumented_unsafe_blocks, reason = "We don't care about these in tests" )] use anyhow::Result; -use indexer_ffi::Runtime; -use integration_tests::L2_TO_L1_TIMEOUT; use log::info; #[path = "indexer_ffi_helpers/mod.rs"] @@ -14,20 +11,13 @@ mod indexer_ffi_helpers; #[test] fn indexer_test_run_ffi() -> Result<()> { - let (ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; + // `_ctx` keeps the bedrock/sequencer harness (and its runtime) alive for the + // duration of the test; the indexer was started on that runtime. + let (_ctx, indexer_ffi, _indexer_dir) = indexer_ffi_helpers::setup()?; - // RUN OBSERVATION - std::thread::sleep(L2_TO_L1_TIMEOUT); - - // Safety: ctx runtime is valid for the lifetime of the returned Runtime - let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) }; - let last_block_indexer_ffi_res = unsafe { - indexer_ffi_helpers::query_last_block(&raw const runtime, &raw const indexer_ffi) - }; - - assert!(last_block_indexer_ffi_res.error.is_ok()); - - let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value }; + // RUN OBSERVATION: poll until the indexer has finalized at least one block, + // returning early instead of sleeping for the full timeout. + let last_block_indexer_ffi = indexer_ffi_helpers::wait_for_indexer_ffi_block(&indexer_ffi, 1)?; info!("Last block on indexer FFI now is {last_block_indexer_ffi}"); diff --git a/integration_tests/tests/keys.rs b/integration_tests/tests/keys.rs index 01af23cd..9fd3b3f1 100644 --- a/integration_tests/tests/keys.rs +++ b/integration_tests/tests/keys.rs @@ -12,7 +12,7 @@ use integration_tests::{ public_mention, verify_commitment_is_in_state, }; use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use lee::{AccountId, program::Program}; +use lee::AccountId; use log::info; use sequencer_service_rpc::RpcClient as _; use tokio::test; @@ -257,11 +257,11 @@ async fn restore_keys_from_seed() -> Result<()> { assert_eq!( acc1.account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); assert_eq!( acc2.account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); assert_eq!(acc1.account.balance, 100); diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs index 9beb5b1f..fa4c3d98 100644 --- a/integration_tests/tests/pinata.rs +++ b/integration_tests/tests/pinata.rs @@ -7,7 +7,6 @@ use std::time::Duration; use anyhow::{Context as _, Result}; -use common::PINATA_BASE58; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, private_mention, public_mention, verify_commitment_is_in_state, @@ -44,7 +43,7 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()> let pinata_balance_pre = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; let claim_result = wallet::cli::execute_subcommand( @@ -67,7 +66,7 @@ async fn claim_pinata_to_uninitialized_public_account_fails_fast() -> Result<()> let pinata_balance_post = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; assert_eq!(pinata_balance_post, pinata_balance_pre); @@ -96,7 +95,7 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<() let pinata_balance_pre = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; let claim_result = wallet::cli::execute_subcommand( @@ -119,7 +118,7 @@ async fn claim_pinata_to_uninitialized_private_account_fails_fast() -> Result<() let pinata_balance_post = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; assert_eq!(pinata_balance_post, pinata_balance_pre); @@ -138,7 +137,7 @@ async fn claim_pinata_to_existing_public_account() -> Result<()> { let pinata_balance_pre = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; @@ -149,7 +148,7 @@ async fn claim_pinata_to_existing_public_account() -> Result<()> { info!("Checking correct balance move"); let pinata_balance_post = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; let winner_balance_post = ctx @@ -176,7 +175,7 @@ async fn claim_pinata_to_existing_private_account() -> Result<()> { let pinata_balance_pre = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; @@ -199,7 +198,7 @@ async fn claim_pinata_to_existing_private_account() -> Result<()> { let pinata_balance_post = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); @@ -253,7 +252,7 @@ async fn claim_pinata_to_new_private_account() -> Result<()> { let pinata_balance_pre = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; @@ -269,7 +268,7 @@ async fn claim_pinata_to_new_private_account() -> Result<()> { let pinata_balance_post = ctx .sequencer_client() - .get_account_balance(PINATA_BASE58.parse().unwrap()) + .get_account_balance(system_accounts::pinata_account_id()) .await?; assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs index f96faa52..f3136717 100644 --- a/integration_tests/tests/private_pda.rs +++ b/integration_tests/tests/private_pda.rs @@ -3,14 +3,13 @@ reason = "We don't care about these in tests" )] -use std::{path::PathBuf, time::Duration}; +use std::time::Duration; use anyhow::{Context as _, Result}; use authenticated_transfer_core::Instruction as AuthTransferInstruction; use common::transaction::LeeTransaction; use integration_tests::{ - LEE_PROGRAM_FOR_TEST_PDA_SPEND_PROXY, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, - verify_commitment_is_in_state, + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, verify_commitment_is_in_state, }; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; use lee::{ @@ -165,14 +164,8 @@ async fn private_pda_family_members_receive_and_spend() -> Result<()> { (kc.nullifier_public_key, kc.viewing_public_key.clone()) }; - let proxy = { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../artifacts/test_program_methods") - .join(LEE_PROGRAM_FOR_TEST_PDA_SPEND_PROXY); - Program::new(std::fs::read(&path).with_context(|| format!("reading {path:?}"))?) - .context("invalid pda_spend_proxy binary")? - }; - let auth_transfer = Program::authenticated_transfer_program(); + let proxy = test_programs::pda_spend_proxy(); + let auth_transfer = programs::authenticated_transfer(); let proxy_id = proxy.id(); let auth_transfer_id = auth_transfer.id(); let seed = PdaSeed::new([42; 32]); diff --git a/integration_tests/tests/program_deployment.rs b/integration_tests/tests/program_deployment.rs index 77ac1fb9..ec01c3c8 100644 --- a/integration_tests/tests/program_deployment.rs +++ b/integration_tests/tests/program_deployment.rs @@ -3,14 +3,11 @@ reason = "We don't care about these in tests" )] -use std::{path::PathBuf, time::Duration}; +use std::{io::Write as _, time::Duration}; use anyhow::Result; use common::transaction::LeeTransaction; -use integration_tests::{ - LEE_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, -}; -use lee::program::Program; +use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext}; use log::info; use sequencer_service_rpc::RpcClient as _; use tokio::test; @@ -23,10 +20,11 @@ use wallet::cli::{ async fn deploy_and_execute_program() -> Result<()> { let mut ctx = TestContext::new().await?; - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let binary_filepath: PathBuf = PathBuf::from(manifest_dir) - .join("../artifacts/test_program_methods") - .join(LEE_PROGRAM_FOR_TEST_DATA_CHANGER); + let claimer = test_programs::claimer(); + let mut tempfile = tempfile::NamedTempFile::new()?; + tempfile.write_all(claimer.elf())?; + + let binary_filepath = tempfile.path().to_owned(); let command = Command::DeployProgram { binary_filepath: binary_filepath.clone(), @@ -37,13 +35,6 @@ async fn deploy_and_execute_program() -> Result<()> { info!("Waiting for next block creation"); tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - // The program is the data changer and takes one account as input. - // We pass an uninitialized account and we expect after execution to be owned by the data - // changer program (LEE account claiming mechanism) with data equal to [0] (due to program - // logic) - let bytecode = std::fs::read(binary_filepath)?; - let data_changer = Program::new(bytecode)?; - let SubcommandReturnValue::RegisterAccount { account_id } = wallet::cli::execute_subcommand( ctx.wallet_mut(), Command::Account(AccountSubcommand::New(NewSubcommand::Public { @@ -61,12 +52,8 @@ async fn deploy_and_execute_program() -> Result<()> { .wallet() .get_account_public_signing_key(account_id) .unwrap(); - let message = lee::public_transaction::Message::try_new( - data_changer.id(), - vec![account_id], - nonces, - vec![0], - )?; + let message = + lee::public_transaction::Message::try_new(claimer.id(), vec![account_id], nonces, ())?; let witness_set = lee::public_transaction::WitnessSet::for_message(&message, &[private_key]); let transaction = lee::PublicTransaction::new(message, witness_set); let _response = ctx @@ -81,9 +68,10 @@ async fn deploy_and_execute_program() -> Result<()> { let post_state_account = ctx.sequencer_client().get_account(account_id).await?; - assert_eq!(post_state_account.program_owner, data_changer.id()); + let expected_data: &[u8] = &[]; + assert_eq!(post_state_account.program_owner, claimer.id()); assert_eq!(post_state_account.balance, 0); - assert_eq!(post_state_account.data.as_ref(), &[0]); + assert_eq!(post_state_account.data.as_ref(), expected_data); assert_eq!(post_state_account.nonce.0, 1); info!("Successfully deployed and executed program"); diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs index b0c569e8..60bd3de8 100644 --- a/integration_tests/tests/token.rs +++ b/integration_tests/tests/token.rs @@ -12,7 +12,6 @@ use integration_tests::{ verify_commitment_is_in_state, }; use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use lee::program::Program; use log::info; use sequencer_service_rpc::RpcClient as _; use token_core::{TokenDefinition, TokenHolding}; @@ -99,7 +98,7 @@ async fn create_and_transfer_public_token() -> Result<()> { .await?; let token_definition = TokenDefinition::try_from(&definition_acc.data)?; - assert_eq!(definition_acc.program_owner, Program::token().id()); + assert_eq!(definition_acc.program_owner, programs::token().id()); assert_eq!( token_definition, TokenDefinition::Fungible { @@ -116,7 +115,7 @@ async fn create_and_transfer_public_token() -> Result<()> { .await?; // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.program_owner, programs::token().id()); let token_holding = TokenHolding::try_from(&supply_acc.data)?; assert_eq!( token_holding, @@ -148,7 +147,7 @@ async fn create_and_transfer_public_token() -> Result<()> { .sequencer_client() .get_account(supply_account_id) .await?; - assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.program_owner, programs::token().id()); let token_holding = TokenHolding::try_from(&supply_acc.data)?; assert_eq!( token_holding, @@ -163,7 +162,7 @@ async fn create_and_transfer_public_token() -> Result<()> { .sequencer_client() .get_account(recipient_account_id) .await?; - assert_eq!(recipient_acc.program_owner, Program::token().id()); + assert_eq!(recipient_acc.program_owner, programs::token().id()); let token_holding = TokenHolding::try_from(&recipient_acc.data)?; assert_eq!( token_holding, @@ -344,7 +343,7 @@ async fn create_and_transfer_token_with_private_supply() -> Result<()> { .await?; let token_definition = TokenDefinition::try_from(&definition_acc.data)?; - assert_eq!(definition_acc.program_owner, Program::token().id()); + assert_eq!(definition_acc.program_owner, programs::token().id()); assert_eq!( token_definition, TokenDefinition::Fungible { @@ -508,7 +507,7 @@ async fn create_token_with_private_definition() -> Result<()> { .get_account(supply_account_id) .await?; - assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.program_owner, programs::token().id()); let token_holding = TokenHolding::try_from(&supply_acc.data)?; assert_eq!( token_holding, @@ -1231,7 +1230,7 @@ async fn create_token_using_labels() -> Result<()> { .await?; let token_definition = TokenDefinition::try_from(&definition_acc.data)?; - assert_eq!(definition_acc.program_owner, Program::token().id()); + assert_eq!(definition_acc.program_owner, programs::token().id()); assert_eq!( token_definition, TokenDefinition::Fungible { diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 459f3d61..a11668a8 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -76,7 +76,7 @@ impl TpsTestManager { &self, sequencer_client: &sequencer_service_rpc::SequencerClient, ) -> Result<()> { - let vault_program_id = Program::vault().id(); + let vault_program_id = programs::vault().id(); let mut tx_hashes = Vec::with_capacity(self.public_keypairs.len()); for (private_key, account_id) in &self.public_keypairs { @@ -126,7 +126,7 @@ impl TpsTestManager { /// Must be called after `claim_vault_funds`, which sets each account's nonce to 1. pub fn build_public_txs(&self) -> Vec { // Create valid public transactions - let program = Program::authenticated_transfer_program(); + let program = programs::authenticated_transfer(); let public_txs: Vec = self .public_keypairs .windows(2) @@ -254,7 +254,7 @@ pub async fn tps_test() -> Result<()> { /// multiple times with the purpose of testing the node's processing performance. #[expect(dead_code, reason = "No idea if we need this, should we remove it?")] fn build_privacy_transaction() -> PrivacyPreservingTransaction { - let program = Program::authenticated_transfer_program(); + let program = programs::authenticated_transfer(); let sender_nsk = [1; 32]; let sender_vpk = ViewingPublicKey::from_seed(&[99_u8; 32], &[100_u8; 32]); let sender_npk = NullifierPublicKey::from(&sender_nsk); diff --git a/integration_tests/tests/two_zone.rs b/integration_tests/tests/two_zone.rs index 73c176b7..c895acd1 100644 --- a/integration_tests/tests/two_zone.rs +++ b/integration_tests/tests/two_zone.rs @@ -1,7 +1,6 @@ #![expect( clippy::tests_outside_test_module, - clippy::arithmetic_side_effects, - reason = "We don't care about these in tests" + reason = "top-level test functions are conventional for integration tests" )] //! Two zones (sequencer + indexer each, on separate channels) sharing one diff --git a/integration_tests/tests/vault.rs b/integration_tests/tests/vault.rs index 33d2e9a9..e9ea2075 100644 --- a/integration_tests/tests/vault.rs +++ b/integration_tests/tests/vault.rs @@ -5,7 +5,6 @@ use anyhow::{Context as _, Result}; use integration_tests::{TestContext, private_mention, public_mention}; -use lee::program::Program; use sequencer_service_rpc::RpcClient as _; use tokio::test; use wallet::cli::{Command, SubcommandReturnValue, programs::vault::VaultSubcommand}; @@ -18,7 +17,7 @@ async fn public_transfer_and_public_claim() -> Result<()> { let sender = ctx.existing_public_accounts()[0]; let recipient = ctx.existing_public_accounts()[1]; - let vault_program_id = Program::vault().id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient); let sender_balance_before = ctx.sequencer_client().get_account_balance(sender).await?; @@ -109,7 +108,7 @@ async fn private_transfer_and_private_claim() -> Result<()> { let sender = ctx.existing_private_accounts()[0]; let owner = ctx.existing_private_accounts()[1]; - let vault_program_id = Program::vault().id(); + let vault_program_id = programs::vault().id(); let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner); let sender_balance_before = ctx diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 24f2a9c8..0ef53592 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -28,11 +28,12 @@ use lee::{ use lee_core::program::DEFAULT_PROGRAM_ID; use log::info; use tempfile::tempdir; -use wallet::account::HumanReadableAccount; +use wallet::{account::HumanReadableAccount, program_facades::vault::Vault}; use wallet_ffi::{ FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, - FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, + FfiProgramId, FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult}, + wallet::FfiCreateWalletOutput, }; unsafe extern "C" { @@ -40,7 +41,7 @@ unsafe extern "C" { config_path: *const c_char, storage_path: *const c_char, password: *const c_char, - ) -> *mut WalletHandle; + ) -> FfiCreateWalletOutput; fn wallet_ffi_open( config_path: *const c_char, @@ -165,6 +166,34 @@ unsafe extern "C" { fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult); + fn wallet_ffi_bridge_withdraw( + handle: *mut WalletHandle, + from: *const FfiBytes32, + amount: u64, + bedrock_account_pk: *const FfiBytes32, + out_result: *mut FfiTransferResult, + ) -> error::WalletFfiError; + + fn wallet_ffi_get_vault_balance( + handle: *mut WalletHandle, + owner: *const FfiBytes32, + out_balance: *mut [u8; 16], + ) -> error::WalletFfiError; + + fn wallet_ffi_vault_claim( + handle: *mut WalletHandle, + owner: *const FfiBytes32, + amount: *const [u8; 16], + out_result: *mut FfiTransferResult, + ) -> error::WalletFfiError; + + fn wallet_ffi_vault_claim_private( + handle: *mut WalletHandle, + owner: *const FfiBytes32, + amount: *const [u8; 16], + out_result: *mut FfiTransferResult, + ) -> error::WalletFfiError; + fn wallet_ffi_register_public_account( handle: *mut WalletHandle, account_id: *const FfiBytes32, @@ -186,6 +215,13 @@ unsafe extern "C" { out_block_height: *mut u64, ) -> error::WalletFfiError; + fn wallet_ffi_restore_data( + handle: *mut WalletHandle, + mnemonic: *const c_char, + password: *const c_char, + depth: u32, + ) -> error::WalletFfiError; + fn wallet_ffi_resolve_public_account( account_id: FfiBytes32, needs_sign: bool, @@ -198,7 +234,7 @@ unsafe extern "C" { account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: *const FfiProgramWithDependencies, + program_id: FfiProgramId, out_result: *mut FfiTransactionResult, ) -> error::WalletFfiError; @@ -226,7 +262,7 @@ unsafe extern "C" { fn new_wallet_ffi_with_test_context_config( ctx: &BlockingTestContext, home: &Path, -) -> Result<*mut WalletHandle> { +) -> Result { let config_path = home.join("wallet_config.json"); let storage_path = home.join("storage.json"); let mut config = ctx.ctx().wallet().config().to_owned(); @@ -247,7 +283,7 @@ fn new_wallet_ffi_with_test_context_config( let storage_path = CString::new(storage_path.to_str().unwrap())?; let password = CString::new(ctx.ctx().wallet_password())?; - let wallet_ffi_handle = unsafe { + let create_wallet_result = unsafe { wallet_ffi_create_new( config_path.as_ptr(), storage_path.as_ptr(), @@ -265,8 +301,10 @@ fn new_wallet_ffi_with_test_context_config( .unwrap() .to_string(); let private_key_hex = CString::new(private_key_hex)?; - unsafe { wallet_ffi_import_public_account(wallet_ffi_handle, private_key_hex.as_ptr()) } - .unwrap(); + unsafe { + wallet_ffi_import_public_account(create_wallet_result.wallet, private_key_hex.as_ptr()) + } + .unwrap(); } for (account_id, _chain_index) in source_key_chain.private_account_ids() { @@ -289,7 +327,7 @@ fn new_wallet_ffi_with_test_context_config( unsafe { wallet_ffi_import_private_account( - wallet_ffi_handle, + create_wallet_result.wallet, key_chain_json.as_ptr(), chain_index_ptr, &raw const identifier, @@ -299,10 +337,10 @@ fn new_wallet_ffi_with_test_context_config( .unwrap(); } - Ok(wallet_ffi_handle) + Ok(create_wallet_result) } -fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandle> { +fn new_wallet_ffi_with_default_config(password: &str) -> Result { let tempdir = tempdir()?; let config_path = tempdir.path().join("wallet_config.json"); let storage_path = tempdir.path().join("storage.json"); @@ -310,13 +348,15 @@ fn new_wallet_ffi_with_default_config(password: &str) -> Result<*mut WalletHandl let storage_path_c = CString::new(storage_path.to_str().unwrap())?; let password = CString::new(password)?; - Ok(unsafe { + let create_wallet_result = unsafe { wallet_ffi_create_new( config_path_c.as_ptr(), storage_path_c.as_ptr(), password.as_ptr(), ) - }) + }; + + Ok(create_wallet_result) } fn load_existing_ffi_wallet(home: &Path) -> Result<*mut WalletHandle> { @@ -337,7 +377,10 @@ fn wallet_ffi_create_public_accounts() -> Result<()> { let new_public_account_ids_ffi = unsafe { let mut account_ids = Vec::new(); - let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_default_config(password)?; for _ in 0..n_accounts { let mut out_account_id = FfiBytes32::from_bytes([0; 32]); wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id).unwrap(); @@ -373,7 +416,10 @@ fn wallet_ffi_create_private_accounts() -> Result<()> { let new_npks_ffi = unsafe { let mut npks = Vec::new(); - let wallet_ffi_handle = new_wallet_ffi_with_default_config(password)?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_default_config(password)?; for _ in 0..n_accounts { let mut out_keys = FfiPrivateAccountKeys::default(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); @@ -402,7 +448,10 @@ fn wallet_ffi_save_and_load_persistent_storage() -> Result<()> { let home = tempfile::tempdir()?; // Create a receiving key and save let first_npk = unsafe { - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let mut out_keys = FfiPrivateAccountKeys::default(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); let npk = out_keys.nullifier_public_key.data; @@ -439,7 +488,10 @@ fn test_wallet_ffi_list_accounts() -> Result<()> { // Create the wallet FFI and track which account IDs were created as public/private let (wallet_ffi_handle, created_public_ids) = unsafe { - let handle = new_wallet_ffi_with_default_config(password)?; + let FfiCreateWalletOutput { + wallet: handle, + mnemonic: _, + } = new_wallet_ffi_with_default_config(password)?; let mut public_ids: Vec<[u8; 32]> = Vec::new(); // Create 5 public accounts and 5 receiving keys @@ -504,7 +556,10 @@ fn test_wallet_ffi_get_balance_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let balance = unsafe { let mut out_balance: [u8; 16] = [0; 16]; @@ -534,7 +589,10 @@ fn test_wallet_ffi_get_account_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let mut out_account = FfiAccount::default(); let account: Account = unsafe { @@ -550,7 +608,7 @@ fn test_wallet_ffi_get_account_public() -> Result<()> { assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); assert_eq!(account.balance, 10000); assert!(account.data.is_empty()); @@ -571,7 +629,10 @@ fn test_wallet_ffi_get_account_private() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let mut out_account = FfiAccount::default(); let account: Account = unsafe { @@ -587,7 +648,7 @@ fn test_wallet_ffi_get_account_private() -> Result<()> { assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); assert_eq!(account.balance, 10000); assert!(account.data.is_empty()); @@ -607,7 +668,10 @@ fn test_wallet_ffi_get_public_account_keys() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_public_accounts()[0]; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let mut out_key = FfiPublicAccountKey::default(); let key: PublicKey = unsafe { @@ -646,7 +710,10 @@ fn test_wallet_ffi_get_private_account_keys() -> Result<()> { let ctx = BlockingTestContext::new()?; let account_id: AccountId = ctx.ctx().existing_private_accounts()[0]; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let mut keys = FfiPrivateAccountKeys::default(); unsafe { @@ -728,7 +795,10 @@ fn wallet_ffi_base58_to_account_id() -> Result<()> { fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; // Create a new uninitialized public account let mut out_account_id = FfiBytes32::from_bytes([0; 32]); @@ -776,7 +846,7 @@ fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { }; assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); unsafe { @@ -791,7 +861,10 @@ fn wallet_ffi_init_public_account_auth_transfer() -> Result<()> { fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; // Create a new private account let mut out_account_id = FfiBytes32::default(); @@ -833,7 +906,7 @@ fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { }; assert_eq!( account.program_owner, - Program::authenticated_transfer_program().id() + programs::authenticated_transfer().id() ); unsafe { @@ -848,7 +921,10 @@ fn wallet_ffi_init_private_account_auth_transfer() -> Result<()> { fn test_wallet_ffi_transfer_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into(); let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[1].into(); let amount: [u8; 16] = 100_u128.to_le_bytes(); @@ -902,7 +978,10 @@ fn test_wallet_ffi_transfer_public() -> Result<()> { fn test_wallet_ffi_transfer_shielded() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into(); let (to, to_keys) = unsafe { let mut out_keys = FfiPrivateAccountKeys::default(); @@ -978,7 +1057,10 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> { fn test_wallet_ffi_transfer_deshielded() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into(); let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into(); let amount: [u8; 16] = 100_u128.to_le_bytes(); @@ -1038,7 +1120,10 @@ fn test_wallet_ffi_transfer_deshielded() -> Result<()> { fn test_wallet_ffi_transfer_private() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into(); let (to, to_keys) = unsafe { @@ -1110,11 +1195,366 @@ fn test_wallet_ffi_transfer_private() -> Result<()> { Ok(()) } +#[test] +fn restore_keys_from_seed_ffi() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + + let mnemonic = unsafe { CString::from_raw(mnemonic) }; + + // Create 2 new private accounts + let (private_account_id_1, private_account_1_keys) = unsafe { + let mut out_keys = FfiPrivateAccountKeys::default(); + wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); + let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); + let to: FfiBytes32 = account_id.into(); + (to, out_keys) + }; + + let (private_account_id_2, private_account_2_keys) = unsafe { + let mut out_keys = FfiPrivateAccountKeys::default(); + wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys).unwrap(); + let account_id = lee::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); + let to: FfiBytes32 = account_id.into(); + (to, out_keys) + }; + + // Create 2 new public accounts + let mut public_account_id_1 = FfiBytes32::default(); + unsafe { + wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut public_account_id_1).unwrap(); + } + + let mut public_account_id_2 = FfiBytes32::default(); + unsafe { + wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut public_account_id_2).unwrap(); + } + + info!("Accounts created"); + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + // Send funds to accounts + let from_private: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into(); + let from_public: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into(); + + let amount_1: [u8; 16] = 100_u128.to_le_bytes(); + + let mut transfer_result_1 = FfiTransferResult::default(); + unsafe { + let to_identifier = FfiU128 { + data: 0_u128.to_le_bytes(), + }; + wallet_ffi_transfer_private( + wallet_ffi_handle, + &raw const from_private, + &raw const private_account_1_keys, + &raw const to_identifier, + &raw const amount_1, + &raw mut transfer_result_1, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + let amount_2: [u8; 16] = 101_u128.to_le_bytes(); + + let mut transfer_result_2 = FfiTransferResult::default(); + unsafe { + let to_identifier = FfiU128 { + data: 0_u128.to_le_bytes(), + }; + wallet_ffi_transfer_private( + wallet_ffi_handle, + &raw const from_private, + &raw const private_account_2_keys, + &raw const to_identifier, + &raw const amount_2, + &raw mut transfer_result_2, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + let amount_3: [u8; 16] = 102_u128.to_le_bytes(); + + let mut transfer_result_3 = FfiTransferResult::default(); + unsafe { + wallet_ffi_transfer_public( + wallet_ffi_handle, + &raw const from_public, + &raw const public_account_id_1, + &raw const amount_3, + &raw mut transfer_result_3, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + let amount_4: [u8; 16] = 103_u128.to_le_bytes(); + + let mut transfer_result_4 = FfiTransferResult::default(); + unsafe { + wallet_ffi_transfer_public( + wallet_ffi_handle, + &raw const from_public, + &raw const public_account_id_2, + &raw const amount_4, + &raw mut transfer_result_4, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + unsafe { + wallet_ffi_free_transfer_result(&raw mut transfer_result_1); + wallet_ffi_free_transfer_result(&raw mut transfer_result_2); + wallet_ffi_free_transfer_result(&raw mut transfer_result_3); + wallet_ffi_free_transfer_result(&raw mut transfer_result_4); + } + + info!("Preparation complete, performing keys restoration"); + + let password = CString::new(ctx.ctx().wallet_password())?; + + info!("Checking balance correctness before restoration"); + + let private_account_id_1_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const private_account_id_1, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let private_account_id_2_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const private_account_id_2, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let public_account_id_1_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const public_account_id_1, + true, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let public_account_id_2_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const public_account_id_2, + true, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + assert_eq!(private_account_id_1_balance, 100); + assert_eq!(private_account_id_2_balance, 101); + assert_eq!(public_account_id_1_balance, 102); + assert_eq!(public_account_id_2_balance, 103); + + unsafe { + wallet_ffi_restore_data(wallet_ffi_handle, mnemonic.as_ptr(), password.as_ptr(), 5) + .unwrap(); + } + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + info!("Checking balance correctness after restoration"); + + let private_account_id_1_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const private_account_id_1, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let private_account_id_2_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const private_account_id_2, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let public_account_id_1_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const public_account_id_1, + true, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + let public_account_id_2_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const public_account_id_2, + true, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + + assert_eq!(private_account_id_1_balance, 100); + assert_eq!(private_account_id_2_balance, 101); + assert_eq!(public_account_id_1_balance, 102); + assert_eq!(public_account_id_2_balance, 103); + + info!("Accounts restored"); + + Ok(()) +} + +#[test] +fn test_wallet_ffi_bridge_withdraw() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into(); + let bridge_account: FfiBytes32 = system_accounts::bridge_account_id().into(); + let bedrock_account_pk = FfiBytes32::from_bytes([0x42; 32]); + let amount = 100_u64; + + let mut transfer_result = FfiTransferResult::default(); + unsafe { + wallet_ffi_bridge_withdraw( + wallet_ffi_handle, + &raw const from, + amount, + &raw const bedrock_account_pk, + &raw mut transfer_result, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let from_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const from, + true, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + + let bridge_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const bridge_account, + true, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + + assert_eq!(from_balance, 9900); + assert_eq!(bridge_balance, 1_000_100); + + unsafe { + wallet_ffi_free_transfer_result(&raw mut transfer_result); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} + #[test] fn test_wallet_ffi_transfer_generic_public() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = ctx.ctx().existing_public_accounts()[0].into(); let to: FfiBytes32 = ctx.ctx().existing_public_accounts()[1].into(); let amount = 100_u128; @@ -1145,8 +1585,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { let instruction_words_size = instruction_data.len(); let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32; - let program: ProgramWithDependencies = Program::authenticated_transfer_program().into(); - let program_with_dependencies: FfiProgramWithDependencies = program.into(); + let program_id = programs::authenticated_transfer().id(); unsafe { wallet_ffi_send_generic_public_transaction( @@ -1155,7 +1594,7 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { account_identities_size, instruction_words, instruction_words_size, - &raw const program_with_dependencies, + program_id.into(), &raw mut transaction_result, ) .unwrap(); @@ -1206,7 +1645,10 @@ fn test_wallet_ffi_transfer_generic_public() -> Result<()> { fn test_wallet_ffi_transfer_generic_private() -> Result<()> { let ctx = BlockingTestContext::new()?; let home = tempfile::tempdir()?; - let wallet_ffi_handle = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; let from: FfiBytes32 = ctx.ctx().existing_private_accounts()[0].into(); let to: FfiBytes32 = ctx.ctx().existing_private_accounts()[1].into(); let amount = 100_u128; @@ -1239,7 +1681,7 @@ fn test_wallet_ffi_transfer_generic_private() -> Result<()> { let instruction_words_size = instruction_data.len(); let instruction_words = Box::into_raw(instruction_data.into_boxed_slice()) as *const u32; - let program: ProgramWithDependencies = Program::authenticated_transfer_program().into(); + let program: ProgramWithDependencies = programs::authenticated_transfer().into(); let program_with_dependencies: FfiProgramWithDependencies = program.into(); unsafe { @@ -1307,3 +1749,180 @@ fn test_wallet_ffi_transfer_generic_private() -> Result<()> { Ok(()) } + +#[test] +fn test_wallet_ffi_vault_balance_and_claim_public() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + + let sender = ctx.ctx().existing_public_accounts()[0]; + let owner = ctx.ctx().existing_public_accounts()[1]; + let owner_ffi: FfiBytes32 = owner.into(); + let amount: u128 = 100; + + // Fund the owner's vault, simulating an L1 bridge deposit. + ctx.block_on(|ctx| async move { + Vault(ctx.wallet()) + .send_transfer(sender, owner, amount) + .await + }) + .unwrap(); + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let vault_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_vault_balance( + wallet_ffi_handle, + &raw const owner_ffi, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + assert_eq!(vault_balance, amount); + + let mut transfer_result = FfiTransferResult::default(); + let claim_amount: [u8; 16] = amount.to_le_bytes(); + unsafe { + wallet_ffi_vault_claim( + wallet_ffi_handle, + &raw const owner_ffi, + &raw const claim_amount, + &raw mut transfer_result, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let vault_balance_after_claim = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_vault_balance( + wallet_ffi_handle, + &raw const owner_ffi, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + assert_eq!(vault_balance_after_claim, 0); + + let owner_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const owner_ffi, + true, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + assert_eq!(owner_balance, 20_000 + amount); + + unsafe { + wallet_ffi_free_transfer_result(&raw mut transfer_result); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} + +#[test] +fn test_wallet_ffi_vault_balance_and_claim_private() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + + let sender = ctx.ctx().existing_public_accounts()[0]; + let owner = ctx.ctx().existing_private_accounts()[0]; + let owner_ffi: FfiBytes32 = owner.into(); + let amount: u128 = 100; + + // Fund the owner's vault. Real deposits always land via a public transfer (the bridge + // program crediting the vault PDA), regardless of whether the owner is private. + ctx.block_on(|ctx| async move { + Vault(ctx.wallet()) + .send_transfer(sender, owner, amount) + .await + }) + .unwrap(); + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let vault_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_vault_balance( + wallet_ffi_handle, + &raw const owner_ffi, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + assert_eq!(vault_balance, amount); + + let mut transfer_result = FfiTransferResult::default(); + let claim_amount: [u8; 16] = amount.to_le_bytes(); + unsafe { + wallet_ffi_vault_claim_private( + wallet_ffi_handle, + &raw const owner_ffi, + &raw const claim_amount, + &raw mut transfer_result, + ) + .unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + // Sync private account local storage with onchain encrypted state + unsafe { + let mut current_height = 0; + wallet_ffi_get_current_block_height(wallet_ffi_handle, &raw mut current_height).unwrap(); + wallet_ffi_sync_to_block(wallet_ffi_handle, current_height).unwrap(); + }; + + let vault_balance_after_claim = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + wallet_ffi_get_vault_balance( + wallet_ffi_handle, + &raw const owner_ffi, + &raw mut out_balance, + ) + .unwrap(); + u128::from_le_bytes(out_balance) + }; + assert_eq!(vault_balance_after_claim, 0); + + let owner_balance = unsafe { + let mut out_balance: [u8; 16] = [0; 16]; + let _result = wallet_ffi_get_balance( + wallet_ffi_handle, + &raw const owner_ffi, + false, + &raw mut out_balance, + ); + u128::from_le_bytes(out_balance) + }; + assert_eq!(owner_balance, 10_000 + amount); + + unsafe { + wallet_ffi_free_transfer_result(&raw mut transfer_result); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} diff --git a/lee/privacy_preserving_circuit/Cargo.toml b/lee/privacy_preserving_circuit/Cargo.toml new file mode 100644 index 00000000..26dfa132 --- /dev/null +++ b/lee/privacy_preserving_circuit/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "privacy_preserving_circuit_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +# Renaming binary to privacy_preserving_circuit for better looking. +[[bin]] +name = "privacy_preserving_circuit" +path = "src/main.rs" + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +risc0-zkvm.workspace = true diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs b/lee/privacy_preserving_circuit/src/execution_state.rs similarity index 100% rename from program_methods/guest/src/bin/privacy_preserving_circuit/execution_state.rs rename to lee/privacy_preserving_circuit/src/execution_state.rs diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit/main.rs b/lee/privacy_preserving_circuit/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/privacy_preserving_circuit/main.rs rename to lee/privacy_preserving_circuit/src/main.rs diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit/output.rs b/lee/privacy_preserving_circuit/src/output.rs similarity index 100% rename from program_methods/guest/src/bin/privacy_preserving_circuit/output.rs rename to lee/privacy_preserving_circuit/src/output.rs diff --git a/lee/state_machine/Cargo.toml b/lee/state_machine/Cargo.toml index 5e7fe722..d4014465 100644 --- a/lee/state_machine/Cargo.toml +++ b/lee/state_machine/Cargo.toml @@ -9,10 +9,6 @@ workspace = true [dependencies] lee_core = { workspace = true, features = ["host"] } -clock_core.workspace = true -faucet_core.workspace = true -bridge_core.workspace = true -wrapped_token_core.workspace = true anyhow.workspace = true thiserror.workspace = true @@ -28,18 +24,12 @@ risc0-binfmt = "3.0.2" log.workspace = true [build-dependencies] -risc0-build = "3.0.3" -risc0-binfmt = "3.0.2" +build_utils.workspace = true [dev-dependencies] lee_core = { workspace = true, features = ["test_utils"] } token_core.workspace = true -authenticated_transfer_core.workspace = true -cross_zone_inbox_core.workspace = true -cross_zone_outbox_core.workspace = true -bridge_lock_core.workspace = true -ping_core.workspace = true -test_program_methods.workspace = true +test_methods = { path = "test_methods" } env_logger.workspace = true hex-literal = "1.0.0" diff --git a/lee/state_machine/build.rs b/lee/state_machine/build.rs index 5e8ac989..de7c6095 100644 --- a/lee/state_machine/build.rs +++ b/lee/state_machine/build.rs @@ -1,43 +1,5 @@ -use std::{env, fmt::Write as _, fs, path::PathBuf}; - fn main() -> Result<(), Box> { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); - let out_dir = PathBuf::from(env::var("OUT_DIR")?); - let mod_dir = out_dir.join("program_methods"); - let mod_file = mod_dir.join("mod.rs"); - let program_methods_dir = manifest_dir.join("../../artifacts/program_methods/"); - - println!("cargo:rerun-if-changed={}", program_methods_dir.display()); - - let bins = fs::read_dir(&program_methods_dir)? - .filter_map(Result::ok) - .filter(|e| e.path().extension().is_some_and(|ext| ext == "bin")) - .collect::>(); - - if bins.is_empty() { - return Err(format!("No .bin files found in {}", program_methods_dir.display()).into()); - } - - fs::create_dir_all(&mod_dir)?; - let mut src = String::new(); - for entry in bins { - let path = entry.path(); - let name = path.file_stem().unwrap().to_string_lossy(); - let bytecode = fs::read(&path)?; - let image_id: [u32; 8] = risc0_binfmt::compute_image_id(&bytecode)?.into(); - write!( - src, - "pub const {}_ELF: &[u8] = include_bytes!(r#\"{}\"#);\n\ - #[expect(clippy::unreadable_literal, reason = \"Generated image IDs from risc0 are cryptographic hashes represented as u32 arrays\")]\n\ - pub const {}_ID: [u32; 8] = {:?};\n", - name.to_uppercase(), - path.display(), - name.to_uppercase(), - image_id - )?; - } - fs::write(&mod_file, src)?; - println!("cargo:warning=Generated module at {}", mod_file.display()); + build_utils::include_artifacts("lee/privacy_preserving_circuit")?; Ok(()) } diff --git a/lee/state_machine/src/cross_zone_dispatch_tests.rs b/lee/state_machine/src/cross_zone_dispatch_tests.rs deleted file mode 100644 index aab90e85..00000000 --- a/lee/state_machine/src/cross_zone_dispatch_tests.rs +++ /dev/null @@ -1,104 +0,0 @@ -#![expect( - clippy::tests_outside_test_module, - clippy::arithmetic_side_effects, - reason = "We don't care about these in tests" -)] - -use std::collections::BTreeMap; - -use cross_zone_inbox_core::{ - CrossZoneMessage, InboxConfig, Instruction, inbox_config_account_id, - inbox_seen_shard_account_id, -}; -use lee_core::account::Account; -use ping_core::{ReceiverInstruction, ping_record_pda}; - -use crate::{ - V03State, - program::Program, - public_transaction::{Message, WitnessSet}, - validated_state_diff::ValidatedStateDiff, -}; - -/// Drives `cross_zone_inbox::Dispatch` directly through the state machine -/// (no watcher) and asserts the message is delivered to `ping_receiver`, which -/// records the payload into its own PDA. -#[test] -fn inbox_dispatch_delivers_payload_to_ping_receiver() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - // ping_receiver is a throwaway demo target, registered only in this test state. - state.insert_program(Program::ping_receiver()); - - let inbox_id = Program::cross_zone_inbox().id(); - let receiver_id = Program::ping_receiver().id(); - - let self_zone = [1_u8; 32]; - let src_zone = [2_u8; 32]; - let src_block_id = 5; - - // Seed the inbox config account (inbox-owned) allowing src_zone -> ping_receiver. - let mut allowed_targets = BTreeMap::new(); - allowed_targets.insert(src_zone, vec![receiver_id]); - let config = InboxConfig { - self_zone, - allowed_peers: BTreeMap::new(), - allowed_targets, - }; - let config_id = inbox_config_account_id(inbox_id); - state.force_insert_account( - config_id, - Account { - program_owner: inbox_id, - balance: 0_u128, - data: config - .to_bytes() - .try_into() - .expect("config fits in account data"), - nonce: 0_u128.into(), - }, - ); - - // The payload is the ping_receiver instruction, serialized as risc0 words in - // little-endian bytes (the contract the inbox reverses when forwarding). - let inner = b"hello-cross-zone".to_vec(); - let words = risc0_zkvm::serde::to_vec(&ReceiverInstruction::Record { - payload: inner.clone(), - }) - .expect("serialize ping instruction"); - let payload: Vec = words.iter().flat_map(|word| word.to_le_bytes()).collect(); - - let msg = CrossZoneMessage { - src_zone, - src_block_id, - src_tx_index: 0, - src_program_id: [9_u32; 8], - target_program_id: receiver_id, - payload, - l1_inclusion_witness: None, - }; - - let seen_id = inbox_seen_shard_account_id(inbox_id, &src_zone, src_block_id); - let record_id = ping_record_pda(receiver_id); - - let message = Message::try_new( - inbox_id, - vec![config_id, seen_id, record_id], - vec![], - Instruction::Dispatch(msg), - ) - .expect("build dispatch message"); - let tx = crate::PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); - - let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) - .expect("dispatch must validate and execute"); - let public_diff = diff.public_diff(); - - let record = public_diff - .get(&record_id) - .expect("ping record account must change"); - assert_eq!( - record.data.clone().into_inner(), - inner, - "ping_receiver must record the delivered payload" - ); -} diff --git a/lee/state_machine/src/lib.rs b/lee/state_machine/src/lib.rs index 58584176..f8cc034a 100644 --- a/lee/state_machine/src/lib.rs +++ b/lee/state_machine/src/lib.rs @@ -9,17 +9,16 @@ pub use lee_core::{ encryption::EphemeralPublicKey, program::ProgramId, }; +pub use privacy_preserving_circuit::{ + PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID, +}; pub use privacy_preserving_transaction::{ PrivacyPreservingTransaction, circuit::execute_and_prove, }; pub use program_deployment_transaction::ProgramDeploymentTransaction; -pub use program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; pub use public_transaction::PublicTransaction; pub use signature::{PrivateKey, PublicKey, Signature}; -pub use state::{ - CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, - CLOCK_PROGRAM_ACCOUNT_IDS, V03State, system_bridge_account_id, system_faucet_account_id, -}; +pub use state::V03State; pub use validated_state_diff::ValidatedStateDiff; pub mod encoding; @@ -33,12 +32,238 @@ mod signature; mod state; mod validated_state_diff; -#[cfg(test)] -mod cross_zone_dispatch_tests; - -#[cfg(test)] -mod cross_zone_bridge_tests; - -pub mod program_methods { - include!(concat!(env!("OUT_DIR"), "/program_methods/mod.rs")); +mod privacy_preserving_circuit { + include!(concat!( + env!("OUT_DIR"), + "/lee/privacy_preserving_circuit/mod.rs" + )); +} + +#[cfg(test)] +mod test_methods { + use std::borrow::Cow; + + use crate::program::Program; + + #[must_use] + pub const fn simple_balance_transfer() -> Program { + Program::new_unchecked( + test_methods::SIMPLE_BALANCE_TRANSFER_ID, + Cow::Borrowed(test_methods::SIMPLE_BALANCE_TRANSFER_ELF), + ) + } + + #[must_use] + pub const fn nonce_changer() -> Program { + Program::new_unchecked( + test_methods::NONCE_CHANGER_ID, + Cow::Borrowed(test_methods::NONCE_CHANGER_ELF), + ) + } + + #[must_use] + pub const fn extra_output() -> Program { + Program::new_unchecked( + test_methods::EXTRA_OUTPUT_ID, + Cow::Borrowed(test_methods::EXTRA_OUTPUT_ELF), + ) + } + + #[must_use] + pub const fn missing_output() -> Program { + Program::new_unchecked( + test_methods::MISSING_OUTPUT_ID, + Cow::Borrowed(test_methods::MISSING_OUTPUT_ELF), + ) + } + + #[must_use] + pub const fn program_owner_changer() -> Program { + Program::new_unchecked( + test_methods::PROGRAM_OWNER_CHANGER_ID, + Cow::Borrowed(test_methods::PROGRAM_OWNER_CHANGER_ELF), + ) + } + + #[must_use] + pub const fn data_changer() -> Program { + Program::new_unchecked( + test_methods::DATA_CHANGER_ID, + Cow::Borrowed(test_methods::DATA_CHANGER_ELF), + ) + } + + #[must_use] + pub const fn minter() -> Program { + Program::new_unchecked( + test_methods::MINTER_ID, + Cow::Borrowed(test_methods::MINTER_ELF), + ) + } + + #[must_use] + pub const fn burner() -> Program { + Program::new_unchecked( + test_methods::BURNER_ID, + Cow::Borrowed(test_methods::BURNER_ELF), + ) + } + + #[must_use] + pub const fn auth_asserting_noop() -> Program { + Program::new_unchecked( + test_methods::AUTH_ASSERTING_NOOP_ID, + Cow::Borrowed(test_methods::AUTH_ASSERTING_NOOP_ELF), + ) + } + + #[must_use] + pub const fn private_pda_delegator() -> Program { + Program::new_unchecked( + test_methods::PRIVATE_PDA_DELEGATOR_ID, + Cow::Borrowed(test_methods::PRIVATE_PDA_DELEGATOR_ELF), + ) + } + + #[must_use] + pub const fn pda_claimer() -> Program { + Program::new_unchecked( + test_methods::PDA_CLAIMER_ID, + Cow::Borrowed(test_methods::PDA_CLAIMER_ELF), + ) + } + + #[must_use] + pub const fn two_pda_claimer() -> Program { + Program::new_unchecked( + test_methods::TWO_PDA_CLAIMER_ID, + Cow::Borrowed(test_methods::TWO_PDA_CLAIMER_ELF), + ) + } + + #[must_use] + pub const fn noop() -> Program { + Program::new_unchecked(test_methods::NOOP_ID, Cow::Borrowed(test_methods::NOOP_ELF)) + } + + #[must_use] + pub const fn chain_caller() -> Program { + Program::new_unchecked( + test_methods::CHAIN_CALLER_ID, + Cow::Borrowed(test_methods::CHAIN_CALLER_ELF), + ) + } + + #[must_use] + pub const fn modified_transfer_program() -> Program { + Program::new_unchecked( + test_methods::MODIFIED_TRANSFER_ID, + Cow::Borrowed(test_methods::MODIFIED_TRANSFER_ELF), + ) + } + + #[must_use] + pub const fn malicious_authorization_changer() -> Program { + Program::new_unchecked( + test_methods::MALICIOUS_AUTHORIZATION_CHANGER_ID, + Cow::Borrowed(test_methods::MALICIOUS_AUTHORIZATION_CHANGER_ELF), + ) + } + + #[must_use] + pub const fn validity_window() -> Program { + Program::new_unchecked( + test_methods::VALIDITY_WINDOW_ID, + Cow::Borrowed(test_methods::VALIDITY_WINDOW_ELF), + ) + } + + #[must_use] + pub const fn flash_swap_initiator() -> Program { + Program::new_unchecked( + test_methods::FLASH_SWAP_INITIATOR_ID, + Cow::Borrowed(test_methods::FLASH_SWAP_INITIATOR_ELF), + ) + } + + #[must_use] + pub const fn flash_swap_callback() -> Program { + Program::new_unchecked( + test_methods::FLASH_SWAP_CALLBACK_ID, + Cow::Borrowed(test_methods::FLASH_SWAP_CALLBACK_ELF), + ) + } + + #[must_use] + pub const fn malicious_self_program_id() -> Program { + Program::new_unchecked( + test_methods::MALICIOUS_SELF_PROGRAM_ID_ID, + Cow::Borrowed(test_methods::MALICIOUS_SELF_PROGRAM_ID_ELF), + ) + } + + #[must_use] + pub const fn malicious_caller_program_id() -> Program { + Program::new_unchecked( + test_methods::MALICIOUS_CALLER_PROGRAM_ID_ID, + Cow::Borrowed(test_methods::MALICIOUS_CALLER_PROGRAM_ID_ELF), + ) + } + + #[must_use] + pub const fn pda_spend_proxy() -> Program { + Program::new_unchecked( + test_methods::PDA_SPEND_PROXY_ID, + Cow::Borrowed(test_methods::PDA_SPEND_PROXY_ELF), + ) + } + + #[must_use] + pub const fn claimer() -> Program { + Program::new_unchecked( + test_methods::CLAIMER_ID, + Cow::Borrowed(test_methods::CLAIMER_ELF), + ) + } + + #[must_use] + pub const fn changer_claimer() -> Program { + Program::new_unchecked( + test_methods::CHANGER_CLAIMER_ID, + Cow::Borrowed(test_methods::CHANGER_CLAIMER_ELF), + ) + } + + #[must_use] + pub const fn validity_window_chain_caller() -> Program { + Program::new_unchecked( + test_methods::VALIDITY_WINDOW_CHAIN_CALLER_ID, + Cow::Borrowed(test_methods::VALIDITY_WINDOW_CHAIN_CALLER_ELF), + ) + } + + #[must_use] + #[inline] + pub const fn simple_transfer_proxy() -> Program { + Program::new_unchecked( + test_methods::SIMPLE_TRANSFER_PROXY_ID, + Cow::Borrowed(test_methods::SIMPLE_TRANSFER_PROXY_ELF), + ) + } + + #[must_use] + pub const fn malicious_injector() -> Program { + Program::new_unchecked( + test_methods::MALICIOUS_INJECTOR_ID, + Cow::Borrowed(test_methods::MALICIOUS_INJECTOR_ELF), + ) + } + + #[must_use] + pub const fn malicious_launderer() -> Program { + Program::new_unchecked( + test_methods::MALICIOUS_LAUNDERER_ID, + Cow::Borrowed(test_methods::MALICIOUS_LAUNDERER_ELF), + ) + } } diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit.rs index 87c7c5bb..489ee373 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit.rs @@ -9,9 +9,9 @@ use lee_core::{ use risc0_zkvm::{ExecutorEnv, InnerReceipt, ProverOpts, Receipt, default_prover}; use crate::{ + PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID, error::{InvalidProgramBehaviorError, LeeError}, program::Program, - program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT_ID}, state::MAX_NUMBER_CHAINED_CALLS, }; @@ -224,7 +224,7 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { let recipient_keys = test_private_account_keys_1(); - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let sender = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -261,10 +261,7 @@ mod tests { let (output, proof) = execute_and_prove( vec![sender, recipient], - Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { - amount: balance_to_move, - }) - .unwrap(), + Program::serialize_instruction(balance_to_move).unwrap(), vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivateUnauthorized { @@ -278,7 +275,7 @@ mod tests { identifier: 0, }, ], - &Program::authenticated_transfer_program().into(), + &crate::test_methods::simple_balance_transfer().into(), ) .unwrap(); @@ -304,7 +301,7 @@ mod tests { #[test] fn prove_privacy_preserving_execution_circuit_fully_private() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); @@ -339,7 +336,7 @@ mod tests { ), ]; - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let expected_private_account_1 = Account { program_owner: program.id(), @@ -366,10 +363,7 @@ mod tests { let (output, proof) = execute_and_prove( vec![sender_pre, recipient], - Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { - amount: balance_to_move, - }) - .unwrap(), + Program::serialize_instruction(balance_to_move).unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { epk: EphemeralPublicKey(Vec::new()), @@ -434,8 +428,8 @@ mod tests { AccountId::for_regular_private_account(&account_keys.npk(), 0), ); - let validity_window_chain_caller = Program::validity_window_chain_caller(); - let validity_window = Program::validity_window(); + let validity_window_chain_caller = crate::test_methods::validity_window_chain_caller(); + let validity_window = crate::test_methods::validity_window(); let instruction = Program::serialize_instruction(( Some(1_u64), @@ -477,7 +471,7 @@ mod tests { /// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`. #[test] fn private_pda_claim_with_custom_identifier_encrypts_correct_kind() { - let program = Program::pda_claimer(); + let program = crate::test_methods::pda_claimer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); @@ -513,13 +507,13 @@ mod tests { ); } - /// PDA init: initializes a new PDA under `authenticated_transfer`'s ownership. - /// The `auth_transfer_proxy` program chains to `authenticated_transfer` with `pda_seeds` + /// PDA init: initializes a new PDA under `simple_balance_transfer`'s ownership. + /// The `simple_transfer_proxy` program chains to `simple_balance_transfer` with `pda_seeds` /// to establish authorization and the private PDA binding. #[test] fn private_pda_init() { - let program = Program::auth_transfer_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_transfer_proxy(); + let simple_transfer = crate::test_methods::simple_balance_transfer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); @@ -530,9 +524,9 @@ mod tests { let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 0); let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id); - let auth_id = auth_transfer.id(); + let auth_id = simple_transfer.id(); let program_with_deps = - ProgramWithDependencies::new(program, [(auth_id, auth_transfer)].into()); + ProgramWithDependencies::new(program, [(auth_id, simple_transfer)].into()); // is_withdraw=false triggers init path (1 pre-state) let instruction = Program::serialize_instruction((seed, auth_id, 0_u128, false)).unwrap(); @@ -555,13 +549,13 @@ mod tests { assert_eq!(output.new_commitments.len(), 1); } - /// PDA withdraw: chains to `authenticated_transfer` to move balance from PDA to recipient. + /// PDA withdraw: chains to `simple_balance_transfer` to move balance from PDA to recipient. /// Uses a default PDA (amount=0) because testing with a pre-funded PDA requires a /// two-tx sequence with membership proofs. #[test] fn private_pda_withdraw() { - let program = Program::auth_transfer_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_transfer_proxy(); + let simple_transfer = crate::test_methods::simple_balance_transfer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); @@ -576,7 +570,7 @@ mod tests { let recipient_id = AccountId::new([88; 32]); let recipient_pre = AccountWithMetadata::new( Account { - program_owner: auth_transfer.id(), + program_owner: simple_transfer.id(), balance: 10000, ..Account::default() }, @@ -584,9 +578,9 @@ mod tests { recipient_id, ); - let auth_id = auth_transfer.id(); + let auth_id = simple_transfer.id(); let program_with_deps = - ProgramWithDependencies::new(program, [(auth_id, auth_transfer)].into()); + ProgramWithDependencies::new(program, [(auth_id, simple_transfer)].into()); // is_withdraw=true, amount=0 (PDA has no balance yet) let instruction = Program::serialize_instruction((seed, auth_id, 0_u128, true)).unwrap(); @@ -618,8 +612,8 @@ mod tests { /// uses the standard unauthorized private account path and works with auth-transfer's /// transfer path like any other private account. #[test] - fn shared_account_receives_via_auth_transfer() { - let program = Program::authenticated_transfer_program(); + fn shared_account_receives_via_simple_transfer() { + let program = crate::test_methods::simple_balance_transfer(); let shared_keys = test_private_account_keys_1(); let shared_npk = shared_keys.npk(); let shared_identifier: u128 = 42; @@ -643,11 +637,7 @@ mod tests { let recipient = AccountWithMetadata::new(Account::default(), false, shared_account_id); let balance_to_move: u128 = 100; - let instruction = - Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { - amount: balance_to_move, - }) - .unwrap(); + let instruction = Program::serialize_instruction(balance_to_move).unwrap(); let result = execute_and_prove( vec![sender, recipient], @@ -677,7 +667,7 @@ mod tests { /// to `PrivateAccountKind::Regular` carrying the correct identifier. #[test] fn private_authorized_init_encrypts_regular_kind_with_identifier() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::claimer(); let keys = test_private_account_keys_1(); let identifier: u128 = 99; let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; @@ -686,8 +676,7 @@ mod tests { let (output, _) = execute_and_prove( vec![pre], - Program::serialize_instruction(authenticated_transfer_core::Instruction::Initialize) - .unwrap(), + Program::serialize_instruction(()).unwrap(), vec![InputAccountIdentity::PrivateAuthorizedInit { epk: EphemeralPublicKey(Vec::new()), view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), @@ -709,39 +698,23 @@ mod tests { /// to `PrivateAccountKind::Regular` carrying the correct identifier. #[test] fn private_unauthorized_init_encrypts_regular_kind_with_identifier() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::claimer(); let keys = test_private_account_keys_1(); let identifier: u128 = 99; let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; - - let sender = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 1, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); let recipient_id = AccountId::for_regular_private_account(&keys.npk(), identifier); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_id); let (output, _) = execute_and_prove( - vec![sender, recipient], - Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { - amount: 1, - }) - .unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivateUnauthorized { - epk: EphemeralPublicKey(Vec::new()), - view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), - npk: keys.npk(), - ssk, - identifier, - }, - ], + vec![recipient], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), + npk: keys.npk(), + ssk, + identifier, + }], &program.into(), ) .unwrap(); @@ -756,7 +729,7 @@ mod tests { /// to `PrivateAccountKind::Regular` carrying the correct identifier. #[test] fn private_authorized_update_encrypts_regular_kind_with_identifier() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::noop(); let keys = test_private_account_keys_1(); let identifier: u128 = 99; let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; @@ -771,25 +744,18 @@ mod tests { commitment_set.extend(std::slice::from_ref(&commitment)); let sender = AccountWithMetadata::new(account, true, account_id); - let recipient = AccountWithMetadata::new(Account::default(), true, AccountId::new([0; 32])); let (output, _) = execute_and_prove( - vec![sender, recipient], - Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { - amount: 1, - }) - .unwrap(), - vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - epk: EphemeralPublicKey(Vec::new()), - view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), - ssk, - nsk: keys.nsk, - membership_proof: commitment_set.get_proof_for(&commitment).unwrap(), - identifier, - }, - InputAccountIdentity::Public, - ], + vec![sender], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::PrivateAuthorizedUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()), + ssk, + nsk: keys.nsk, + membership_proof: commitment_set.get_proof_for(&commitment).unwrap(), + identifier, + }], &program.into(), ) .unwrap(); @@ -804,18 +770,18 @@ mod tests { /// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`. #[test] fn private_pda_update_encrypts_pda_kind_with_identifier() { - let program = Program::pda_spend_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); + let program = crate::test_methods::pda_spend_proxy(); + let simple_transfer = crate::test_methods::simple_balance_transfer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let identifier: u128 = 99; let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; - let auth_transfer_id = auth_transfer.id(); + let simple_transfer_id = simple_transfer.id(); let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier); let pda_account = Account { - program_owner: auth_transfer_id, + program_owner: simple_transfer_id, balance: 1, ..Account::default() }; @@ -829,12 +795,12 @@ mod tests { let program_with_deps = ProgramWithDependencies::new( program.clone(), - [(auth_transfer_id, auth_transfer)].into(), + [(simple_transfer_id, simple_transfer)].into(), ); let (output, _) = execute_and_prove( vec![pda_pre, recipient_pre], - Program::serialize_instruction((seed, 1_u128, auth_transfer_id, false)).unwrap(), + Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(), vec![ InputAccountIdentity::PrivatePdaUpdate { epk: EphemeralPublicKey(Vec::new()), @@ -863,7 +829,7 @@ mod tests { #[test] fn private_pda_init_identifier_mismatch_fails() { - let program = Program::pda_claimer(); + let program = crate::test_methods::pda_claimer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); @@ -892,17 +858,17 @@ mod tests { #[test] fn private_pda_update_identifier_mismatch_fails() { - let program = Program::pda_spend_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); + let program = crate::test_methods::pda_spend_proxy(); + let simple_transfer = crate::test_methods::simple_balance_transfer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; - let auth_transfer_id = auth_transfer.id(); + let simple_transfer_id = simple_transfer.id(); let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 5); let pda_account = Account { - program_owner: auth_transfer_id, + program_owner: simple_transfer_id, balance: 1, ..Account::default() }; @@ -915,11 +881,11 @@ mod tests { AccountWithMetadata::new(Account::default(), true, AccountId::new([0; 32])); let program_with_deps = - ProgramWithDependencies::new(program, [(auth_transfer_id, auth_transfer)].into()); + ProgramWithDependencies::new(program, [(simple_transfer_id, simple_transfer)].into()); let result = execute_and_prove( vec![pda_pre, recipient_pre], - Program::serialize_instruction((seed, 1_u128, auth_transfer_id, false)).unwrap(), + Program::serialize_instruction((seed, 1_u128, simple_transfer_id, false)).unwrap(), vec![ InputAccountIdentity::PrivatePdaUpdate { epk: EphemeralPublicKey(Vec::new()), diff --git a/lee/state_machine/src/program.rs b/lee/state_machine/src/program.rs index de10913c..65d60a42 100644 --- a/lee/state_machine/src/program.rs +++ b/lee/state_machine/src/program.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use borsh::{BorshDeserialize, BorshSerialize}; use lee_core::{ account::AccountWithMetadata, @@ -6,18 +8,7 @@ use lee_core::{ use risc0_zkvm::{ExecutorEnv, ExecutorEnvBuilder, default_executor, serde::to_vec}; use serde::Serialize; -use crate::{ - error::LeeError, - program_methods::{ - AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID, - AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, BRIDGE_ELF, BRIDGE_ID, - BRIDGE_LOCK_ELF, BRIDGE_LOCK_ID, CLOCK_ELF, CLOCK_ID, CROSS_ZONE_INBOX_ELF, - CROSS_ZONE_INBOX_ID, CROSS_ZONE_OUTBOX_ELF, CROSS_ZONE_OUTBOX_ID, FAUCET_ELF, FAUCET_ID, - PINATA_ELF, PINATA_ID, PING_RECEIVER_ELF, PING_RECEIVER_ID, PING_SENDER_ELF, - PING_SENDER_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID, WRAPPED_TOKEN_ELF, - WRAPPED_TOKEN_ID, - }, -}; +use crate::error::LeeError; /// Maximum number of cycles for a public execution. /// TODO: Make this variable when fees are implemented. @@ -26,18 +17,23 @@ const MAX_NUM_CYCLES_PUBLIC_EXECUTION: u64 = 1024 * 1024 * 32; // 32M cycles #[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Program { id: ProgramId, - elf: Vec, + elf: Cow<'static, [u8]>, } impl Program { - pub fn new(bytecode: Vec) -> Result { - let binary = risc0_binfmt::ProgramBinary::decode(&bytecode) + pub fn new(elf: Cow<'static, [u8]>) -> Result { + let binary = risc0_binfmt::ProgramBinary::decode(elf.as_ref()) .map_err(LeeError::InvalidProgramBytecode)?; let id = binary .compute_image_id() .map_err(LeeError::InvalidProgramBytecode)? .into(); - Ok(Self { elf: bytecode, id }) + Ok(Self { id, elf }) + } + + #[must_use] + pub const fn new_unchecked(id: ProgramId, elf: Cow<'static, [u8]>) -> Self { + Self { id, elf } } #[must_use] @@ -112,460 +108,17 @@ impl Program { .map_err(|e| LeeError::ProgramWriteInputFailed(e.to_string()))?; Ok(()) } - - #[must_use] - pub fn authenticated_transfer_program() -> Self { - Self { - id: AUTHENTICATED_TRANSFER_ID, - elf: AUTHENTICATED_TRANSFER_ELF.to_vec(), - } - } - - #[must_use] - pub fn token() -> Self { - Self { - id: TOKEN_ID, - elf: TOKEN_ELF.to_vec(), - } - } - - #[must_use] - pub fn amm() -> Self { - Self { - id: AMM_ID, - elf: AMM_ELF.to_vec(), - } - } - - #[must_use] - pub fn clock() -> Self { - Self { - id: CLOCK_ID, - elf: CLOCK_ELF.to_vec(), - } - } - - #[must_use] - pub fn ata() -> Self { - Self { - id: ASSOCIATED_TOKEN_ACCOUNT_ID, - elf: ASSOCIATED_TOKEN_ACCOUNT_ELF.to_vec(), - } - } - - #[must_use] - pub fn vault() -> Self { - Self { - id: VAULT_ID, - elf: VAULT_ELF.to_vec(), - } - } - - #[must_use] - pub fn faucet() -> Self { - Self { - id: FAUCET_ID, - elf: FAUCET_ELF.to_vec(), - } - } - - #[must_use] - pub fn bridge() -> Self { - Self { - id: BRIDGE_ID, - elf: BRIDGE_ELF.to_vec(), - } - } - - #[must_use] - pub fn cross_zone_outbox() -> Self { - Self { - id: CROSS_ZONE_OUTBOX_ID, - elf: CROSS_ZONE_OUTBOX_ELF.to_vec(), - } - } - - #[must_use] - pub fn cross_zone_inbox() -> Self { - Self { - id: CROSS_ZONE_INBOX_ID, - elf: CROSS_ZONE_INBOX_ELF.to_vec(), - } - } - - #[must_use] - pub fn ping_receiver() -> Self { - Self { - id: PING_RECEIVER_ID, - elf: PING_RECEIVER_ELF.to_vec(), - } - } - - #[must_use] - pub fn ping_sender() -> Self { - Self { - id: PING_SENDER_ID, - elf: PING_SENDER_ELF.to_vec(), - } - } - - #[must_use] - pub fn bridge_lock() -> Self { - Self { - id: BRIDGE_LOCK_ID, - elf: BRIDGE_LOCK_ELF.to_vec(), - } - } - - #[must_use] - pub fn wrapped_token() -> Self { - Self { - id: WRAPPED_TOKEN_ID, - elf: WRAPPED_TOKEN_ELF.to_vec(), - } - } -} - -// TODO: Testnet only. Refactor to prevent compilation on mainnet. -impl Program { - #[must_use] - pub fn pinata() -> Self { - Self { - id: PINATA_ID, - elf: PINATA_ELF.to_vec(), - } - } - - #[must_use] - pub fn pinata_token() -> Self { - use crate::program_methods::{PINATA_TOKEN_ELF, PINATA_TOKEN_ID}; - Self { - id: PINATA_TOKEN_ID, - elf: PINATA_TOKEN_ELF.to_vec(), - } - } } #[cfg(test)] mod tests { use lee_core::account::{Account, AccountId, AccountWithMetadata}; - use crate::{ - program::Program, - program_methods::{ - AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID, - AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, BRIDGE_ELF, BRIDGE_ID, - CLOCK_ELF, CLOCK_ID, FAUCET_ELF, FAUCET_ID, PINATA_ELF, PINATA_ID, PINATA_TOKEN_ELF, - PINATA_TOKEN_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID, - }, - }; - - impl Program { - /// A program that changes the nonce of an account. - #[must_use] - pub fn nonce_changer_program() -> Self { - use test_program_methods::{NONCE_CHANGER_ELF, NONCE_CHANGER_ID}; - - Self { - id: NONCE_CHANGER_ID, - elf: NONCE_CHANGER_ELF.to_vec(), - } - } - - /// A program that produces more output accounts than the inputs it received. - #[must_use] - pub fn extra_output_program() -> Self { - use test_program_methods::{EXTRA_OUTPUT_ELF, EXTRA_OUTPUT_ID}; - - Self { - id: EXTRA_OUTPUT_ID, - elf: EXTRA_OUTPUT_ELF.to_vec(), - } - } - - /// A program that produces less output accounts than the inputs it received. - #[must_use] - pub fn missing_output_program() -> Self { - use test_program_methods::{MISSING_OUTPUT_ELF, MISSING_OUTPUT_ID}; - - Self { - id: MISSING_OUTPUT_ID, - elf: MISSING_OUTPUT_ELF.to_vec(), - } - } - - /// A program that changes the program owner of an account to [0, 1, 2, 3, 4, 5, 6, 7]. - #[must_use] - pub fn program_owner_changer() -> Self { - use test_program_methods::{PROGRAM_OWNER_CHANGER_ELF, PROGRAM_OWNER_CHANGER_ID}; - - Self { - id: PROGRAM_OWNER_CHANGER_ID, - elf: PROGRAM_OWNER_CHANGER_ELF.to_vec(), - } - } - - /// A program that transfers balance without caring about authorizations. - #[must_use] - pub fn simple_balance_transfer() -> Self { - use test_program_methods::{SIMPLE_BALANCE_TRANSFER_ELF, SIMPLE_BALANCE_TRANSFER_ID}; - - Self { - id: SIMPLE_BALANCE_TRANSFER_ID, - elf: SIMPLE_BALANCE_TRANSFER_ELF.to_vec(), - } - } - - /// A program that modifies the data of an account. - #[must_use] - pub fn data_changer() -> Self { - use test_program_methods::{DATA_CHANGER_ELF, DATA_CHANGER_ID}; - - Self { - id: DATA_CHANGER_ID, - elf: DATA_CHANGER_ELF.to_vec(), - } - } - - /// A program that mints balance. - #[must_use] - pub fn minter() -> Self { - use test_program_methods::{MINTER_ELF, MINTER_ID}; - - Self { - id: MINTER_ID, - elf: MINTER_ELF.to_vec(), - } - } - - /// A program that burns balance. - #[must_use] - pub fn burner() -> Self { - use test_program_methods::{BURNER_ELF, BURNER_ID}; - - Self { - id: BURNER_ID, - elf: BURNER_ELF.to_vec(), - } - } - - #[must_use] - pub fn chain_caller() -> Self { - use test_program_methods::{CHAIN_CALLER_ELF, CHAIN_CALLER_ID}; - - Self { - id: CHAIN_CALLER_ID, - elf: CHAIN_CALLER_ELF.to_vec(), - } - } - - #[must_use] - pub fn claimer() -> Self { - use test_program_methods::{CLAIMER_ELF, CLAIMER_ID}; - - Self { - id: CLAIMER_ID, - elf: CLAIMER_ELF.to_vec(), - } - } - - #[must_use] - pub fn pda_claimer() -> Self { - use test_program_methods::{PDA_CLAIMER_ELF, PDA_CLAIMER_ID}; - - Self { - id: PDA_CLAIMER_ID, - elf: PDA_CLAIMER_ELF.to_vec(), - } - } - - #[must_use] - pub fn private_pda_delegator() -> Self { - use test_program_methods::{PRIVATE_PDA_DELEGATOR_ELF, PRIVATE_PDA_DELEGATOR_ID}; - - Self { - id: PRIVATE_PDA_DELEGATOR_ID, - elf: PRIVATE_PDA_DELEGATOR_ELF.to_vec(), - } - } - - #[must_use] - pub fn auth_transfer_proxy() -> Self { - use test_program_methods::{AUTH_TRANSFER_PROXY_ELF, AUTH_TRANSFER_PROXY_ID}; - - Self { - id: AUTH_TRANSFER_PROXY_ID, - elf: AUTH_TRANSFER_PROXY_ELF.to_vec(), - } - } - - #[must_use] - pub fn two_pda_claimer() -> Self { - use test_program_methods::{TWO_PDA_CLAIMER_ELF, TWO_PDA_CLAIMER_ID}; - - Self { - id: TWO_PDA_CLAIMER_ID, - elf: TWO_PDA_CLAIMER_ELF.to_vec(), - } - } - - #[must_use] - pub fn pda_spend_proxy() -> Self { - use test_program_methods::{PDA_SPEND_PROXY_ELF, PDA_SPEND_PROXY_ID}; - - Self { - id: PDA_SPEND_PROXY_ID, - elf: PDA_SPEND_PROXY_ELF.to_vec(), - } - } - - #[must_use] - pub fn changer_claimer() -> Self { - use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID}; - - Self { - id: CHANGER_CLAIMER_ID, - elf: CHANGER_CLAIMER_ELF.to_vec(), - } - } - - #[must_use] - pub fn noop() -> Self { - use test_program_methods::{NOOP_ELF, NOOP_ID}; - - Self { - id: NOOP_ID, - elf: NOOP_ELF.to_vec(), - } - } - - #[must_use] - pub fn auth_asserting_noop() -> Self { - use test_program_methods::{AUTH_ASSERTING_NOOP_ELF, AUTH_ASSERTING_NOOP_ID}; - - Self { - id: AUTH_ASSERTING_NOOP_ID, - elf: AUTH_ASSERTING_NOOP_ELF.to_vec(), - } - } - - #[must_use] - pub fn malicious_authorization_changer() -> Self { - use test_program_methods::{ - MALICIOUS_AUTHORIZATION_CHANGER_ELF, MALICIOUS_AUTHORIZATION_CHANGER_ID, - }; - - Self { - id: MALICIOUS_AUTHORIZATION_CHANGER_ID, - elf: MALICIOUS_AUTHORIZATION_CHANGER_ELF.to_vec(), - } - } - - #[must_use] - pub fn modified_transfer_program() -> Self { - use test_program_methods::{MODIFIED_TRANSFER_ELF, MODIFIED_TRANSFER_ID}; - Self { - id: MODIFIED_TRANSFER_ID, - elf: MODIFIED_TRANSFER_ELF.to_vec(), - } - } - - #[must_use] - pub fn validity_window() -> Self { - use test_program_methods::{VALIDITY_WINDOW_ELF, VALIDITY_WINDOW_ID}; - Self { - id: VALIDITY_WINDOW_ID, - elf: VALIDITY_WINDOW_ELF.to_vec(), - } - } - - #[must_use] - pub fn validity_window_chain_caller() -> Self { - use test_program_methods::{ - VALIDITY_WINDOW_CHAIN_CALLER_ELF, VALIDITY_WINDOW_CHAIN_CALLER_ID, - }; - Self { - id: VALIDITY_WINDOW_CHAIN_CALLER_ID, - elf: VALIDITY_WINDOW_CHAIN_CALLER_ELF.to_vec(), - } - } - - #[must_use] - pub fn flash_swap_initiator() -> Self { - use test_program_methods::FLASH_SWAP_INITIATOR_ELF; - Self::new(FLASH_SWAP_INITIATOR_ELF.to_vec()) - .expect("flash_swap_initiator must be a valid Risc0 program") - } - - #[must_use] - pub fn flash_swap_callback() -> Self { - use test_program_methods::FLASH_SWAP_CALLBACK_ELF; - Self::new(FLASH_SWAP_CALLBACK_ELF.to_vec()) - .expect("flash_swap_callback must be a valid Risc0 program") - } - - #[must_use] - pub fn malicious_self_program_id() -> Self { - use test_program_methods::MALICIOUS_SELF_PROGRAM_ID_ELF; - Self::new(MALICIOUS_SELF_PROGRAM_ID_ELF.to_vec()) - .expect("malicious_self_program_id must be a valid Risc0 program") - } - - #[must_use] - pub fn malicious_caller_program_id() -> Self { - use test_program_methods::MALICIOUS_CALLER_PROGRAM_ID_ELF; - Self::new(MALICIOUS_CALLER_PROGRAM_ID_ELF.to_vec()) - .expect("malicious_caller_program_id must be a valid Risc0 program") - } - - #[must_use] - pub fn time_locked_transfer() -> Self { - use test_program_methods::TIME_LOCKED_TRANSFER_ELF; - Self::new(TIME_LOCKED_TRANSFER_ELF.to_vec()).unwrap() - } - - #[must_use] - pub fn pinata_cooldown() -> Self { - use test_program_methods::PINATA_COOLDOWN_ELF; - Self::new(PINATA_COOLDOWN_ELF.to_vec()).unwrap() - } - - #[must_use] - pub fn malicious_injector() -> Self { - use test_program_methods::{MALICIOUS_INJECTOR_ELF, MALICIOUS_INJECTOR_ID}; - Self { - id: MALICIOUS_INJECTOR_ID, - elf: MALICIOUS_INJECTOR_ELF.to_vec(), - } - } - - #[must_use] - pub fn malicious_launderer() -> Self { - use test_program_methods::{MALICIOUS_LAUNDERER_ELF, MALICIOUS_LAUNDERER_ID}; - Self { - id: MALICIOUS_LAUNDERER_ID, - elf: MALICIOUS_LAUNDERER_ELF.to_vec(), - } - } - } - - #[test] - fn elf_returns_the_program_bytecode_constant() { - // `Program::elf` must return exactly the compile-time ELF, never an empty - // or placeholder slice. Catches mutations returning `Vec::leak(Vec::new())`, - // `Vec::leak(vec![0])`, or `Vec::leak(vec![1])`. - let at = Program::authenticated_transfer_program(); - assert!(!at.elf().is_empty()); - assert_eq!(at.elf(), AUTHENTICATED_TRANSFER_ELF); - - let token = Program::token(); - assert!(!token.elf().is_empty()); - assert_eq!(token.elf(), TOKEN_ELF); - } + use crate::program::Program; #[test] fn program_execution() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let balance_to_move: u128 = 11_223_344_556_677; let instruction_data = Program::serialize_instruction(balance_to_move).unwrap(); let sender = AccountWithMetadata::new( @@ -596,47 +149,4 @@ mod tests { assert_eq!(sender_post.account(), &expected_sender_post); assert_eq!(recipient_post.account(), &expected_recipient_post); } - - #[test] - fn builtin_programs() { - let auth_transfer_program = Program::authenticated_transfer_program(); - let token_program = Program::token(); - let vault_program = Program::vault(); - let faucet_program = Program::faucet(); - let bridge_program = Program::bridge(); - let pinata_program = Program::pinata(); - - assert_eq!(auth_transfer_program.id, AUTHENTICATED_TRANSFER_ID); - assert_eq!(auth_transfer_program.elf, AUTHENTICATED_TRANSFER_ELF); - assert_eq!(token_program.id, TOKEN_ID); - assert_eq!(token_program.elf, TOKEN_ELF); - assert_eq!(vault_program.id, VAULT_ID); - assert_eq!(vault_program.elf, VAULT_ELF); - assert_eq!(faucet_program.id, FAUCET_ID); - assert_eq!(faucet_program.elf, FAUCET_ELF); - assert_eq!(bridge_program.id, BRIDGE_ID); - assert_eq!(bridge_program.elf, BRIDGE_ELF); - assert_eq!(pinata_program.id, PINATA_ID); - assert_eq!(pinata_program.elf, PINATA_ELF); - } - - #[test] - fn builtin_program_ids_match_elfs() { - let cases: &[(&[u8], [u32; 8])] = &[ - (AMM_ELF, AMM_ID), - (AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID), - (ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID), - (CLOCK_ELF, CLOCK_ID), - (FAUCET_ELF, FAUCET_ID), - (BRIDGE_ELF, BRIDGE_ID), - (PINATA_ELF, PINATA_ID), - (PINATA_TOKEN_ELF, PINATA_TOKEN_ID), - (TOKEN_ELF, TOKEN_ID), - (VAULT_ELF, VAULT_ID), - ]; - for (elf, expected_id) in cases { - let program = Program::new(elf.to_vec()).unwrap(); - assert_eq!(program.id(), *expected_id); - } - } } diff --git a/lee/state_machine/src/public_transaction/transaction.rs b/lee/state_machine/src/public_transaction/transaction.rs index 1d1fee0d..e0d1fe1b 100644 --- a/lee/state_machine/src/public_transaction/transaction.rs +++ b/lee/state_machine/src/public_transaction/transaction.rs @@ -66,7 +66,6 @@ pub mod tests { use crate::{ AccountId, PrivateKey, PublicKey, PublicTransaction, Signature, V03State, error::LeeError, - program::Program, public_transaction::{Message, WitnessSet}, validated_state_diff::ValidatedStateDiff, }; @@ -82,7 +81,9 @@ pub mod tests { fn state_for_tests() -> V03State { let (_, _, addr1, addr2) = keys_for_tests(); let initial_data = [(addr1, 10000), (addr2, 20000)]; - V03State::new_with_genesis_accounts(&initial_data, vec![], 0) + V03State::new() + .with_public_account_balances(initial_data) + .with_programs([crate::test_methods::simple_balance_transfer()]) } fn transaction_for_tests() -> PublicTransaction { @@ -90,7 +91,7 @@ pub mod tests { let nonces = vec![0_u128.into(), 0_u128.into()]; let instruction = 1337; let message = Message::try_new( - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), vec![addr1, addr2], nonces, instruction, @@ -168,7 +169,7 @@ pub mod tests { let nonces = vec![0_u128.into(), 0_u128.into()]; let instruction = 1337; let message = Message::try_new( - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), vec![addr1, addr1], nonces, instruction, @@ -188,7 +189,7 @@ pub mod tests { let nonces = vec![0_u128.into()]; let instruction = 1337; let message = Message::try_new( - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), vec![addr1, addr2], nonces, instruction, @@ -208,7 +209,7 @@ pub mod tests { let nonces = vec![0_u128.into(), 0_u128.into()]; let instruction = 1337; let message = Message::try_new( - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), vec![addr1, addr2], nonces, instruction, @@ -229,7 +230,7 @@ pub mod tests { let nonces = vec![0_u128.into(), 1_u128.into()]; let instruction = 1337; let message = Message::try_new( - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), vec![addr1, addr2], nonces, instruction, @@ -242,6 +243,21 @@ pub mod tests { assert!(matches!(result, Err(LeeError::InvalidInput(_)))); } + #[test] + fn empty_transaction_is_rejected() { + let state = state_for_tests(); + let message = Message::new_preserialized( + crate::test_methods::simple_balance_transfer().id(), + vec![], + vec![], + vec![0; 4], + ); + let witness_set = WitnessSet::from_raw_parts(vec![]); + let tx = PublicTransaction::new(message, witness_set); + let result = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0); + assert!(matches!(result, Err(LeeError::InvalidInput(_)))); + } + #[test] fn program_id_must_belong_to_bulitin_program_ids() { let (key1, key2, addr1, addr2) = keys_for_tests(); diff --git a/lee/state_machine/src/state.rs b/lee/state_machine/src/state.rs index 0423dd02..1ac6ad33 100644 --- a/lee/state_machine/src/state.rs +++ b/lee/state_machine/src/state.rs @@ -1,15 +1,10 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh::{BorshDeserialize, BorshSerialize}; -use clock_core::ClockAccountData; -pub use clock_core::{ - CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, - CLOCK_PROGRAM_ACCOUNT_IDS, -}; use lee_core::{ BlockId, Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, Nullifier, Timestamp, - account::{Account, AccountId, Nonce}, + account::{Account, AccountId}, program::ProgramId, }; @@ -124,18 +119,15 @@ pub struct V03State { impl Default for V03State { fn default() -> Self { - let faucet_account_id = system_faucet_account_id(); - let faucet_account = system_faucet_account(); - let bridge_account_id = system_bridge_account_id(); - let bridge_account = system_bridge_account(); - let mut public_state = HashMap::new(); - public_state.insert(faucet_account_id, faucet_account); - public_state.insert(bridge_account_id, bridge_account); + let mut commitment_set = CommitmentSet::with_capacity(32); + commitment_set.extend(&[DUMMY_COMMITMENT]); + let nullifier_set = NullifierSet::new(); + let private_state = (commitment_set, nullifier_set); Self { - public_state, - private_state: (CommitmentSet::with_capacity(32), NullifierSet::new()), - programs: HashMap::new(), + public_state: HashMap::default(), + private_state, + programs: HashMap::default(), } } } @@ -146,108 +138,70 @@ impl V03State { Self::default() } + /// Initializes state with given public account balances leaving other account fields at their + /// default values. #[must_use] - pub fn new_with_genesis_accounts( - initial_data: &[(AccountId, u128)], - initial_private_accounts: Vec<(Commitment, Nullifier)>, - genesis_timestamp: lee_core::Timestamp, + pub fn with_public_account_balances( + mut self, + balances: impl IntoIterator, ) -> Self { - let faucet_account_id = system_faucet_account_id(); - let bridge_account_id = system_bridge_account_id(); - let authenticated_transfer_program = Program::authenticated_transfer_program(); - let mut public_state: HashMap<_, _> = initial_data - .iter() - .copied() - .map(|(account_id, balance)| { - let account = Account { - balance, - program_owner: authenticated_transfer_program.id(), - ..Account::default() - }; - (account_id, account) - }) - .collect(); - let faucet_account = system_faucet_account(); - let bridge_account = system_bridge_account(); - public_state.insert(faucet_account_id, faucet_account); - public_state.insert(bridge_account_id, bridge_account); - - let mut commitment_set = CommitmentSet::with_capacity(32); - commitment_set.extend(&[DUMMY_COMMITMENT]); - let (commitments, nullifiers): (Vec, Vec) = - initial_private_accounts.into_iter().unzip(); - commitment_set.extend(&commitments); - let mut nullifier_set = NullifierSet::new(); - nullifier_set.extend(&nullifiers); - let private_state = (commitment_set, nullifier_set); - - let mut this = Self { - public_state, - private_state, - programs: HashMap::new(), - }; - - this.insert_program(Program::clock()); - this.insert_clock_accounts(genesis_timestamp); - - this.insert_program(Program::authenticated_transfer_program()); - this.insert_program(Program::token()); - this.insert_program(Program::amm()); - this.insert_program(Program::ata()); - this.insert_program(Program::vault()); - this.insert_program(Program::faucet()); - this.insert_program(Program::bridge()); - this.insert_program(Program::cross_zone_outbox()); - this.insert_program(Program::cross_zone_inbox()); - this.insert_program(Program::ping_sender()); - this.insert_program(Program::ping_receiver()); - this.insert_program(Program::bridge_lock()); - this.insert_program(Program::wrapped_token()); - - // Seed the wrapped-token config with its authorized minter (the cross-zone - // inbox), so the guest can pin its caller without importing the inbox id. - let wrapped_token_id = Program::wrapped_token().id(); - this.public_state.insert( - wrapped_token_core::config_account_id(wrapped_token_id), - Account { - program_owner: wrapped_token_id, - data: wrapped_token_core::minter_bytes(Program::cross_zone_inbox().id()) - .to_vec() - .try_into() - .expect("minter id fits in account data"), - ..Account::default() - }, - ); - - this - } - - fn insert_clock_accounts(&mut self, genesis_timestamp: lee_core::Timestamp) { - let data = ClockAccountData { - block_id: 0, - timestamp: genesis_timestamp, - } - .to_bytes(); - let clock_program_id = Program::clock().id(); - for account_id in CLOCK_PROGRAM_ACCOUNT_IDS { - self.public_state.insert( + let public_accounts = balances.into_iter().map(|(account_id, balance)| { + ( account_id, Account { - program_owner: clock_program_id, - data: data - .clone() - .try_into() - .expect("Clock account data should fit within accounts data"), + balance, ..Account::default() }, - ); + ) + }); + self.public_state.extend(public_accounts); + self + } + + /// Initializes state with given public accounts. + #[must_use] + pub fn with_public_accounts( + mut self, + public_accounts: impl IntoIterator, + ) -> Self { + self.public_state.extend(public_accounts); + self + } + + /// Initializes state with given private accounts. + #[must_use] + pub fn with_private_accounts( + mut self, + private_accounts: impl IntoIterator, + ) -> Self { + let (commitments, nullifiers): (Vec, Vec) = + private_accounts.into_iter().unzip(); + self.private_state.0.extend(&commitments); + self.private_state.1.extend(&nullifiers); + self + } + + /// Initializes state with given builtin programs. + #[must_use] + pub fn with_programs(mut self, programs: impl IntoIterator) -> Self { + for program in programs { + self.insert_program(program); } + self } pub(crate) fn insert_program(&mut self, program: Program) { self.programs.insert(program.id(), program); } + /// Seeds a single genesis account that is not produced by any transaction + /// (e.g. the cross-zone inbox config or a bridge-lock holding). Lets the + /// sequencer and indexer seed identical zone-specific state after building + /// the shared initial state. + pub fn insert_genesis_account(&mut self, account_id: AccountId, account: Account) { + self.public_state.insert(account_id, account); + } + pub fn apply_state_diff(&mut self, diff: ValidatedStateDiff) { let StateDiff { signer_account_ids, @@ -363,45 +317,6 @@ impl V03State { } } -// TODO: Testnet only. Refactor to prevent compilation on mainnet. -impl V03State { - pub fn add_pinata_program(&mut self, account_id: AccountId) { - self.insert_program(Program::pinata()); - - self.public_state.insert( - account_id, - Account { - program_owner: Program::pinata().id(), - balance: 1_500_000, - // Difficulty: 3 - data: vec![3; 33].try_into().expect("should fit"), - nonce: Nonce::default(), - }, - ); - } - - pub fn add_pinata_token_program(&mut self, account_id: AccountId) { - self.insert_program(Program::pinata_token()); - - self.public_state.insert( - account_id, - Account { - program_owner: Program::pinata_token().id(), - // Difficulty: 3 - data: vec![3; 33].try_into().expect("should fit"), - ..Account::default() - }, - ); - } - - /// Inserts an account directly into genesis state, bypassing execution. - /// Genesis-only: used to seed configuration accounts that are not produced by - /// any transaction. Must never be reachable from transaction processing. - pub fn insert_genesis_account(&mut self, account_id: AccountId, account: Account) { - self.public_state.insert(account_id, account); - } -} - #[cfg(any(test, feature = "test-utils"))] impl V03State { pub fn force_insert_account(&mut self, account_id: AccountId, account: Account) { @@ -409,31 +324,6 @@ impl V03State { } } -fn system_faucet_account() -> Account { - Account { - program_owner: Program::authenticated_transfer_program().id(), - balance: u128::MAX, - ..Account::default() - } -} - -fn system_bridge_account() -> Account { - Account { - program_owner: Program::authenticated_transfer_program().id(), - ..Account::default() - } -} - -#[must_use] -pub fn system_faucet_account_id() -> AccountId { - faucet_core::compute_faucet_account_id(Program::faucet().id()) -} - -#[must_use] -pub fn system_bridge_account_id() -> AccountId { - bridge_core::compute_bridge_account_id(Program::bridge().id()) -} - #[cfg(test)] pub mod tests { #![expect( @@ -444,15 +334,14 @@ pub mod tests { use std::collections::HashMap; - use authenticated_transfer_core::Instruction as AuthTransferInstruction; use lee_core::{ BlockId, Commitment, EncryptedAccountData, InputAccountIdentity, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Timestamp, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, encryption::{EphemeralPublicKey, ViewingPublicKey}, program::{ - BlockValidityWindow, ExecutionValidationError, PdaSeed, ProgramId, - TimestampValidityWindow, WrappedBalanceSum, + BlockValidityWindow, ExecutionValidationError, MAX_NUMBER_CHAINED_CALLS, PdaSeed, + ProgramId, TimestampValidityWindow, WrappedBalanceSum, }, }; @@ -469,37 +358,41 @@ pub mod tests { program::Program, public_transaction, signature::PrivateKey, - state::{ - CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, - CLOCK_PROGRAM_ACCOUNT_IDS, MAX_NUMBER_CHAINED_CALLS, system_bridge_account, - system_faucet_account, - }, - system_bridge_account_id, system_faucet_account_id, }; impl V03State { /// Include test programs in the builtin programs map. #[must_use] pub fn with_test_programs(mut self) -> Self { - self.insert_program(Program::nonce_changer_program()); - self.insert_program(Program::extra_output_program()); - self.insert_program(Program::missing_output_program()); - self.insert_program(Program::program_owner_changer()); - self.insert_program(Program::simple_balance_transfer()); - self.insert_program(Program::data_changer()); - self.insert_program(Program::minter()); - self.insert_program(Program::burner()); - self.insert_program(Program::chain_caller()); - self.insert_program(Program::amm()); - self.insert_program(Program::claimer()); - self.insert_program(Program::changer_claimer()); - self.insert_program(Program::validity_window()); - self.insert_program(Program::flash_swap_initiator()); - self.insert_program(Program::flash_swap_callback()); - self.insert_program(Program::malicious_self_program_id()); - self.insert_program(Program::malicious_caller_program_id()); - self.insert_program(Program::time_locked_transfer()); - self.insert_program(Program::pinata_cooldown()); + self.insert_program(crate::test_methods::simple_balance_transfer()); + self.insert_program(crate::test_methods::nonce_changer()); + self.insert_program(crate::test_methods::extra_output()); + self.insert_program(crate::test_methods::missing_output()); + self.insert_program(crate::test_methods::program_owner_changer()); + self.insert_program(crate::test_methods::data_changer()); + self.insert_program(crate::test_methods::minter()); + self.insert_program(crate::test_methods::burner()); + self.insert_program(crate::test_methods::auth_asserting_noop()); + self.insert_program(crate::test_methods::private_pda_delegator()); + self.insert_program(crate::test_methods::pda_claimer()); + self.insert_program(crate::test_methods::two_pda_claimer()); + self.insert_program(crate::test_methods::noop()); + self.insert_program(crate::test_methods::chain_caller()); + self.insert_program(crate::test_methods::modified_transfer_program()); + self.insert_program(crate::test_methods::malicious_authorization_changer()); + self.insert_program(crate::test_methods::validity_window()); + self.insert_program(crate::test_methods::flash_swap_initiator()); + self.insert_program(crate::test_methods::flash_swap_callback()); + self.insert_program(crate::test_methods::malicious_self_program_id()); + self.insert_program(crate::test_methods::malicious_caller_program_id()); + self.insert_program(crate::test_methods::pda_spend_proxy()); + self.insert_program(crate::test_methods::claimer()); + self.insert_program(crate::test_methods::changer_claimer()); + self.insert_program(crate::test_methods::validity_window_chain_caller()); + self.insert_program(crate::test_methods::simple_transfer_proxy()); + self.insert_program(crate::test_methods::malicious_injector()); + self.insert_program(crate::test_methods::malicious_launderer()); + self.insert_program(crate::test_methods::modified_transfer_program()); self } @@ -535,7 +428,7 @@ pub mod tests { #[must_use] pub fn with_account_owned_by_burner_program(mut self) -> Self { let account = Account { - program_owner: Program::burner().id(), + program_owner: crate::test_methods::burner().id(), balance: 100, ..Default::default() }; @@ -600,6 +493,25 @@ pub mod tests { }, } + fn public_state_from_balances( + initial_data: &[(AccountId, u128)], + ) -> HashMap { + initial_data + .iter() + .copied() + .map(|(account_id, balance)| { + ( + account_id, + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance, + ..Account::default() + }, + ) + }) + .collect() + } + fn transfer_transaction( from: AccountId, from_key: &PrivateKey, @@ -611,14 +523,9 @@ pub mod tests { ) -> PublicTransaction { let account_ids = vec![from, to]; let nonces = vec![Nonce(from_nonce), Nonce(to_nonce)]; - let program_id = Program::authenticated_transfer_program().id(); - let message = public_transaction::Message::try_new( - program_id, - account_ids, - nonces, - AuthTransferInstruction::Transfer { amount: balance }, - ) - .unwrap(); + let program_id = crate::test_methods::simple_balance_transfer().id(); + let message = + public_transaction::Message::try_new(program_id, account_ids, nonces, balance).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key, to_key]); PublicTransaction::new(message, witness_set) @@ -642,63 +549,17 @@ pub mod tests { } #[test] - fn genesis_system_accounts_have_expected_contents() { - // System-account IDs must be distinct and non-default, and the genesis - // faucet/bridge accounts must carry their expected field values. Catches - // mutations that replace `system_faucet_account`/`system_bridge_account` - // with `Default::default()`, delete their `balance`/`program_owner` - // fields, or replace `system_bridge_account_id` with `Default::default()`. - let faucet_id = system_faucet_account_id(); - let bridge_id = system_bridge_account_id(); - assert_ne!(bridge_id, AccountId::default()); - assert_ne!(faucet_id, bridge_id); - - let state = V03State::new_with_genesis_accounts(&[], vec![], 0); - let default_owner = Account::default().program_owner; - - let faucet = state.get_account_by_id(faucet_id); - assert_eq!(faucet.balance, u128::MAX, "faucet must hold u128::MAX"); - assert_ne!( - faucet.program_owner, default_owner, - "faucet must have a non-default program_owner" - ); - - let bridge = state.get_account_by_id(bridge_id); - assert_ne!( - bridge.program_owner, default_owner, - "bridge must have a non-default program_owner" - ); - } - - #[test] - fn genesis_commitment_set_digest_differs_from_empty_state() { - // The genesis state inserts DUMMY_COMMITMENT, so its commitment-set digest - // must differ from a freshly-created empty state's all-zero root. Catches - // the mutation that replaces `commitment_set_digest` with `Default::default()`. - let genesis = V03State::new_with_genesis_accounts(&[], vec![], 0); - let empty = V03State::new(); - assert_ne!( - genesis.commitment_set_digest(), - empty.commitment_set_digest() - ); - } - - #[test] - fn new_with_genesis() { + fn new_works() { let key1 = PrivateKey::try_new([1; 32]).unwrap(); let key2 = PrivateKey::try_new([2; 32]).unwrap(); let addr1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); let addr2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); - let initial_data = [(addr1, 100_u128), (addr2, 151_u128)]; - let authenticated_transfers_program = Program::authenticated_transfer_program(); - let clock_program = Program::clock(); let expected_public_state = { let mut this = HashMap::new(); this.insert( addr1, Account { balance: 100, - program_owner: authenticated_transfers_program.id(), ..Account::default() }, ); @@ -706,77 +567,27 @@ pub mod tests { addr2, Account { balance: 151, - program_owner: authenticated_transfers_program.id(), - ..Account::default() - }, - ); - this.insert(system_faucet_account_id(), system_faucet_account()); - this.insert(system_bridge_account_id(), system_bridge_account()); - for account_id in CLOCK_PROGRAM_ACCOUNT_IDS { - this.insert( - account_id, - Account { - program_owner: clock_program.id(), - data: [0_u8; 16].to_vec().try_into().unwrap(), - ..Account::default() - }, - ); - } - this.insert( - wrapped_token_core::config_account_id(Program::wrapped_token().id()), - Account { - program_owner: Program::wrapped_token().id(), - data: wrapped_token_core::minter_bytes(Program::cross_zone_inbox().id()) - .to_vec() - .try_into() - .unwrap(), ..Account::default() }, ); this }; - let expected_builtin_programs = { - let mut this = HashMap::new(); - this.insert( - authenticated_transfers_program.id(), - authenticated_transfers_program, - ); - this.insert(clock_program.id(), clock_program); - this.insert(Program::token().id(), Program::token()); - this.insert(Program::amm().id(), Program::amm()); - this.insert(Program::ata().id(), Program::ata()); - this.insert(Program::vault().id(), Program::vault()); - this.insert(Program::faucet().id(), Program::faucet()); - this.insert(Program::bridge().id(), Program::bridge()); - this.insert( - Program::cross_zone_outbox().id(), - Program::cross_zone_outbox(), - ); - this.insert( - Program::cross_zone_inbox().id(), - Program::cross_zone_inbox(), - ); - this.insert(Program::ping_sender().id(), Program::ping_sender()); - this.insert(Program::ping_receiver().id(), Program::ping_receiver()); - this.insert(Program::bridge_lock().id(), Program::bridge_lock()); - this.insert(Program::wrapped_token().id(), Program::wrapped_token()); - this - }; + let expected_builtin_programs = HashMap::new(); - let state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); + let state = + V03State::new().with_public_account_balances([(addr1, 100_u128), (addr2, 151_u128)]); assert_eq!(state.public_state, expected_public_state); assert_eq!(state.programs, expected_builtin_programs); } #[test] - fn new_with_genesis_includes_nullifiers_for_private_accounts() { + fn new_includes_nullifiers_for_private_accounts() { let keys1 = test_private_account_keys_1(); let keys2 = test_private_account_keys_2(); let account = Account { balance: 100, - program_owner: Program::authenticated_transfer_program().id(), ..Account::default() }; @@ -793,7 +604,7 @@ pub mod tests { (init_commitment2, init_nullifier2), ]; - let state = V03State::new_with_genesis_accounts(&[], initial_private_accounts, 0); + let state = V03State::new().with_private_accounts(initial_private_accounts); assert!(state.private_state.1.contains(&init_nullifier1)); assert!(state.private_state.1.contains(&init_nullifier2)); @@ -801,8 +612,8 @@ pub mod tests { #[test] fn insert_program() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - let program_to_insert = Program::simple_balance_transfer(); + let mut state = V03State::new(); + let program_to_insert = crate::test_methods::simple_balance_transfer(); let program_id = program_to_insert.id(); assert!(!state.programs.contains_key(&program_id)); @@ -815,8 +626,15 @@ pub mod tests { fn get_account_by_account_id_non_default_account() { let key = PrivateKey::try_new([1; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); - let initial_data = [(account_id, 100_u128)]; - let state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); + let initial_data = [( + account_id, + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance: 100, + ..Account::default() + }, + )]; + let state = V03State::new().with_public_accounts(initial_data); let expected_account = &state.public_state[&account_id]; let account = state.get_account_by_id(account_id); @@ -827,7 +645,7 @@ pub mod tests { #[test] fn get_account_by_account_id_default_account() { let addr2 = AccountId::new([0; 32]); - let state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let state = V03State::new(); let expected_account = Account::default(); let account = state.get_account_by_id(addr2); @@ -837,7 +655,7 @@ pub mod tests { #[test] fn builtin_programs_getter() { - let state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let state = V03State::new(); let builtin_programs = state.programs(); @@ -848,8 +666,17 @@ pub mod tests { fn transition_from_authenticated_transfer_program_invocation_default_account_destination() { let key = PrivateKey::try_new([1; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); - let initial_data = [(account_id, 100)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); + let initial_data = [( + account_id, + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance: 100, + ..Account::default() + }, + )]; + let mut state = V03State::new() + .with_public_accounts(initial_data) + .with_test_programs(); let from = account_id; let to_key = PrivateKey::try_new([2; 32]).unwrap(); let to = AccountId::from(&PublicKey::new_from_private_key(&to_key)); @@ -869,8 +696,9 @@ pub mod tests { fn transition_from_authenticated_transfer_program_invocation_insuficient_balance() { let key = PrivateKey::try_new([1; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&key)); - let initial_data = [(account_id, 100)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); + let mut state = V03State::new() + .with_public_account_balances([(account_id, 100)]) + .with_test_programs(); let from = account_id; let from_key = key; let to_key = PrivateKey::try_new([2; 32]).unwrap(); @@ -894,8 +722,27 @@ pub mod tests { let key2 = PrivateKey::try_new([2; 32]).unwrap(); let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); - let initial_data = [(account_id1, 100), (account_id2, 200)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); + let initial_data = [ + ( + account_id1, + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance: 100, + ..Account::default() + }, + ), + ( + account_id2, + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance: 200, + ..Account::default() + }, + ), + ]; + let mut state = V03State::new() + .with_public_accounts(initial_data) + .with_test_programs(); let from = account_id2; let from_key = key2; let to = account_id1; @@ -918,8 +765,17 @@ pub mod tests { let account_id1 = AccountId::from(&PublicKey::new_from_private_key(&key1)); let key2 = PrivateKey::try_new([2; 32]).unwrap(); let account_id2 = AccountId::from(&PublicKey::new_from_private_key(&key2)); - let initial_data = [(account_id1, 100)]; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); + let initial_data = [( + account_id1, + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance: 100, + ..Account::default() + }, + )]; + let mut state = V03State::new() + .with_public_accounts(initial_data) + .with_test_programs(); let key3 = PrivateKey::try_new([3; 32]).unwrap(); let account_id3 = AccountId::from(&PublicKey::new_from_private_key(&key3)); let balance_to_move = 5; @@ -954,157 +810,14 @@ pub mod tests { assert_eq!(state.get_account_by_id(account_id3).nonce, Nonce(1)); } - fn clock_transaction(timestamp: lee_core::Timestamp) -> PublicTransaction { - let message = public_transaction::Message::try_new( - Program::clock().id(), - CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), - vec![], - timestamp, - ) - .unwrap(); - PublicTransaction::new( - message, - public_transaction::WitnessSet::from_raw_parts(vec![]), - ) - } - - fn clock_account_data(state: &V03State, account_id: AccountId) -> (u64, lee_core::Timestamp) { - let data = state.get_account_by_id(account_id).data.into_inner(); - let parsed = clock_core::ClockAccountData::from_bytes(&data); - (parsed.block_id, parsed.timestamp) - } - - #[test] - fn clock_genesis_state_has_zero_block_id_and_genesis_timestamp() { - let genesis_timestamp = 1_000_000_u64; - let state = V03State::new_with_genesis_accounts(&[], vec![], genesis_timestamp); - - let (block_id, timestamp) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID); - - assert_eq!(block_id, 0); - assert_eq!(timestamp, genesis_timestamp); - } - - #[test] - fn clock_invocation_increments_block_id() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - let tx = clock_transaction(1234); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - - let (block_id, _) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID); - assert_eq!(block_id, 1); - } - - #[test] - fn clock_invocation_stores_timestamp_from_instruction() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - let block_timestamp = 1_700_000_000_000_u64; - - let tx = clock_transaction(block_timestamp); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - - let (_, timestamp) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID); - assert_eq!(timestamp, block_timestamp); - } - - #[test] - fn clock_invocation_sequence_correctly_increments_block_id() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - for expected_block_id in 1_u64..=5 { - let tx = clock_transaction(expected_block_id * 1000); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - - let (block_id, timestamp) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID); - assert_eq!(block_id, expected_block_id); - assert_eq!(timestamp, expected_block_id * 1000); - } - } - - #[test] - fn clock_10_account_not_updated_when_block_id_not_multiple_of_10() { - let genesis_timestamp = 0_u64; - let mut state = V03State::new_with_genesis_accounts(&[], vec![], genesis_timestamp); - - // Run 9 clock ticks (block_ids 1..=9), none of which are multiples of 10. - for tick in 1_u64..=9 { - let tx = clock_transaction(tick * 1000); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - } - - let (block_id_10, timestamp_10) = clock_account_data(&state, CLOCK_10_PROGRAM_ACCOUNT_ID); - // The 10-block account should still reflect genesis state. - assert_eq!(block_id_10, 0); - assert_eq!(timestamp_10, genesis_timestamp); - } - - #[test] - fn clock_10_account_updated_when_block_id_is_multiple_of_10() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - // Run 10 clock ticks so block_id reaches 10. - for tick in 1_u64..=10 { - let tx = clock_transaction(tick * 1000); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - } - - let (block_id_1, timestamp_1) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID); - let (block_id_10, timestamp_10) = clock_account_data(&state, CLOCK_10_PROGRAM_ACCOUNT_ID); - assert_eq!(block_id_1, 10); - assert_eq!(block_id_10, 10); - assert_eq!(timestamp_10, timestamp_1); - } - - #[test] - fn clock_50_account_only_updated_at_multiples_of_50() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - // After 49 ticks the 50-block account should be unchanged. - for tick in 1_u64..=49 { - let tx = clock_transaction(tick * 1000); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - } - let (block_id_50, _) = clock_account_data(&state, CLOCK_50_PROGRAM_ACCOUNT_ID); - assert_eq!(block_id_50, 0); - - // Tick 50 โ€” now the 50-block account should update. - let tx = clock_transaction(50 * 1000); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - let (block_id_50, timestamp_50) = clock_account_data(&state, CLOCK_50_PROGRAM_ACCOUNT_ID); - assert_eq!(block_id_50, 50); - assert_eq!(timestamp_50, 50 * 1000); - } - - #[test] - fn all_three_clock_accounts_updated_at_multiple_of_50() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - - // Advance to block 50 (a multiple of both 10 and 50). - for tick in 1_u64..=50 { - let tx = clock_transaction(tick * 1000); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - } - - let (block_id_1, ts_1) = clock_account_data(&state, CLOCK_01_PROGRAM_ACCOUNT_ID); - let (block_id_10, ts_10) = clock_account_data(&state, CLOCK_10_PROGRAM_ACCOUNT_ID); - let (block_id_50, ts_50) = clock_account_data(&state, CLOCK_50_PROGRAM_ACCOUNT_ID); - - assert_eq!(block_id_1, 50); - assert_eq!(block_id_10, 50); - assert_eq!(block_id_50, 50); - assert_eq!(ts_1, ts_10); - assert_eq!(ts_1, ts_50); - } - #[test] fn program_should_fail_if_modifies_nonces() { let account_id = AccountId::new([1; 32]); - let initial_data = [(account_id, 100)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_account_balances([(account_id, 100)]) + .with_test_programs(); let account_ids = vec![account_id]; - let program_id = Program::nonce_changer_program().id(); + let program_id = crate::test_methods::nonce_changer().id(); let message = public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); @@ -1124,11 +837,11 @@ pub mod tests { #[test] fn program_should_fail_if_output_accounts_exceed_inputs() { - let initial_data = [(AccountId::new([1; 32]), 100)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_account_balances([(AccountId::new([1; 32]), 0)]) + .with_test_programs(); let account_ids = vec![AccountId::new([1; 32])]; - let program_id = Program::extra_output_program().id(); + let program_id = crate::test_methods::extra_output().id(); let message = public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); @@ -1151,11 +864,11 @@ pub mod tests { #[test] fn program_should_fail_with_missing_output_accounts() { - let initial_data = [(AccountId::new([1; 32]), 100)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_account_balances([(AccountId::new([1; 32]), 100)]) + .with_test_programs(); let account_ids = vec![AccountId::new([1; 32]), AccountId::new([2; 32])]; - let program_id = Program::missing_output_program().id(); + let program_id = crate::test_methods::missing_output().id(); let message = public_transaction::Message::try_new(program_id, account_ids, vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); @@ -1178,9 +891,16 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_program_owner_with_only_non_default_program_owner() { - let initial_data = [(AccountId::new([1; 32]), 0)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let initial_data = [( + AccountId::new([1; 32]), + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + ..Account::default() + }, + )]; + let mut state = V03State::new() + .with_public_accounts(initial_data) + .with_test_programs(); let account_id = AccountId::new([1; 32]); let account = state.get_account_by_id(account_id); // Assert the target account only differs from the default account in the program owner @@ -1189,7 +909,7 @@ pub mod tests { assert_eq!(account.balance, Account::default().balance); assert_eq!(account.nonce, Account::default().nonce); assert_eq!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); + let program_id = crate::test_methods::program_owner_changer().id(); let message = public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); @@ -1207,8 +927,9 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_program_owner_with_only_non_default_balance() { - let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0) + let initial_data = HashMap::new(); + let mut state = V03State::new() + .with_public_accounts(initial_data) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([255; 32]); @@ -1218,7 +939,7 @@ pub mod tests { assert_ne!(account.balance, Account::default().balance); assert_eq!(account.nonce, Account::default().nonce); assert_eq!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); + let program_id = crate::test_methods::program_owner_changer().id(); let message = public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); @@ -1236,8 +957,9 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_program_owner_with_only_non_default_nonce() { - let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0) + let initial_data = HashMap::new(); + let mut state = V03State::new() + .with_public_accounts(initial_data) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([254; 32]); @@ -1247,7 +969,7 @@ pub mod tests { assert_eq!(account.balance, Account::default().balance); assert_ne!(account.nonce, Account::default().nonce); assert_eq!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); + let program_id = crate::test_methods::program_owner_changer().id(); let message = public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); @@ -1265,8 +987,9 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_program_owner_with_only_non_default_data() { - let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0) + let initial_data = HashMap::new(); + let mut state = V03State::new() + .with_public_accounts(initial_data) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([253; 32]); @@ -1276,7 +999,7 @@ pub mod tests { assert_eq!(account.balance, Account::default().balance); assert_eq!(account.nonce, Account::default().nonce); assert_ne!(account.data, Account::default().data); - let program_id = Program::program_owner_changer().id(); + let program_id = crate::test_methods::program_owner_changer().id(); let message = public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); @@ -1296,11 +1019,11 @@ pub mod tests { fn program_should_fail_if_transfers_balance_from_non_owned_account() { let sender_account_id = AccountId::new([1; 32]); let receiver_account_id = AccountId::new([2; 32]); - let initial_data = [(sender_account_id, 100)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_account_balances([(sender_account_id, 100)]) + .with_test_programs(); let balance_to_move: u128 = 1; - let program_id = Program::simple_balance_transfer().id(); + let program_id = crate::test_methods::simple_balance_transfer().id(); assert_ne!( state.get_account_by_id(sender_account_id).program_owner, program_id @@ -1327,12 +1050,13 @@ pub mod tests { #[test] fn program_should_fail_if_modifies_data_of_non_owned_account() { - let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0) + let initial_data = HashMap::new(); + let mut state = V03State::new() + .with_public_accounts(initial_data) .with_test_programs() .with_non_default_accounts_but_default_program_owners(); let account_id = AccountId::new([255; 32]); - let program_id = Program::data_changer().id(); + let program_id = crate::test_methods::data_changer().id(); assert_ne!(state.get_account_by_id(account_id), Account::default()); assert_ne!( @@ -1357,11 +1081,12 @@ pub mod tests { #[test] fn program_should_fail_if_does_not_preserve_total_balance_by_minting() { - let initial_data = []; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let initial_data = HashMap::new(); + let mut state = V03State::new() + .with_public_accounts(initial_data) + .with_test_programs(); let account_id = AccountId::new([1; 32]); - let program_id = Program::minter().id(); + let program_id = crate::test_methods::minter().id(); let message = public_transaction::Message::try_new(program_id, vec![account_id], vec![], ()).unwrap(); @@ -1380,11 +1105,12 @@ pub mod tests { #[test] fn program_should_fail_if_does_not_preserve_total_balance_by_burning() { - let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0) + let initial_data = HashMap::new(); + let mut state = V03State::new() + .with_public_accounts(initial_data) .with_test_programs() .with_account_owned_by_burner_program(); - let program_id = Program::burner().id(); + let program_id = crate::test_methods::burner().id(); let account_id = AccountId::new([252; 32]); assert_eq!( state.get_account_by_id(account_id).program_owner, @@ -1462,10 +1188,7 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender, recipient], - Program::serialize_instruction(AuthTransferInstruction::Transfer { - amount: balance_to_move, - }) - .unwrap(), + Program::serialize_instruction(balance_to_move).unwrap(), vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivateUnauthorized { @@ -1479,7 +1202,7 @@ pub mod tests { identifier: 0, }, ], - &Program::authenticated_transfer_program().into(), + &crate::test_methods::simple_balance_transfer().into(), ) .unwrap(); @@ -1501,7 +1224,7 @@ pub mod tests { balance_to_move: u128, state: &V03State, ) -> PrivacyPreservingTransaction { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_pre = AccountWithMetadata::new( @@ -1520,10 +1243,7 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], - Program::serialize_instruction(AuthTransferInstruction::Transfer { - amount: balance_to_move, - }) - .unwrap(), + Program::serialize_instruction(balance_to_move).unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { epk: epk_1, @@ -1567,7 +1287,7 @@ pub mod tests { balance_to_move: u128, state: &V03State, ) -> PrivacyPreservingTransaction { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_pre = AccountWithMetadata::new( @@ -1586,10 +1306,7 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], - Program::serialize_instruction(AuthTransferInstruction::Transfer { - amount: balance_to_move, - }) - .unwrap(), + Program::serialize_instruction(balance_to_move).unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { epk, @@ -1623,8 +1340,14 @@ pub mod tests { let sender_keys = test_public_account_keys_1(); let recipient_keys = test_private_account_keys_1(); - let mut state = - V03State::new_with_genesis_accounts(&[(sender_keys.account_id(), 200)], vec![], 0); + let mut state = V03State::new().with_public_accounts([( + sender_keys.account_id(), + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance: 200, + ..Account::default() + }, + )]); let balance_to_move = 37; @@ -1665,15 +1388,14 @@ pub mod tests { let sender_nonce = Nonce(0xdead_beef); let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: 100, nonce: sender_nonce, data: Data::default(), }; let recipient_keys = test_private_account_keys_2(); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0) - .with_private_account(&sender_keys, &sender_private_account); + let mut state = V03State::new().with_private_account(&sender_keys, &sender_private_account); let balance_to_move = 37; @@ -1690,7 +1412,7 @@ pub mod tests { let expected_new_commitment_1 = Commitment::new( &sender_account_id, &Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), nonce: sender_nonce.private_account_nonce_increment(&sender_keys.nsk), balance: sender_private_account.balance - balance_to_move, data: Data::default(), @@ -1704,7 +1426,7 @@ pub mod tests { let expected_new_commitment_2 = Commitment::new( &recipient_account_id, &Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), nonce: Nonce::private_account_nonce_init(&recipient_account_id), balance: balance_to_move, ..Account::default() @@ -1731,14 +1453,13 @@ pub mod tests { fn valid_private_transfer_tx_and_state() -> (V03State, PrivacyPreservingTransaction) { let sender_keys = test_private_account_keys_1(); let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: 100, nonce: Nonce(0xdead_beef), ..Account::default() }; let recipient_keys = test_private_account_keys_2(); - let state = V03State::new_with_genesis_accounts(&[], vec![], 0) - .with_private_account(&sender_keys, &sender_private_account); + let state = V03State::new().with_private_account(&sender_keys, &sender_private_account); let tx = private_balance_transfer_for_tests( &sender_keys, &sender_private_account, @@ -1807,19 +1528,23 @@ pub mod tests { let sender_nonce = Nonce(0xdead_beef); let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: 100, nonce: sender_nonce, data: Data::default(), }; let recipient_keys = test_public_account_keys_1(); let recipient_initial_balance = 400; - let mut state = V03State::new_with_genesis_accounts( - &[(recipient_keys.account_id(), recipient_initial_balance)], - vec![], - 0, - ) - .with_private_account(&sender_keys, &sender_private_account); + let mut state = V03State::new() + .with_public_accounts([( + recipient_keys.account_id(), + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance: recipient_initial_balance, + ..Account::default() + }, + )]) + .with_private_account(&sender_keys, &sender_private_account); let balance_to_move = 37; @@ -1841,7 +1566,7 @@ pub mod tests { let expected_new_commitment = Commitment::new( &sender_account_id, &Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), nonce: sender_nonce.private_account_nonce_increment(&sender_keys.nsk), balance: sender_private_account.balance - balance_to_move, data: Data::default(), @@ -1873,7 +1598,7 @@ pub mod tests { #[test] fn burner_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::burner(); + let program = crate::test_methods::burner(); let public_account = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -1896,7 +1621,7 @@ pub mod tests { #[test] fn minter_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::minter(); + let program = crate::test_methods::minter(); let public_account = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -1919,7 +1644,7 @@ pub mod tests { #[test] fn nonce_changer_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::nonce_changer_program(); + let program = crate::test_methods::nonce_changer(); let public_account = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -1942,7 +1667,7 @@ pub mod tests { #[test] fn data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() { - let program = Program::data_changer(); + let program = crate::test_methods::data_changer(); let public_account = AccountWithMetadata::new( Account { program_owner: [0, 1, 2, 3, 4, 5, 6, 7], @@ -1965,7 +1690,7 @@ pub mod tests { #[test] fn data_changer_program_should_fail_for_too_large_data_in_privacy_preserving_circuit() { - let program = Program::data_changer(); + let program = crate::test_methods::data_changer(); let public_account = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -1996,7 +1721,7 @@ pub mod tests { #[test] fn extra_output_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::extra_output_program(); + let program = crate::test_methods::extra_output(); let public_account = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -2019,7 +1744,7 @@ pub mod tests { #[test] fn missing_output_program_should_fail_in_privacy_preserving_circuit() { - let program = Program::missing_output_program(); + let program = crate::test_methods::missing_output(); let public_account_1 = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -2051,7 +1776,7 @@ pub mod tests { #[test] fn program_owner_changer_should_fail_in_privacy_preserving_circuit() { - let program = Program::program_owner_changer(); + let program = crate::test_methods::program_owner_changer(); let public_account = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -2074,7 +1799,7 @@ pub mod tests { #[test] fn transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let public_account_1 = AccountWithMetadata::new( Account { program_owner: [0, 1, 2, 3, 4, 5, 6, 7], @@ -2106,7 +1831,7 @@ pub mod tests { #[test] fn circuit_fails_if_visibility_masks_have_incorrect_lenght() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let public_account_1 = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -2139,7 +1864,7 @@ pub mod tests { #[test] fn circuit_fails_if_invalid_auth_keys_are_provided() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); let private_account_1 = AccountWithMetadata::new( @@ -2202,7 +1927,7 @@ pub mod tests { #[test] fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); let private_account_1 = AccountWithMetadata::new( @@ -2268,7 +1993,7 @@ pub mod tests { #[test] fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); let private_account_1 = AccountWithMetadata::new( @@ -2334,7 +2059,7 @@ pub mod tests { #[test] fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); let private_account_1 = AccountWithMetadata::new( @@ -2400,7 +2125,7 @@ pub mod tests { #[test] fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); let private_account_1 = AccountWithMetadata::new( @@ -2467,7 +2192,7 @@ pub mod tests { #[test] fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let recipient_keys = test_private_account_keys_2(); let private_account_1 = AccountWithMetadata::new( @@ -2534,7 +2259,7 @@ pub mod tests { /// second account, leaving position 1 unbound. #[test] fn private_pda_without_binding_fails() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let shared_secret = @@ -2578,7 +2303,7 @@ pub mod tests { /// and binds the supplied npk to the `account_id`. #[test] fn private_pda_claim_succeeds() { - let program = Program::pda_claimer(); + let program = crate::test_methods::pda_claimer(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); @@ -2616,7 +2341,7 @@ pub mod tests { fn private_pda_npk_mismatch_fails() { // `keys_a` produces the `pre_state`'s `account_id` (the registered pair), `keys_b` is // the mismatched pair supplied in `private_account_keys` for that pre_state. - let program = Program::pda_claimer(); + let program = crate::test_methods::pda_claimer(); let keys_a = test_private_account_keys_1(); let keys_b = test_private_account_keys_2(); let npk_a = keys_a.npk(); @@ -2655,8 +2380,8 @@ pub mod tests { /// `AccountId::for_private_pda(delegator, seed, npk) == pre.account_id`. #[test] fn caller_pda_seeds_authorize_private_pda_for_callee() { - let delegator = Program::private_pda_delegator(); - let callee = Program::auth_asserting_noop(); + let delegator = crate::test_methods::private_pda_delegator(); + let callee = crate::test_methods::auth_asserting_noop(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([77; 32]); @@ -2696,8 +2421,8 @@ pub mod tests { /// assertion rejects. #[test] fn caller_pda_seeds_with_wrong_seed_rejects_private_pda_for_callee() { - let delegator = Program::private_pda_delegator(); - let callee = Program::auth_asserting_noop(); + let delegator = crate::test_methods::private_pda_delegator(); + let callee = crate::test_methods::auth_asserting_noop(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let claim_seed = PdaSeed::new([77; 32]); @@ -2739,7 +2464,7 @@ pub mod tests { /// tries to record `(program, seed) โ†’ PDA_bob` and panics. #[test] fn two_private_pda_claims_under_same_seed_are_rejected() { - let program = Program::two_pda_claimer(); + let program = crate::test_methods::two_pda_claimer(); let keys_a = test_private_account_keys_1(); let keys_b = test_private_account_keys_2(); let seed = PdaSeed::new([55; 32]); @@ -2786,7 +2511,7 @@ pub mod tests { /// the correct path for top-level reuse; this test pins the failure when no seed is provided. #[test] fn private_pda_top_level_reuse_rejected_by_binding_check() { - let program = Program::noop(); + let program = crate::test_methods::noop(); let keys = test_private_account_keys_1(); let npk = keys.npk(); let shared_secret = @@ -2828,15 +2553,14 @@ pub mod tests { let sender_nonce = Nonce(0xdead_beef); let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: 100, nonce: sender_nonce, data: Data::default(), }; let recipient_keys = test_private_account_keys_2(); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0) - .with_private_account(&sender_keys, &sender_private_account); + let mut state = V03State::new().with_private_account(&sender_keys, &sender_private_account); let balance_to_move = 37; let balance_to_move_2 = 30; @@ -2854,7 +2578,7 @@ pub mod tests { .unwrap(); let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: 100, nonce: sender_nonce, data: Data::default(), @@ -2880,7 +2604,7 @@ pub mod tests { #[test] fn circuit_should_fail_if_there_are_repeated_ids() { - let program = Program::simple_balance_transfer(); + let program = crate::test_methods::simple_balance_transfer(); let sender_keys = test_private_account_keys_1(); let private_account_1 = AccountWithMetadata::new( Account { @@ -2929,13 +2653,14 @@ pub mod tests { #[test] fn claiming_mechanism() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let from_key = PrivateKey::try_new([1; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&from_key)); let initial_balance = 100; let initial_data = [(from, initial_balance)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let to_key = PrivateKey::try_new([2; 32]).unwrap(); let to = AccountId::from(&PublicKey::new_from_private_key(&to_key)); let amount: u128 = 37; @@ -2954,7 +2679,7 @@ pub mod tests { program.id(), vec![from, to], vec![Nonce(0), Nonce(0)], - AuthTransferInstruction::Transfer { amount }, + amount, ) .unwrap(); let witness_set = @@ -2970,20 +2695,16 @@ pub mod tests { #[test] fn unauthorized_public_account_claiming_fails() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let account_key = PrivateKey::try_new([9; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&account_key)); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let mut state = V03State::new().with_test_programs(); assert_eq!(state.get_account_by_id(account_id), Account::default()); - let message = public_transaction::Message::try_new( - program.id(), - vec![account_id], - vec![], - AuthTransferInstruction::Initialize, - ) - .unwrap(); + let message = + public_transaction::Message::try_new(program.id(), vec![account_id], vec![], 0_u128) + .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let tx = PublicTransaction::new(message, witness_set); @@ -2995,10 +2716,10 @@ pub mod tests { #[test] fn authorized_public_account_claiming_succeeds() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let account_key = PrivateKey::try_new([10; 32]).unwrap(); let account_id = AccountId::from(&PublicKey::new_from_private_key(&account_key)); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let mut state = V03State::new().with_test_programs(); assert_eq!(state.get_account_by_id(account_id), Account::default()); @@ -3006,7 +2727,7 @@ pub mod tests { program.id(), vec![account_id], vec![Nonce(0)], - AuthTransferInstruction::Initialize, + 0_u128, ) .unwrap(); let witness_set = public_transaction::WitnessSet::for_message(&message, &[&account_key]); @@ -3026,25 +2747,26 @@ pub mod tests { #[test] fn public_chained_call() { - let program = Program::chain_caller(); + let program = crate::test_methods::chain_caller(); let key = PrivateKey::try_new([1; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&key)); let to = AccountId::new([2; 32]); let initial_balance = 1000; let initial_data = [(from, initial_balance), (to, 0)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let from_key = key; let amount: u128 = 37; let instruction: (u128, ProgramId, u32, Option) = ( amount, - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), 2, None, ); let expected_to_post = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: amount * 2, // The `chain_caller` chains the program twice ..Account::default() }; @@ -3071,19 +2793,20 @@ pub mod tests { #[test] fn execution_fails_if_chained_calls_exceeds_depth() { - let program = Program::chain_caller(); + let program = crate::test_methods::chain_caller(); let key = PrivateKey::try_new([1; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&key)); let to = AccountId::new([2; 32]); let initial_balance = 100; let initial_data = [(from, initial_balance), (to, 0)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let from_key = key; let amount: u128 = 0; let instruction: (u128, ProgramId, u32, Option) = ( amount, - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), u32::try_from(MAX_NUMBER_CHAINED_CALLS).expect("MAX_NUMBER_CHAINED_CALLS fits in u32") + 1, None, @@ -3109,24 +2832,25 @@ pub mod tests { #[test] fn execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() { - let chain_caller = Program::chain_caller(); + let chain_caller = crate::test_methods::chain_caller(); let pda_seed = PdaSeed::new([37; 32]); let from = AccountId::for_public_pda(&chain_caller.id(), &pda_seed); let to = AccountId::new([2; 32]); let initial_balance = 1000; let initial_data = [(from, initial_balance), (to, 0)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let amount: u128 = 58; let instruction: (u128, ProgramId, u32, Option) = ( amount, - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), 1, Some(pda_seed), ); let expected_to_post = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: amount, // The `chain_caller` chains the program twice ..Account::default() }; @@ -3155,14 +2879,15 @@ pub mod tests { // The transfer is made from an initialized sender to an uninitialized recipient. And // it is expected that the recipient account is claimed by the authenticated transfer // program and not the chained_caller program. - let chain_caller = Program::chain_caller(); - let auth_transfer = Program::authenticated_transfer_program(); + let chain_caller = crate::test_methods::chain_caller(); + let simple_transfer = crate::test_methods::simple_balance_transfer(); let from_key = PrivateKey::try_new([1; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&from_key)); let initial_balance = 100; let initial_data = [(from, initial_balance)]; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let to_key = PrivateKey::try_new([2; 32]).unwrap(); let to = AccountId::from(&PublicKey::new_from_private_key(&to_key)); let amount: u128 = 37; @@ -3172,7 +2897,7 @@ pub mod tests { let expected_to_post = Account { // The expected program owner is the authenticated transfer program - program_owner: auth_transfer.id(), + program_owner: simple_transfer.id(), balance: amount, nonce: Nonce(1), ..Account::default() @@ -3182,7 +2907,7 @@ pub mod tests { // authenticated_transfer program let instruction: (u128, ProgramId, u32, Option) = ( amount, - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), 1, None, ); @@ -3208,13 +2933,13 @@ pub mod tests { #[test] fn unauthorized_public_account_claiming_fails_when_executed_privately() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let account_id = AccountId::new([11; 32]); let public_account = AccountWithMetadata::new(Account::default(), false, account_id); let result = execute_and_prove( vec![public_account], - Program::serialize_instruction(AuthTransferInstruction::Initialize).unwrap(), + Program::serialize_instruction(0_u128).unwrap(), vec![InputAccountIdentity::Public], &program.into(), ); @@ -3224,7 +2949,7 @@ pub mod tests { #[test] fn authorized_public_account_claiming_succeeds_when_executed_privately() { - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); let program_id = program.id(); let sender_keys = test_private_account_keys_1(); let sender_private_account = Account { @@ -3235,11 +2960,8 @@ pub mod tests { let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account); let sender_init_nullifier = Nullifier::for_account_initialization(&sender_account_id); - let mut state = V03State::new_with_genesis_accounts( - &[], - vec![(sender_commitment.clone(), sender_init_nullifier)], - 0, - ); + let mut state = V03State::new() + .with_private_accounts([(sender_commitment.clone(), sender_init_nullifier)]); let sender_pre = AccountWithMetadata::new(sender_private_account, true, (&sender_keys.npk(), 0)); let recipient_private_key = PrivateKey::try_new([2; 32]).unwrap(); @@ -3254,10 +2976,7 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![sender_pre, recipient_pre], - Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer { - amount: balance, - }) - .unwrap(), + Program::serialize_instruction(balance).unwrap(), vec![ InputAccountIdentity::PrivateAuthorizedUpdate { epk, @@ -3307,14 +3026,14 @@ pub mod tests { #[test_case::test_case(2; "two calls")] fn private_chained_call(number_of_calls: u32) { // Arrange - let chain_caller = Program::chain_caller(); - let auth_transfers = Program::authenticated_transfer_program(); + let chain_caller = crate::test_methods::chain_caller(); + let simple_transfers = crate::test_methods::simple_balance_transfer(); let from_keys = test_private_account_keys_1(); let to_keys = test_private_account_keys_2(); let initial_balance = 100; let from_account = AccountWithMetadata::new( Account { - program_owner: auth_transfers.id(), + program_owner: simple_transfers.id(), balance: initial_balance, ..Account::default() }, @@ -3323,7 +3042,7 @@ pub mod tests { ); let to_account = AccountWithMetadata::new( Account { - program_owner: auth_transfers.id(), + program_owner: simple_transfers.id(), ..Account::default() }, true, @@ -3336,19 +3055,16 @@ pub mod tests { let to_commitment = Commitment::new(&to_account_id, &to_account.account); let from_init_nullifier = Nullifier::for_account_initialization(&from_account_id); let to_init_nullifier = Nullifier::for_account_initialization(&to_account_id); - let mut state = V03State::new_with_genesis_accounts( - &[], - vec![ + let mut state = V03State::new() + .with_private_accounts([ (from_commitment.clone(), from_init_nullifier), (to_commitment.clone(), to_init_nullifier), - ], - 0, - ) - .with_test_programs(); + ]) + .with_test_programs(); let amount: u128 = 37; let instruction: (u128, ProgramId, u32, Option) = ( amount, - Program::authenticated_transfer_program().id(), + crate::test_methods::simple_balance_transfer().id(), number_of_calls, None, ); @@ -3361,7 +3077,7 @@ pub mod tests { let mut dependencies = HashMap::new(); - dependencies.insert(auth_transfers.id(), auth_transfers); + dependencies.insert(simple_transfers.id(), simple_transfers); let program_with_deps = ProgramWithDependencies::new(chain_caller, dependencies); let from_new_nonce = Nonce::default().private_account_nonce_increment(&from_keys.nsk); @@ -3438,101 +3154,10 @@ pub mod tests { ); } - #[test] - fn pda_mechanism_with_pinata_token_program() { - let pinata_token = Program::pinata_token(); - let token = Program::token(); - - let pinata_definition_id = AccountId::new([1; 32]); - let pinata_token_definition_id = AccountId::new([2; 32]); - // Total supply of pinata token will be in an account under a PDA. - let pinata_token_holding_id = - AccountId::for_public_pda(&pinata_token.id(), &PdaSeed::new([0; 32])); - let winner_token_holding_id = AccountId::new([3; 32]); - - let expected_winner_account_holding = token_core::TokenHolding::Fungible { - definition_id: pinata_token_definition_id, - balance: 150, - }; - let expected_winner_token_holding_post = Account { - program_owner: token.id(), - data: Data::from(&expected_winner_account_holding), - ..Account::default() - }; - - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); - state.add_pinata_token_program(pinata_definition_id); - - // Set up the token accounts directly (bypassing public transactions which - // would require signers for Claim::Authorized). The focus of this test is - // the PDA mechanism in the pinata program's chained call, not token creation. - let total_supply: u128 = 10_000_000; - let token_definition = token_core::TokenDefinition::Fungible { - name: String::from("PINATA"), - total_supply, - metadata_id: None, - }; - let token_holding = token_core::TokenHolding::Fungible { - definition_id: pinata_token_definition_id, - balance: total_supply, - }; - let winner_holding = token_core::TokenHolding::Fungible { - definition_id: pinata_token_definition_id, - balance: 0, - }; - state.force_insert_account( - pinata_token_definition_id, - Account { - program_owner: token.id(), - data: Data::from(&token_definition), - ..Account::default() - }, - ); - state.force_insert_account( - pinata_token_holding_id, - Account { - program_owner: token.id(), - data: Data::from(&token_holding), - ..Account::default() - }, - ); - state.force_insert_account( - winner_token_holding_id, - Account { - program_owner: token.id(), - data: Data::from(&winner_holding), - ..Account::default() - }, - ); - - // Submit a solution to the pinata program to claim the prize - let solution: u128 = 989_106; - let message = public_transaction::Message::try_new( - pinata_token.id(), - vec![ - pinata_definition_id, - pinata_token_holding_id, - winner_token_holding_id, - ], - vec![], - solution, - ) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 1, 0).unwrap(); - - let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id); - assert_eq!( - winner_token_holding_post, - expected_winner_token_holding_post - ); - } - #[test] fn claiming_mechanism_cannot_claim_initialied_accounts() { - let claimer = Program::claimer(); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let claimer = crate::test_methods::claimer(); + let mut state = V03State::new().with_test_programs(); let account_id = AccountId::new([2; 32]); // Insert an account with non-default program owner @@ -3572,16 +3197,28 @@ pub mod tests { let recipient_id = AccountId::from(&PublicKey::new_from_private_key(&recipient_key)); let recipient_init_balance: u128 = 10; - let mut state = V03State::new_with_genesis_accounts( - &[ - (sender_id, sender_init_balance), - (recipient_id, recipient_init_balance), - ], - vec![], - 0, - ); + let modified_transfer_id = crate::test_methods::modified_transfer_program().id(); - state.insert_program(Program::modified_transfer_program()); + let mut state = V03State::new() + .with_public_accounts([ + ( + sender_id, + Account { + program_owner: modified_transfer_id, + balance: sender_init_balance, + ..Account::default() + }, + ), + ( + recipient_id, + Account { + program_owner: modified_transfer_id, + balance: recipient_init_balance, + ..Account::default() + }, + ), + ]) + .with_test_programs(); let balance_to_move: u128 = 4; @@ -3593,7 +3230,7 @@ pub mod tests { AccountWithMetadata::new(state.get_account_by_id(recipient_id), false, sender_id); let message = public_transaction::Message::try_new( - Program::modified_transfer_program().id(), + modified_transfer_id, vec![sender_id, recipient_id], vec![sender_nonce], balance_to_move, @@ -3643,7 +3280,7 @@ pub mod tests { #[test] fn private_authorized_uninitialized_account() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let mut state = V03State::new().with_test_programs(); // Set up keys for the authorized private account let private_keys = test_private_account_keys_1(); @@ -3652,13 +3289,13 @@ pub mod tests { let authorized_account = AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0)); - let program = Program::authenticated_transfer_program(); + let program = crate::test_methods::simple_balance_transfer(); // Set up parameters for the new account let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0); - let instruction = authenticated_transfer_core::Instruction::Initialize; + let instruction: u128 = 0; // Execute and prove the circuit with the authorized account but no commitment proof let (output, proof) = execute_and_prove( @@ -3694,7 +3331,7 @@ pub mod tests { #[test] fn private_unauthorized_uninitialized_account_can_still_be_claimed() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); let private_keys = test_private_account_keys_1(); // This is intentional: claim authorization was introduced to protect public accounts, @@ -3704,13 +3341,13 @@ pub mod tests { let unauthorized_account = AccountWithMetadata::new(Account::default(), false, (&private_keys.npk(), 0)); - let program = Program::claimer(); + let program = crate::test_methods::claimer(); let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0); let (output, proof) = execute_and_prove( vec![unauthorized_account], - Program::serialize_instruction(0_u128).unwrap(), + Program::serialize_instruction(()).unwrap(), vec![InputAccountIdentity::PrivateUnauthorized { epk, view_tag: EncryptedAccountData::compute_view_tag( @@ -3741,7 +3378,7 @@ pub mod tests { #[test] fn private_account_claimed_then_used_without_init_flag_should_fail() { - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); // Set up keys for the private account let private_keys = test_private_account_keys_1(); @@ -3750,13 +3387,13 @@ pub mod tests { let authorized_account = AccountWithMetadata::new(Account::default(), true, (&private_keys.npk(), 0)); - let claimer_program = Program::claimer(); + let claimer_program = crate::test_methods::claimer(); // Set up parameters for claiming the new account let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0); - let instruction = authenticated_transfer_core::Instruction::Initialize; + let instruction = (); // Step 2: Execute claimer program to claim the account with authentication let (output, proof) = execute_and_prove( @@ -3796,11 +3433,11 @@ pub mod tests { // Prepare new state of account let account_metadata = { let mut acc = authorized_account; - acc.account.program_owner = Program::claimer().id(); + acc.account.program_owner = crate::test_methods::claimer().id(); acc }; - let noop_program = Program::noop(); + let noop_program = crate::test_methods::noop(); let shared_secret2 = SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0).0; @@ -3827,10 +3464,11 @@ pub mod tests { #[test] fn public_changer_claimer_no_data_change_no_claim_succeeds() { let initial_data = []; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let account_id = AccountId::new([1; 32]); - let program_id = Program::changer_claimer().id(); + let program_id = crate::test_methods::changer_claimer().id(); // Don't change data (None) and don't claim (false) let instruction: (Option>, bool) = (None, false); @@ -3851,10 +3489,11 @@ pub mod tests { #[test] fn public_changer_claimer_data_change_no_claim_fails() { let initial_data = []; - let mut state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let mut state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let account_id = AccountId::new([1; 32]); - let program_id = Program::changer_claimer().id(); + let program_id = crate::test_methods::changer_claimer().id(); // Change data but don't claim (false) - should fail let new_data = vec![1, 2, 3, 4, 5]; let instruction: (Option>, bool) = (Some(new_data), false); @@ -3880,7 +3519,7 @@ pub mod tests { #[test] fn private_changer_claimer_no_data_change_no_claim_succeeds() { - let program = Program::changer_claimer(); + let program = crate::test_methods::changer_claimer(); let sender_keys = test_private_account_keys_1(); let private_account = AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0)); @@ -3911,7 +3550,7 @@ pub mod tests { #[test] fn private_changer_claimer_data_change_no_claim_fails() { - let program = Program::changer_claimer(); + let program = crate::test_methods::changer_claimer(); let sender_keys = test_private_account_keys_1(); let private_account = AccountWithMetadata::new(Account::default(), true, (&sender_keys.npk(), 0)); @@ -3944,14 +3583,14 @@ pub mod tests { #[test] fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() { // Arrange - let malicious_program = Program::malicious_authorization_changer(); - let auth_transfers = Program::authenticated_transfer_program(); + let malicious_program = crate::test_methods::malicious_authorization_changer(); + let simple_transfers = crate::test_methods::simple_balance_transfer(); let sender_keys = test_public_account_keys_1(); let recipient_keys = test_private_account_keys_1(); let sender_account = AccountWithMetadata::new( Account { - program_owner: auth_transfers.id(), + program_owner: simple_transfers.id(), balance: 100, ..Default::default() }, @@ -3965,21 +3604,22 @@ pub mod tests { let recipient_commitment = Commitment::new(&recipient_account_id, &recipient_account.account); let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id); - let state = V03State::new_with_genesis_accounts( - &[(sender_account.account_id, sender_account.account.balance)], - vec![(recipient_commitment.clone(), recipient_init_nullifier)], - 0, - ) - .with_test_programs(); + let state = V03State::new() + .with_public_accounts(public_state_from_balances(&[( + sender_account.account_id, + sender_account.account.balance, + )])) + .with_private_accounts([(recipient_commitment.clone(), recipient_init_nullifier)]) + .with_test_programs(); let balance_to_transfer = 10_u128; - let instruction = (balance_to_transfer, auth_transfers.id()); + let instruction = (balance_to_transfer, simple_transfers.id()); let recipient = SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0).0; let mut dependencies = HashMap::new(); - dependencies.insert(auth_transfers.id(), auth_transfers); + dependencies.insert(simple_transfers.id(), simple_transfers); let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies); // Act - execute the malicious program - this should fail during proving @@ -4027,10 +3667,10 @@ pub mod tests { block_id: BlockId, ) { let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap(); - let validity_window_program = Program::validity_window(); + let validity_window_program = crate::test_methods::validity_window(); let account_keys = test_public_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id()); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); let tx = { let account_ids = vec![pre.account_id]; let nonces = vec![]; @@ -4079,10 +3719,10 @@ pub mod tests { ) { let timestamp_validity_window: TimestampValidityWindow = validity_window.try_into().unwrap(); - let validity_window_program = Program::validity_window(); + let validity_window_program = crate::test_methods::validity_window(); let account_keys = test_public_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, account_keys.account_id()); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); let tx = { let account_ids = vec![pre.account_id]; let nonces = vec![]; @@ -4132,10 +3772,10 @@ pub mod tests { block_id: BlockId, ) { let block_validity_window: BlockValidityWindow = validity_window.try_into().unwrap(); - let validity_window_program = Program::validity_window(); + let validity_window_program = crate::test_methods::validity_window(); let account_keys = test_private_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, (&account_keys.npk(), 0)); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); let tx = { let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0); @@ -4200,10 +3840,10 @@ pub mod tests { ) { let timestamp_validity_window: TimestampValidityWindow = validity_window.try_into().unwrap(); - let validity_window_program = Program::validity_window(); + let validity_window_program = crate::test_methods::validity_window(); let account_keys = test_private_account_keys_1(); let pre = AccountWithMetadata::new(Account::default(), false, (&account_keys.npk(), 0)); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); let tx = { let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0); @@ -4251,234 +3891,14 @@ pub mod tests { } } - fn time_locked_transfer_transaction( - from: AccountId, - from_key: &PrivateKey, - from_nonce: u128, - to: AccountId, - clock_account_id: AccountId, - amount: u128, - deadline: u64, - ) -> PublicTransaction { - let program_id = Program::time_locked_transfer().id(); - let message = public_transaction::Message::try_new( - program_id, - vec![from, to, clock_account_id], - vec![Nonce(from_nonce)], - (amount, deadline), - ) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[from_key]); - PublicTransaction::new(message, witness_set) - } - - #[test] - fn time_locked_transfer_succeeds_when_deadline_has_passed() { - let recipient_id = AccountId::new([42; 32]); - let genesis_timestamp = 500_u64; - let mut state = - V03State::new_with_genesis_accounts(&[(recipient_id, 0)], vec![], genesis_timestamp) - .with_test_programs(); - let key1 = PrivateKey::try_new([1; 32]).unwrap(); - let sender_id = AccountId::from(&PublicKey::new_from_private_key(&key1)); - state.force_insert_account( - sender_id, - Account { - program_owner: Program::time_locked_transfer().id(), - balance: 100, - ..Account::default() - }, - ); - - let amount = 100_u128; - // Deadline in the past: transfer should succeed. - let deadline = 0_u64; - - let tx = time_locked_transfer_transaction( - sender_id, - &key1, - 0, - recipient_id, - CLOCK_01_PROGRAM_ACCOUNT_ID, - amount, - deadline, - ); - - let block_id = 1; - let timestamp = genesis_timestamp + 100; - state - .transition_from_public_transaction(&tx, block_id, timestamp) - .unwrap(); - - // Balances changed. - assert_eq!(state.get_account_by_id(sender_id).balance, 0); - assert_eq!(state.get_account_by_id(recipient_id).balance, 100); - } - - #[test] - fn time_locked_transfer_fails_when_deadline_is_in_the_future() { - let recipient_id = AccountId::new([42; 32]); - let genesis_timestamp = 500_u64; - let mut state = - V03State::new_with_genesis_accounts(&[(recipient_id, 0)], vec![], genesis_timestamp) - .with_test_programs(); - let key1 = PrivateKey::try_new([1; 32]).unwrap(); - let sender_id = AccountId::from(&PublicKey::new_from_private_key(&key1)); - state.force_insert_account( - sender_id, - Account { - program_owner: Program::time_locked_transfer().id(), - balance: 100, - ..Account::default() - }, - ); - - let amount = 100_u128; - // Far-future deadline: program should panic. - let deadline = u64::MAX; - - let tx = time_locked_transfer_transaction( - sender_id, - &key1, - 0, - recipient_id, - CLOCK_01_PROGRAM_ACCOUNT_ID, - amount, - deadline, - ); - - let block_id = 1; - let timestamp = genesis_timestamp + 100; - let result = state.transition_from_public_transaction(&tx, block_id, timestamp); - - assert!( - result.is_err(), - "Transfer should fail when deadline is in the future" - ); - // Balances unchanged. - assert_eq!(state.get_account_by_id(sender_id).balance, 100); - assert_eq!(state.get_account_by_id(recipient_id).balance, 0); - } - - fn pinata_cooldown_data(prize: u128, cooldown_ms: u64, last_claim_timestamp: u64) -> Vec { - let mut buf = Vec::with_capacity(32); - buf.extend_from_slice(&prize.to_le_bytes()); - buf.extend_from_slice(&cooldown_ms.to_le_bytes()); - buf.extend_from_slice(&last_claim_timestamp.to_le_bytes()); - buf - } - - fn pinata_cooldown_transaction( - pinata_id: AccountId, - winner_id: AccountId, - clock_account_id: AccountId, - ) -> PublicTransaction { - let program_id = Program::pinata_cooldown().id(); - let message = public_transaction::Message::try_new( - program_id, - vec![pinata_id, winner_id, clock_account_id], - vec![], - (), - ) - .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); - PublicTransaction::new(message, witness_set) - } - - #[test] - fn pinata_cooldown_claim_succeeds_after_cooldown() { - let winner_id = AccountId::new([11; 32]); - let pinata_id = AccountId::new([99; 32]); - - let genesis_timestamp = 1000_u64; - let mut state = - V03State::new_with_genesis_accounts(&[(winner_id, 0)], vec![], genesis_timestamp) - .with_test_programs(); - - let prize = 50_u128; - let cooldown_ms = 500_u64; - // Last claim was at genesis, so any timestamp >= genesis + cooldown should work. - let last_claim_timestamp = genesis_timestamp; - - state.force_insert_account( - pinata_id, - Account { - program_owner: Program::pinata_cooldown().id(), - balance: 1000, - data: pinata_cooldown_data(prize, cooldown_ms, last_claim_timestamp) - .try_into() - .unwrap(), - ..Account::default() - }, - ); - - let tx = pinata_cooldown_transaction(pinata_id, winner_id, CLOCK_01_PROGRAM_ACCOUNT_ID); - - let block_id = 1; - let block_timestamp = genesis_timestamp + cooldown_ms; - // Advance clock so the cooldown check reads an updated timestamp. - let clock_tx = clock_transaction(block_timestamp); - state - .transition_from_public_transaction(&clock_tx, block_id, block_timestamp) - .unwrap(); - - state - .transition_from_public_transaction(&tx, block_id, block_timestamp) - .unwrap(); - - assert_eq!(state.get_account_by_id(pinata_id).balance, 1000 - prize); - assert_eq!(state.get_account_by_id(winner_id).balance, prize); - } - - #[test] - fn pinata_cooldown_claim_fails_during_cooldown() { - let winner_id = AccountId::new([11; 32]); - let pinata_id = AccountId::new([99; 32]); - - let genesis_timestamp = 1000_u64; - let mut state = - V03State::new_with_genesis_accounts(&[(winner_id, 0)], vec![], genesis_timestamp) - .with_test_programs(); - - let prize = 50_u128; - let cooldown_ms = 500_u64; - let last_claim_timestamp = genesis_timestamp; - - state.force_insert_account( - pinata_id, - Account { - balance: 1000, - data: pinata_cooldown_data(prize, cooldown_ms, last_claim_timestamp) - .try_into() - .unwrap(), - ..Account::default() - }, - ); - - let tx = pinata_cooldown_transaction(pinata_id, winner_id, CLOCK_01_PROGRAM_ACCOUNT_ID); - - let block_id = 1; - // Timestamp is only 100ms after last claim, well within the 500ms cooldown. - let block_timestamp = genesis_timestamp + 100; - let clock_tx = clock_transaction(block_timestamp); - state - .transition_from_public_transaction(&clock_tx, block_id, block_timestamp) - .unwrap(); - - let result = state.transition_from_public_transaction(&tx, block_id, block_timestamp); - - assert!(result.is_err(), "Claim should fail during cooldown period"); - assert_eq!(state.get_account_by_id(pinata_id).balance, 1000); - assert_eq!(state.get_account_by_id(winner_id).balance, 0); - } - #[test] fn state_serialization_roundtrip() { let account_id_1 = AccountId::new([1; 32]); let account_id_2 = AccountId::new([2; 32]); let initial_data = [(account_id_1, 100_u128), (account_id_2, 151_u128)]; - let state = - V03State::new_with_genesis_accounts(&initial_data, vec![], 0).with_test_programs(); + let state = V03State::new() + .with_public_accounts(public_state_from_balances(&initial_data)) + .with_test_programs(); let bytes = borsh::to_vec(&state).unwrap(); let state_from_bytes: V03State = borsh::from_slice(&bytes).unwrap(); assert_eq!(state, state_from_bytes); @@ -4486,9 +3906,9 @@ pub mod tests { #[test] fn flash_swap_successful() { - let initiator = Program::flash_swap_initiator(); - let callback = Program::flash_swap_callback(); - let token = Program::authenticated_transfer_program(); + let initiator = crate::test_methods::flash_swap_initiator(); + let callback = crate::test_methods::flash_swap_callback(); + let token = crate::test_methods::simple_balance_transfer(); let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32])); let receiver_id = AccountId::for_public_pda(&callback.id(), &PdaSeed::new([1_u8; 32])); @@ -4507,7 +3927,7 @@ pub mod tests { ..Account::default() }; - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); state.force_insert_account(vault_id, vault_account); state.force_insert_account(receiver_id, receiver_account); @@ -4537,9 +3957,9 @@ pub mod tests { #[test] fn flash_swap_callback_keeps_funds_rollback() { - let initiator = Program::flash_swap_initiator(); - let callback = Program::flash_swap_callback(); - let token = Program::authenticated_transfer_program(); + let initiator = crate::test_methods::flash_swap_initiator(); + let callback = crate::test_methods::flash_swap_callback(); + let token = crate::test_methods::simple_balance_transfer(); let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32])); let receiver_id = AccountId::for_public_pda(&callback.id(), &PdaSeed::new([1_u8; 32])); @@ -4558,7 +3978,7 @@ pub mod tests { ..Account::default() }; - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); state.force_insert_account(vault_id, vault_account); state.force_insert_account(receiver_id, receiver_account); @@ -4595,9 +4015,9 @@ pub mod tests { fn flash_swap_self_call_targets_correct_program() { // Zero-amount flash swap: the invariant self-call still runs and succeeds // because vault balance doesn't decrease. - let initiator = Program::flash_swap_initiator(); - let callback = Program::flash_swap_callback(); - let token = Program::authenticated_transfer_program(); + let initiator = crate::test_methods::flash_swap_initiator(); + let callback = crate::test_methods::flash_swap_callback(); + let token = crate::test_methods::simple_balance_transfer(); let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32])); let receiver_id = AccountId::for_public_pda(&callback.id(), &PdaSeed::new([1_u8; 32])); @@ -4615,7 +4035,7 @@ pub mod tests { ..Account::default() }; - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); state.force_insert_account(vault_id, vault_account); state.force_insert_account(receiver_id, receiver_account); @@ -4645,8 +4065,8 @@ pub mod tests { fn flash_swap_standalone_invariant_check_rejected() { // Calling InvariantCheck directly (not as a chained self-call) should fail // because caller_program_id will be None. - let initiator = Program::flash_swap_initiator(); - let token = Program::authenticated_transfer_program(); + let initiator = crate::test_methods::flash_swap_initiator(); + let token = crate::test_methods::simple_balance_transfer(); let vault_id = AccountId::for_public_pda(&initiator.id(), &PdaSeed::new([0_u8; 32])); @@ -4656,7 +4076,7 @@ pub mod tests { ..Account::default() }; - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); state.force_insert_account(vault_id, vault_account); let instruction = FlashSwapInstruction::InvariantCheck { @@ -4682,11 +4102,11 @@ pub mod tests { #[test] fn malicious_self_program_id_rejected_in_public_execution() { - let program = Program::malicious_self_program_id(); + let program = crate::test_methods::malicious_self_program_id(); let acc_id = AccountId::new([99; 32]); let account = Account::default(); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); state.force_insert_account(acc_id, account); let message = @@ -4703,11 +4123,11 @@ pub mod tests { #[test] fn malicious_caller_program_id_rejected_in_public_execution() { - let program = Program::malicious_caller_program_id(); + let program = crate::test_methods::malicious_caller_program_id(); let acc_id = AccountId::new([99; 32]); let account = Account::default(); - let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); + let mut state = V03State::new().with_test_programs(); state.force_insert_account(acc_id, account); let message = @@ -4728,15 +4148,17 @@ pub mod tests { let alice_keys = test_private_account_keys_1(); let alice_npk = alice_keys.npk(); - let proxy = Program::pda_spend_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); + let proxy = crate::test_methods::pda_spend_proxy(); + let simple_transfer = crate::test_methods::simple_balance_transfer(); let proxy_id = proxy.id(); - let auth_transfer_id = auth_transfer.id(); + let simple_transfer_id = simple_transfer.id(); let seed = PdaSeed::new([42; 32]); let amount: u128 = 100; - let spend_with_deps = - ProgramWithDependencies::new(proxy, [(auth_transfer_id, auth_transfer.clone())].into()); + let spend_with_deps = ProgramWithDependencies::new( + proxy, + [(simple_transfer_id, simple_transfer.clone())].into(), + ); let funder_id = funder_keys.account_id(); let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0); @@ -4744,16 +4166,17 @@ pub mod tests { let recipient_id = test_public_account_keys_2().account_id(); let recipient_signing_key = test_public_account_keys_2().signing_key; - let mut state = V03State::new_with_genesis_accounts(&[(funder_id, 500)], vec![], 0); + let mut state = + V03State::new().with_public_accounts(public_state_from_balances(&[(funder_id, 500)])); let alice_pda_0_account = Account { - program_owner: auth_transfer_id, + program_owner: simple_transfer_id, balance: amount, nonce: Nonce::private_account_nonce_init(&alice_pda_0_id), ..Account::default() }; let alice_pda_1_account = Account { - program_owner: auth_transfer_id, + program_owner: simple_transfer_id, balance: amount, nonce: Nonce::private_account_nonce_init(&alice_pda_1_id), ..Account::default() @@ -4773,8 +4196,7 @@ pub mod tests { AccountWithMetadata::new(funder_account, true, funder_id), AccountWithMetadata::new(Account::default(), false, alice_pda_0_id), ], - Program::serialize_instruction(AuthTransferInstruction::Transfer { amount }) - .unwrap(), + Program::serialize_instruction(amount).unwrap(), vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaInit { @@ -4789,7 +4211,7 @@ pub mod tests { seed: Some((seed, proxy_id)), }, ], - &auth_transfer.clone().into(), + &simple_transfer.clone().into(), ) .unwrap(); let message = @@ -4814,8 +4236,7 @@ pub mod tests { AccountWithMetadata::new(funder_account, true, funder_id), AccountWithMetadata::new(Account::default(), false, alice_pda_1_id), ], - Program::serialize_instruction(AuthTransferInstruction::Transfer { amount }) - .unwrap(), + Program::serialize_instruction(amount).unwrap(), vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaInit { @@ -4830,7 +4251,7 @@ pub mod tests { seed: Some((seed, proxy_id)), }, ], - &auth_transfer.into(), + &simple_transfer.into(), ) .unwrap(); let message = @@ -4860,7 +4281,7 @@ pub mod tests { AccountWithMetadata::new(alice_pda_0_account, true, alice_pda_0_id), AccountWithMetadata::new(recipient_account, true, recipient_id), ], - Program::serialize_instruction((seed, amount, auth_transfer_id)).unwrap(), + Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(), vec![ InputAccountIdentity::PrivatePdaUpdate { epk: alice_epk_0, @@ -4902,7 +4323,7 @@ pub mod tests { AccountWithMetadata::new(alice_pda_1_account.clone(), true, alice_pda_1_id), AccountWithMetadata::new(recipient_account, false, recipient_id), ], - Program::serialize_instruction((seed, amount, auth_transfer_id)).unwrap(), + Program::serialize_instruction((seed, amount, simple_transfer_id)).unwrap(), vec![ InputAccountIdentity::PrivatePdaUpdate { epk: alice_epk_1, @@ -4937,10 +4358,10 @@ pub mod tests { assert_eq!(state.get_account_by_id(recipient_id).balance, 2 * amount); - // Re-fund alice_pda_1 top-level via auth_transfer using PrivatePdaUpdate with an + // Re-fund alice_pda_1 top-level via simple_transfer using PrivatePdaUpdate with an // external seed. let alice_pda_1_account_after_spend = Account { - program_owner: auth_transfer_id, + program_owner: simple_transfer_id, balance: 0, nonce: alice_pda_1_account .nonce @@ -4962,8 +4383,7 @@ pub mod tests { alice_pda_1_id, ), ], - Program::serialize_instruction(AuthTransferInstruction::Transfer { amount }) - .unwrap(), + Program::serialize_instruction(amount).unwrap(), vec![ InputAccountIdentity::Public, InputAccountIdentity::PrivatePdaUpdate { @@ -4981,7 +4401,7 @@ pub mod tests { seed: Some((seed, proxy_id)), }, ], - &Program::authenticated_transfer_program().into(), + &crate::test_methods::simple_balance_transfer().into(), ) .unwrap(); let message = diff --git a/lee/state_machine/src/validated_state_diff.rs b/lee/state_machine/src/validated_state_diff.rs index 0d71d485..44a307af 100644 --- a/lee/state_machine/src/validated_state_diff.rs +++ b/lee/state_machine/src/validated_state_diff.rs @@ -49,6 +49,11 @@ impl ValidatedStateDiff { let message = tx.message(); let witness_set = tx.witness_set(); + ensure!( + !message.account_ids.is_empty(), + LeeError::InvalidInput("Public transaction must have at least one account".into()) + ); + // All account_ids must be different ensure!( message.account_ids.iter().collect::>().len() == message.account_ids.len(), @@ -457,7 +462,7 @@ impl ValidatedStateDiff { state: &V03State, ) -> Result { // TODO: remove clone - let program = Program::new(tx.message.bytecode.clone())?; + let program = Program::new(tx.message.bytecode.clone().into())?; if state.programs().contains_key(&program.id()) { return Err(LeeError::ProgramAlreadyExists); } @@ -511,7 +516,9 @@ fn n_unique(data: &[T]) -> usize { #[cfg(test)] mod tests { - use lee_core::account::{AccountId, Nonce}; + use std::collections::HashMap; + + use lee_core::account::{Account, AccountId, Nonce}; use crate::{ PrivateKey, PublicKey, V03State, @@ -521,27 +528,43 @@ mod tests { validated_state_diff::ValidatedStateDiff, }; + fn public_state_from_balances( + initial_data: &[(AccountId, u128)], + ) -> HashMap { + initial_data + .iter() + .copied() + .map(|(account_id, balance)| { + ( + account_id, + Account { + program_owner: crate::test_methods::simple_balance_transfer().id(), + balance, + ..Account::default() + }, + ) + }) + .collect() + } + #[test] fn public_diff_reflects_a_successful_transfer() { // A successful native transfer must record the debited sender in // `public_diff()`. Catches the mutation that replaces `public_diff` with // `HashMap::new()` (which would hide every account change). - use authenticated_transfer_core::Instruction as AtInstruction; - let from_key = PrivateKey::try_new([1_u8; 32]).unwrap(); let from = AccountId::from(&PublicKey::new_from_private_key(&from_key)); let to_key = PrivateKey::try_new([2_u8; 32]).unwrap(); let to = AccountId::from(&PublicKey::new_from_private_key(&to_key)); - let state = V03State::new_with_genesis_accounts(&[(from, 100)], vec![], 0); - let program_id = Program::authenticated_transfer_program().id(); - let message = Message::try_new( - program_id, - vec![from, to], - vec![Nonce(0), Nonce(0)], - AtInstruction::Transfer { amount: 5 }, - ) - .unwrap(); + let state = V03State::new() + .with_public_accounts(public_state_from_balances(&[(from, 100)])) + .with_programs(std::iter::once( + crate::test_methods::simple_balance_transfer(), + )); + let program_id = crate::test_methods::simple_balance_transfer().id(); + let message = + Message::try_new(program_id, vec![from, to], vec![Nonce(0), Nonce(0)], 5_u128).unwrap(); let witness_set = WitnessSet::for_message(&message, &[&from_key, &to_key]); let tx = crate::PublicTransaction::new(message, witness_set); @@ -591,7 +614,7 @@ mod tests { type InjectorInstruction = ( lee_core::program::ProgramId, // p2_id - lee_core::program::ProgramId, // auth_transfer_id + lee_core::program::ProgramId, // simple_balance_transfer_id [u8; 32], // victim_id_raw u128, // victim_balance u128, // victim_nonce @@ -609,18 +632,21 @@ mod tests { let recipient_id = AccountId::new([42_u8; 32]); let victim_balance = 5_000_u128; - // genesis sets program_owner = authenticated_transfer_program.id() on all accounts. - let mut state = V03State::new_with_genesis_accounts( - &[(victim_id, victim_balance), (recipient_id, 0)], - vec![], - 0, - ); - state.insert_program(Program::malicious_injector()); - state.insert_program(Program::malicious_launderer()); + // genesis sets program_owner = simple_balance_transfer_program.id() on all accounts. + let state = V03State::new() + .with_public_accounts(public_state_from_balances(&[ + (victim_id, victim_balance), + (recipient_id, 0), + ])) + .with_programs([ + crate::test_methods::simple_balance_transfer(), + crate::test_methods::malicious_injector(), + crate::test_methods::malicious_launderer(), + ]); // Build attacker's private account and its local commitment tree. let attacker_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: 100, ..Account::default() }; @@ -635,8 +661,8 @@ mod tests { let victim_account = state.get_account_by_id(victim_id); let instruction: InjectorInstruction = ( - Program::malicious_launderer().id(), - Program::authenticated_transfer_program().id(), + crate::test_methods::malicious_launderer().id(), + crate::test_methods::simple_balance_transfer().id(), *victim_id.value(), victim_account.balance, victim_account.nonce.0, @@ -646,17 +672,17 @@ mod tests { ); let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let p2 = Program::malicious_launderer(); - let at = Program::authenticated_transfer_program(); + let p2 = crate::test_methods::malicious_launderer(); + let at = crate::test_methods::simple_balance_transfer(); let program_with_deps = ProgramWithDependencies::new( - Program::malicious_injector(), + crate::test_methods::malicious_injector(), [(p2.id(), p2), (at.id(), at)].into(), ); // account_identities order must match self.pre_states as built by the circuit: // [0] attacker โ€” first seen in P1's program_output.pre_states - // [1] victim โ€” first seen in authenticated_transfer's program_output.pre_states - // [2] recipient โ€” first seen in authenticated_transfer's program_output.pre_states + // [1] victim โ€” first seen in simple_balance_transfer's program_output.pre_states + // [2] recipient โ€” first seen in simple_balance_transfer's program_output.pre_states let account_identities = vec![ InputAccountIdentity::PrivateAuthorizedUpdate { epk: attacker_epk, @@ -746,7 +772,7 @@ mod tests { type InjectorInstruction = ( lee_core::program::ProgramId, // p2_id - lee_core::program::ProgramId, // auth_transfer_id + lee_core::program::ProgramId, // simple_balance_transfer_id [u8; 32], // victim_id_raw u128, // victim_balance u128, // victim_nonce @@ -768,13 +794,17 @@ mod tests { let recipient_id = AccountId::new([42_u8; 32]); // Victim has no public state entry; only recipient is registered at genesis. - let mut state = V03State::new_with_genesis_accounts(&[(recipient_id, 0)], vec![], 0); - state.insert_program(Program::malicious_injector()); - state.insert_program(Program::malicious_launderer()); + let state = V03State::new() + .with_public_accounts(public_state_from_balances(&[(recipient_id, 0)])) + .with_programs([ + crate::test_methods::simple_balance_transfer(), + crate::test_methods::malicious_injector(), + crate::test_methods::malicious_launderer(), + ]); // Build attacker's private account and its local commitment tree. let attacker_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: crate::test_methods::simple_balance_transfer().id(), balance: 100, ..Account::default() }; @@ -788,32 +818,32 @@ mod tests { let attacker_pre = AccountWithMetadata::new(attacker_account, true, attacker_id); // The attacker supplies the victim's account data directly โ€” it cannot be read from - // public state. The injected balance and program_owner allow authenticated_transfer + // public state. The injected balance and program_owner allow simple_balance_transfer // to succeed inside the circuit, which has no access to chain state and cannot detect // that these values are fabricated. let instruction: InjectorInstruction = ( - Program::malicious_launderer().id(), - Program::authenticated_transfer_program().id(), + crate::test_methods::malicious_launderer().id(), + crate::test_methods::simple_balance_transfer().id(), *victim_id.value(), victim_balance, - 0_u128, // nonce - Program::authenticated_transfer_program().id(), // program_owner + 0_u128, // nonce + crate::test_methods::simple_balance_transfer().id(), // program_owner *recipient_id.value(), victim_balance, ); let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let p2 = Program::malicious_launderer(); - let at = Program::authenticated_transfer_program(); + let p2 = crate::test_methods::malicious_launderer(); + let at = crate::test_methods::simple_balance_transfer(); let program_with_deps = ProgramWithDependencies::new( - Program::malicious_injector(), + crate::test_methods::malicious_injector(), [(p2.id(), p2), (at.id(), at)].into(), ); // account_identities order must match self.pre_states as built by the circuit: // [0] attacker โ€” first seen in P1's program_output.pre_states - // [1] victim โ€” first seen in authenticated_transfer's program_output.pre_states - // [2] recipient โ€” first seen in authenticated_transfer's program_output.pre_states + // [1] victim โ€” first seen in simple_balance_transfer's program_output.pre_states + // [2] recipient โ€” first seen in simple_balance_transfer's program_output.pre_states // // Victim is marked Public: the attacker has no nsk for the victim's private account, // so PrivateAuthorizedUpdate is not an option. @@ -833,7 +863,7 @@ mod tests { InputAccountIdentity::Public, // recipient ]; - // execute_and_prove succeeds: authenticated_transfer runs against the injected + // execute_and_prove succeeds: simple_balance_transfer runs against the injected // victim(balance=5000, is_authorized=true) and produces valid inner receipts. // The outer circuit commits victim(is_authorized=true) to public_pre_states. let (circuit_output, proof) = execute_and_prove( @@ -875,7 +905,7 @@ mod tests { /// Transaction (attacker signs) โ†’ P1 (`malicious_injector`) /// โ†’ injects `victim(is_authorized=true)` into chained-call `pre_states` for P2 /// P2 (`malicious_launderer`) - /// โ†’ outputs empty pre/post states, forwarding the forged flag to `authenticated_transfer` + /// โ†’ outputs empty pre/post states, forwarding the forged flag to `simple_balance_transfer` /// โ†’ if `authorized_accounts` were built from the injected `pre_states`, /// `{victim}.contains(victim)` would pass and the transfer would execute. /// @@ -884,13 +914,13 @@ mod tests { /// input, so a forged `is_authorized=true` flag is never trusted. #[test] fn malicious_programs_cannot_drain_victim_without_signature() { - // p2_id, auth_transfer_id, victim_id_raw, victim_balance, victim_nonce, + // p2_id, simple_balance_transfer_id, victim_id_raw, victim_balance, victim_nonce, // victim_program_owner, recipient_id_raw, amount. // Primitives only โ€” AccountId/Account cannot round-trip through instruction_data // via risc0_zkvm::serde (SerializeDisplay issue). type InjectorInstruction = ( lee_core::program::ProgramId, // p2_id - lee_core::program::ProgramId, // auth_transfer_id + lee_core::program::ProgramId, // simple_balance_transfer_id [u8; 32], // victim_id_raw u128, // victim_balance u128, // victim_nonce @@ -908,25 +938,24 @@ mod tests { let recipient_id = AccountId::new([42; 32]); let victim_balance = 5_000_u128; - let mut state = V03State::new_with_genesis_accounts( - &[ + let state = V03State::new() + .with_public_accounts(public_state_from_balances(&[ (attacker_id, 100), (victim_id, victim_balance), (recipient_id, 0), - ], - vec![], - 0, - ); - - state.insert_program(Program::malicious_injector()); - state.insert_program(Program::malicious_launderer()); + ])) + .with_programs([ + crate::test_methods::simple_balance_transfer(), + crate::test_methods::malicious_injector(), + crate::test_methods::malicious_launderer(), + ]); // Read victim state from chain, exactly as the attacker would. let victim_account = state.get_account_by_id(victim_id); let instruction: InjectorInstruction = ( - Program::malicious_launderer().id(), - Program::authenticated_transfer_program().id(), + crate::test_methods::malicious_launderer().id(), + crate::test_methods::simple_balance_transfer().id(), *victim_id.value(), victim_account.balance, victim_account.nonce.0, @@ -936,7 +965,7 @@ mod tests { ); let message = Message::try_new( - Program::malicious_injector().id(), + crate::test_methods::malicious_injector().id(), vec![attacker_id], vec![Nonce(0)], instruction, @@ -989,7 +1018,7 @@ mod tests { }, }; - let state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let state = V03State::new(); // Minimal message that passes every check up to proof verification: a single // commitment satisfies the non-empty requirement, no signers makes the diff --git a/program_methods/Cargo.toml b/lee/state_machine/test_methods/Cargo.toml similarity index 88% rename from program_methods/Cargo.toml rename to lee/state_machine/test_methods/Cargo.toml index 573fd4e6..a2709e1f 100644 --- a/program_methods/Cargo.toml +++ b/lee/state_machine/test_methods/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "program_methods" +name = "test_methods" version = "0.1.0" edition = "2024" license = { workspace = true } diff --git a/program_methods/build.rs b/lee/state_machine/test_methods/build.rs similarity index 100% rename from program_methods/build.rs rename to lee/state_machine/test_methods/build.rs diff --git a/lee/state_machine/test_methods/guest/Cargo.toml b/lee/state_machine/test_methods/guest/Cargo.toml new file mode 100644 index 00000000..75d34081 --- /dev/null +++ b/lee/state_machine/test_methods/guest/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "test_methods_guests" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true + +risc0-zkvm.workspace = true +serde.workspace = true diff --git a/test_program_methods/guest/src/bin/auth_asserting_noop.rs b/lee/state_machine/test_methods/guest/src/bin/auth_asserting_noop.rs similarity index 100% rename from test_program_methods/guest/src/bin/auth_asserting_noop.rs rename to lee/state_machine/test_methods/guest/src/bin/auth_asserting_noop.rs diff --git a/test_program_methods/guest/src/bin/burner.rs b/lee/state_machine/test_methods/guest/src/bin/burner.rs similarity index 100% rename from test_program_methods/guest/src/bin/burner.rs rename to lee/state_machine/test_methods/guest/src/bin/burner.rs diff --git a/lee/state_machine/test_methods/guest/src/bin/chain_caller.rs b/lee/state_machine/test_methods/guest/src/bin/chain_caller.rs new file mode 100644 index 00000000..b812fc9e --- /dev/null +++ b/lee/state_machine/test_methods/guest/src/bin/chain_caller.rs @@ -0,0 +1,70 @@ +use lee_core::program::{ + AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs, +}; +use risc0_zkvm::serde::to_vec; + +type Instruction = (u128, ProgramId, u32, Option); + +/// A program that calls another program `num_chain_calls` times. +/// It permutes the order of the input accounts on the subsequent call +/// The `ProgramId` in the instruction must be the `program_id` of the transfers +/// program. +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction: (balance, simple_transfer_id, num_chain_calls, pda_seed), + }, + instruction_words, + ) = read_lee_inputs::(); + + let Ok([recipient_pre, sender_pre]) = <[_; 2]>::try_from(pre_states) else { + return; + }; + + let instruction_data = to_vec(&balance).unwrap(); + + let mut running_recipient_pre = recipient_pre.clone(); + let mut running_sender_pre = sender_pre.clone(); + + if pda_seed.is_some() { + running_sender_pre.is_authorized = true; + } + + let mut chained_calls = Vec::new(); + for _i in 0..num_chain_calls { + let new_chained_call = ChainedCall { + program_id: simple_transfer_id, + instruction_data: instruction_data.clone(), + pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], /* <- Account order permutation here */ + pda_seeds: pda_seed.iter().copied().collect(), + }; + chained_calls.push(new_chained_call); + + running_sender_pre.account.balance = + match running_sender_pre.account.balance.checked_sub(balance) { + Some(new_balance) => new_balance, + None => return, + }; + running_recipient_pre.account.balance = + match running_recipient_pre.account.balance.checked_add(balance) { + Some(new_balance) => new_balance, + None => return, + }; + } + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![sender_pre.clone(), recipient_pre.clone()], + vec![ + AccountPostState::new(sender_pre.account), + AccountPostState::new(recipient_pre.account), + ], + ) + .with_chained_calls(chained_calls) + .write(); +} diff --git a/test_program_methods/guest/src/bin/changer_claimer.rs b/lee/state_machine/test_methods/guest/src/bin/changer_claimer.rs similarity index 100% rename from test_program_methods/guest/src/bin/changer_claimer.rs rename to lee/state_machine/test_methods/guest/src/bin/changer_claimer.rs diff --git a/test_program_methods/guest/src/bin/claimer.rs b/lee/state_machine/test_methods/guest/src/bin/claimer.rs similarity index 100% rename from test_program_methods/guest/src/bin/claimer.rs rename to lee/state_machine/test_methods/guest/src/bin/claimer.rs diff --git a/test_program_methods/guest/src/bin/data_changer.rs b/lee/state_machine/test_methods/guest/src/bin/data_changer.rs similarity index 100% rename from test_program_methods/guest/src/bin/data_changer.rs rename to lee/state_machine/test_methods/guest/src/bin/data_changer.rs diff --git a/test_program_methods/guest/src/bin/extra_output.rs b/lee/state_machine/test_methods/guest/src/bin/extra_output.rs similarity index 100% rename from test_program_methods/guest/src/bin/extra_output.rs rename to lee/state_machine/test_methods/guest/src/bin/extra_output.rs diff --git a/test_program_methods/guest/src/bin/flash_swap_callback.rs b/lee/state_machine/test_methods/guest/src/bin/flash_swap_callback.rs similarity index 95% rename from test_program_methods/guest/src/bin/flash_swap_callback.rs rename to lee/state_machine/test_methods/guest/src/bin/flash_swap_callback.rs index 5e1a30aa..28f6509f 100644 --- a/test_program_methods/guest/src/bin/flash_swap_callback.rs +++ b/lee/state_machine/test_methods/guest/src/bin/flash_swap_callback.rs @@ -62,10 +62,7 @@ fn main() { // Mark the receiver as authorized since it will be PDA-authorized in this chained call. let mut receiver_authorized = receiver_pre.clone(); receiver_authorized.is_authorized = true; - let transfer_instruction = - risc0_zkvm::serde::to_vec(&authenticated_transfer_core::Instruction::Transfer { - amount: instruction.amount, - }) + let transfer_instruction = risc0_zkvm::serde::to_vec(&instruction.amount) .expect("transfer instruction serialization"); chained_calls.push(ChainedCall { diff --git a/test_program_methods/guest/src/bin/flash_swap_initiator.rs b/lee/state_machine/test_methods/guest/src/bin/flash_swap_initiator.rs similarity index 97% rename from test_program_methods/guest/src/bin/flash_swap_initiator.rs rename to lee/state_machine/test_methods/guest/src/bin/flash_swap_initiator.rs index 15706c1e..699d7c57 100644 --- a/test_program_methods/guest/src/bin/flash_swap_initiator.rs +++ b/lee/state_machine/test_methods/guest/src/bin/flash_swap_initiator.rs @@ -122,10 +122,7 @@ fn main() { let mut vault_authorized = vault_pre.clone(); vault_authorized.is_authorized = true; let transfer_instruction = - risc0_zkvm::serde::to_vec(&authenticated_transfer_core::Instruction::Transfer { - amount: amount_out, - }) - .expect("transfer instruction serialization"); + risc0_zkvm::serde::to_vec(&amount_out).expect("transfer instruction serialization"); let call_1 = ChainedCall { program_id: token_program_id, pre_states: vec![vault_authorized, receiver_pre.clone()], diff --git a/test_program_methods/guest/src/bin/malicious_authorization_changer.rs b/lee/state_machine/test_methods/guest/src/bin/malicious_authorization_changer.rs similarity index 92% rename from test_program_methods/guest/src/bin/malicious_authorization_changer.rs rename to lee/state_machine/test_methods/guest/src/bin/malicious_authorization_changer.rs index f1e3be6b..80bd8aaa 100644 --- a/test_program_methods/guest/src/bin/malicious_authorization_changer.rs +++ b/lee/state_machine/test_methods/guest/src/bin/malicious_authorization_changer.rs @@ -32,8 +32,7 @@ fn main() { ..sender.clone() }; - let instruction_data = - to_vec(&authenticated_transfer_core::Instruction::Transfer { amount: balance }).unwrap(); + let instruction_data = to_vec(&balance).unwrap(); let chained_call = ChainedCall { program_id: transfer_program_id, diff --git a/test_program_methods/guest/src/bin/malicious_caller_program_id.rs b/lee/state_machine/test_methods/guest/src/bin/malicious_caller_program_id.rs similarity index 100% rename from test_program_methods/guest/src/bin/malicious_caller_program_id.rs rename to lee/state_machine/test_methods/guest/src/bin/malicious_caller_program_id.rs diff --git a/test_program_methods/guest/src/bin/malicious_injector.rs b/lee/state_machine/test_methods/guest/src/bin/malicious_injector.rs similarity index 100% rename from test_program_methods/guest/src/bin/malicious_injector.rs rename to lee/state_machine/test_methods/guest/src/bin/malicious_injector.rs diff --git a/test_program_methods/guest/src/bin/malicious_launderer.rs b/lee/state_machine/test_methods/guest/src/bin/malicious_launderer.rs similarity index 79% rename from test_program_methods/guest/src/bin/malicious_launderer.rs rename to lee/state_machine/test_methods/guest/src/bin/malicious_launderer.rs index 6794f0c0..5ec7989d 100644 --- a/test_program_methods/guest/src/bin/malicious_launderer.rs +++ b/lee/state_machine/test_methods/guest/src/bin/malicious_launderer.rs @@ -9,7 +9,7 @@ fn main() { self_program_id, caller_program_id, pre_states, - instruction: (auth_transfer_id, amount), + instruction: (simple_transfer_id, amount), }, instruction_words, ) = read_lee_inputs::(); @@ -18,13 +18,12 @@ fn main() { // authorization check at validated_state_diff.rs:158-182 runs over nothing. // Victim is never compared against caller_data.authorized_accounts = {attacker}. // - // The bug: authorized_accounts for authenticated_transfer is built from + // The bug: authorized_accounts for simple_transfer is built from // chained_call.pre_states (this call's inputs, set by P1), which contains // victim(is_authorized=true). So authorized_accounts = {victim}, and the // subsequent check passes. let auth_transfer_instruction = - risc0_zkvm::serde::to_vec(&authenticated_transfer_core::Instruction::Transfer { amount }) - .expect("serialization is infallible"); + risc0_zkvm::serde::to_vec(&amount).expect("serialization is infallible"); ProgramOutput::new( self_program_id, @@ -34,7 +33,7 @@ fn main() { vec![], ) .with_chained_calls(vec![ChainedCall { - program_id: auth_transfer_id, + program_id: simple_transfer_id, pre_states, instruction_data: auth_transfer_instruction, pda_seeds: vec![], diff --git a/test_program_methods/guest/src/bin/malicious_self_program_id.rs b/lee/state_machine/test_methods/guest/src/bin/malicious_self_program_id.rs similarity index 100% rename from test_program_methods/guest/src/bin/malicious_self_program_id.rs rename to lee/state_machine/test_methods/guest/src/bin/malicious_self_program_id.rs diff --git a/test_program_methods/guest/src/bin/minter.rs b/lee/state_machine/test_methods/guest/src/bin/minter.rs similarity index 100% rename from test_program_methods/guest/src/bin/minter.rs rename to lee/state_machine/test_methods/guest/src/bin/minter.rs diff --git a/test_program_methods/guest/src/bin/missing_output.rs b/lee/state_machine/test_methods/guest/src/bin/missing_output.rs similarity index 100% rename from test_program_methods/guest/src/bin/missing_output.rs rename to lee/state_machine/test_methods/guest/src/bin/missing_output.rs diff --git a/test_program_methods/guest/src/bin/modified_transfer.rs b/lee/state_machine/test_methods/guest/src/bin/modified_transfer.rs similarity index 100% rename from test_program_methods/guest/src/bin/modified_transfer.rs rename to lee/state_machine/test_methods/guest/src/bin/modified_transfer.rs diff --git a/test_program_methods/guest/src/bin/nonce_changer.rs b/lee/state_machine/test_methods/guest/src/bin/nonce_changer.rs similarity index 100% rename from test_program_methods/guest/src/bin/nonce_changer.rs rename to lee/state_machine/test_methods/guest/src/bin/nonce_changer.rs diff --git a/test_program_methods/guest/src/bin/noop.rs b/lee/state_machine/test_methods/guest/src/bin/noop.rs similarity index 100% rename from test_program_methods/guest/src/bin/noop.rs rename to lee/state_machine/test_methods/guest/src/bin/noop.rs diff --git a/test_program_methods/guest/src/bin/pda_claimer.rs b/lee/state_machine/test_methods/guest/src/bin/pda_claimer.rs similarity index 100% rename from test_program_methods/guest/src/bin/pda_claimer.rs rename to lee/state_machine/test_methods/guest/src/bin/pda_claimer.rs diff --git a/lee/state_machine/test_methods/guest/src/bin/pda_spend_proxy.rs b/lee/state_machine/test_methods/guest/src/bin/pda_spend_proxy.rs new file mode 100644 index 00000000..d8b9bb5c --- /dev/null +++ b/lee/state_machine/test_methods/guest/src/bin/pda_spend_proxy.rs @@ -0,0 +1,48 @@ +use lee_core::program::{ + AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs, +}; +use risc0_zkvm::serde::to_vec; + +/// Proxy for spending from a private PDA via `simple_transfer`. +/// +/// `pre_states = [pda (authorized), recipient]`. Debits the PDA and credits the recipient. +/// The PDA-to-npk binding is established via `pda_seeds` in the chained call to `simple_transfer`. +type Instruction = (PdaSeed, u128, ProgramId); + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction: (seed, amount, simple_transfer_id), + }, + instruction_words, + ) = read_lee_inputs::(); + + let Ok([first, second]) = <[_; 2]>::try_from(pre_states) else { + return; + }; + + assert!(first.is_authorized, "first pre_state must be authorized"); + + let first_post = AccountPostState::new(first.account.clone()); + let second_post = AccountPostState::new(second.account.clone()); + + let chained_call = ChainedCall { + program_id: simple_transfer_id, + instruction_data: to_vec(&amount).unwrap(), + pre_states: vec![first.clone(), second.clone()], + pda_seeds: vec![seed], + }; + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![first, second], + vec![first_post, second_post], + ) + .with_chained_calls(vec![chained_call]) + .write(); +} diff --git a/test_program_methods/guest/src/bin/private_pda_delegator.rs b/lee/state_machine/test_methods/guest/src/bin/private_pda_delegator.rs similarity index 100% rename from test_program_methods/guest/src/bin/private_pda_delegator.rs rename to lee/state_machine/test_methods/guest/src/bin/private_pda_delegator.rs diff --git a/test_program_methods/guest/src/bin/program_owner_changer.rs b/lee/state_machine/test_methods/guest/src/bin/program_owner_changer.rs similarity index 100% rename from test_program_methods/guest/src/bin/program_owner_changer.rs rename to lee/state_machine/test_methods/guest/src/bin/program_owner_changer.rs diff --git a/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/lee/state_machine/test_methods/guest/src/bin/simple_balance_transfer.rs similarity index 57% rename from test_program_methods/guest/src/bin/simple_balance_transfer.rs rename to lee/state_machine/test_methods/guest/src/bin/simple_balance_transfer.rs index 29149272..addc4a19 100644 --- a/test_program_methods/guest/src/bin/simple_balance_transfer.rs +++ b/lee/state_machine/test_methods/guest/src/bin/simple_balance_transfer.rs @@ -1,4 +1,4 @@ -use lee_core::program::{AccountPostState, ProgramInput, ProgramOutput, read_lee_inputs}; +use lee_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs}; type Instruction = u128; @@ -13,6 +13,21 @@ fn main() { instruction_words, ) = read_lee_inputs::(); + if let Ok([account_pre]) = <[_; 1]>::try_from(pre_states.clone()) { + let account_post = + AccountPostState::new_claimed_if_default(account_pre.account, Claim::Authorized); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + pre_states, + vec![account_post], + ) + .write(); + return; + } + let Ok([sender_pre, receiver_pre]) = <[_; 2]>::try_from(pre_states) else { return; }; @@ -34,8 +49,8 @@ fn main() { instruction_words, vec![sender_pre, receiver_pre], vec![ - AccountPostState::new(sender_post), - AccountPostState::new(receiver_post), + AccountPostState::new_claimed_if_default(sender_post, Claim::Authorized), + AccountPostState::new_claimed_if_default(receiver_post, Claim::Authorized), ], ) .write(); diff --git a/test_program_methods/guest/src/bin/auth_transfer_proxy.rs b/lee/state_machine/test_methods/guest/src/bin/simple_transfer_proxy.rs similarity index 65% rename from test_program_methods/guest/src/bin/auth_transfer_proxy.rs rename to lee/state_machine/test_methods/guest/src/bin/simple_transfer_proxy.rs index a7e2f5be..ce7f1d8e 100644 --- a/test_program_methods/guest/src/bin/auth_transfer_proxy.rs +++ b/lee/state_machine/test_methods/guest/src/bin/simple_transfer_proxy.rs @@ -2,23 +2,23 @@ use lee_core::program::{ AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs, }; -/// PDA authorization program that delegates balance operations to `authenticated_transfer`. +/// PDA authorization program that delegates balance operations to `simple_transfer`. /// -/// The PDA is owned by `authenticated_transfer`, not by this program. This program's role +/// The PDA is owned by `simple_transfer`, not by this program. This program's role /// is solely to provide PDA authorization via `pda_seeds` in chained calls. /// -/// Instruction: `(pda_seed, auth_transfer_id, amount, is_withdraw)`. +/// Instruction: `(pda_seed, simple_transfer_id, amount, is_withdraw)`. /// /// **Init** (`is_withdraw = false`, 1 pre-state `[pda]`): -/// Chains to `authenticated_transfer` with `instruction=0` (init path) and `pda_seeds=[seed]` -/// to initialize the PDA under `authenticated_transfer`'s ownership. +/// Chains to `simple_transfer` with `instruction=0` (init path) and `pda_seeds=[seed]` +/// to initialize the PDA under `simple_transfer`'s ownership. /// /// **Withdraw** (`is_withdraw = true`, 2 pre-states `[pda, recipient]`): -/// Chains to `authenticated_transfer` with the amount and `pda_seeds=[seed]` to authorize +/// Chains to `simple_transfer` with the amount and `pda_seeds=[seed]` to authorize /// the PDA for a balance transfer. The actual balance modification happens in -/// `authenticated_transfer`, not here. +/// `simple_transfer`, not here. /// -/// **Deposit**: done directly via `authenticated_transfer` (no need for this program). +/// **Deposit**: done directly via `simple_transfer` (no need for this program). type Instruction = (PdaSeed, ProgramId, u128, bool); #[expect( @@ -35,7 +35,7 @@ fn main() { self_program_id, caller_program_id, pre_states, - instruction: (pda_seed, auth_transfer_id, amount, is_withdraw), + instruction: (pda_seed, simple_transfer_id, amount, is_withdraw), }, instruction_words, ) = read_lee_inputs::(); @@ -46,19 +46,19 @@ fn main() { }; // Post-states stay unchanged in this program. The actual balance transfer - // happens in the chained call to authenticated_transfer. + // happens in the chained call to simple_transfer. let pda_post = AccountPostState::new(pda_pre.account.clone()); let recipient_post = AccountPostState::new(recipient_pre.account.clone()); - // Chain to authenticated_transfer with pda_seeds to authorize the PDA. + // Chain to simple_transfer with pda_seeds to authorize the PDA. // The circuit's resolve_authorization_and_record_bindings establishes the // private PDA (seed, npk) binding when pda_seeds match the private PDA derivation. let mut auth_pda_pre = pda_pre; auth_pda_pre.is_authorized = true; let auth_call = ChainedCall::new( - auth_transfer_id, + simple_transfer_id, vec![auth_pda_pre, recipient_pre], - &authenticated_transfer_core::Instruction::Transfer { amount }, + &amount, ) .with_pda_seeds(vec![pda_seed]); @@ -72,23 +72,19 @@ fn main() { .with_chained_calls(vec![auth_call]) .write(); } else { - // Init: initialize the PDA under authenticated_transfer's ownership. + // Init: initialize the PDA under simple_transfer's ownership. let Ok([pda_pre]) = <[_; 1]>::try_from(pre_states.clone()) else { panic!("expected exactly 1 pre_state for init: [pda]"); }; let pda_post = AccountPostState::new(pda_pre.account.clone()); - // Chain to authenticated_transfer with instruction=0 (init path) and pda_seeds - // to authorize the PDA. authenticated_transfer will claim it with Claim::Authorized. + // Chain to simple_transfer with instruction=0 (init path) and pda_seeds + // to authorize the PDA. simple_transfer will claim it with Claim::Authorized. let mut auth_pda_pre = pda_pre; auth_pda_pre.is_authorized = true; - let auth_call = ChainedCall::new( - auth_transfer_id, - vec![auth_pda_pre], - &authenticated_transfer_core::Instruction::Initialize, - ) - .with_pda_seeds(vec![pda_seed]); + let auth_call = ChainedCall::new(simple_transfer_id, vec![auth_pda_pre], &amount) + .with_pda_seeds(vec![pda_seed]); ProgramOutput::new( self_program_id, diff --git a/test_program_methods/guest/src/bin/two_pda_claimer.rs b/lee/state_machine/test_methods/guest/src/bin/two_pda_claimer.rs similarity index 100% rename from test_program_methods/guest/src/bin/two_pda_claimer.rs rename to lee/state_machine/test_methods/guest/src/bin/two_pda_claimer.rs diff --git a/test_program_methods/guest/src/bin/validity_window.rs b/lee/state_machine/test_methods/guest/src/bin/validity_window.rs similarity index 100% rename from test_program_methods/guest/src/bin/validity_window.rs rename to lee/state_machine/test_methods/guest/src/bin/validity_window.rs diff --git a/test_program_methods/guest/src/bin/validity_window_chain_caller.rs b/lee/state_machine/test_methods/guest/src/bin/validity_window_chain_caller.rs similarity index 100% rename from test_program_methods/guest/src/bin/validity_window_chain_caller.rs rename to lee/state_machine/test_methods/guest/src/bin/validity_window_chain_caller.rs diff --git a/program_methods/src/lib.rs b/lee/state_machine/test_methods/src/lib.rs similarity index 100% rename from program_methods/src/lib.rs rename to lee/state_machine/test_methods/src/lib.rs diff --git a/lez/common/Cargo.toml b/lez/common/Cargo.toml index a559960d..8b2aa322 100644 --- a/lez/common/Cargo.toml +++ b/lez/common/Cargo.toml @@ -12,6 +12,8 @@ lee.workspace = true lee_core.workspace = true authenticated_transfer_core.workspace = true clock_core.workspace = true +programs.workspace = true +system_accounts.workspace = true anyhow.workspace = true thiserror.workspace = true diff --git a/lez/common/src/lib.rs b/lez/common/src/lib.rs index cfbbbd9b..3cca327b 100644 --- a/lez/common/src/lib.rs +++ b/lez/common/src/lib.rs @@ -12,8 +12,6 @@ pub mod transaction; // TODO: Compile only for tests pub mod test_utils; -pub const PINATA_BASE58: &str = "EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7"; - #[derive( Default, Copy, diff --git a/lez/common/src/test_utils.rs b/lez/common/src/test_utils.rs index 179e9601..7afda3dd 100644 --- a/lez/common/src/test_utils.rs +++ b/lez/common/src/test_utils.rs @@ -44,7 +44,7 @@ pub fn produce_dummy_block( #[must_use] pub fn produce_dummy_empty_transaction() -> LeeTransaction { - let program_id = lee::program::Program::authenticated_transfer_program().id(); + let program_id = programs::authenticated_transfer().id(); let account_ids = vec![]; let nonces = vec![]; let message = lee::public_transaction::Message::try_new( @@ -72,7 +72,7 @@ pub fn create_transaction_native_token_transfer( ) -> LeeTransaction { let account_ids = vec![from, to]; let nonces = vec![nonce.into()]; - let program_id = lee::program::Program::authenticated_transfer_program().id(); + let program_id = programs::authenticated_transfer().id(); let message = lee::public_transaction::Message::try_new( program_id, account_ids, diff --git a/lez/common/src/transaction.rs b/lez/common/src/transaction.rs index 7fb32e39..eec01d4e 100644 --- a/lez/common/src/transaction.rs +++ b/lez/common/src/transaction.rs @@ -80,10 +80,9 @@ impl LeeTransaction { ) -> Result { let diff = self.compute_state_diff(state, block_id, timestamp)?; - let restricted_modification_accounts = lee::CLOCK_PROGRAM_ACCOUNT_IDS - .iter() - .copied() - .chain(std::iter::once(lee::system_faucet_account_id())); + let restricted_modification_accounts = system_accounts::clock_account_ids() + .into_iter() + .chain(std::iter::once(system_accounts::faucet_account_id())); for account_id in restricted_modification_accounts { validate_doesnt_modify_account(state, &diff, account_id)?; } @@ -157,7 +156,7 @@ impl LeeTransaction { state: &V03State, diff: &ValidatedStateDiff, ) -> Result<(), lee::error::LeeError> { - let bridge_account_id = lee::system_bridge_account_id(); + let bridge_account_id = system_accounts::bridge_account_id(); let pre = state.get_account_by_id(bridge_account_id); let Some(post) = diff.public_diff().get(&bridge_account_id).cloned() else { return Ok(()); @@ -229,7 +228,7 @@ pub enum TransactionMalformationError { #[must_use] pub fn clock_invocation(timestamp: clock_core::Instruction) -> lee::PublicTransaction { let message = lee::public_transaction::Message::try_new( - lee::program::Program::clock().id(), + programs::clock().id(), clock_core::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), vec![], timestamp, @@ -261,17 +260,14 @@ fn validate_doesnt_modify_account( #[cfg(test)] mod tests { - use lee::{ - AccountId, CLOCK_01_PROGRAM_ACCOUNT_ID, PrivateKey, PublicKey, V03State, - system_bridge_account_id, system_faucet_account_id, - }; + use lee::{AccountId, PrivateKey, PublicKey, V03State}; use crate::test_utils::create_transaction_native_token_transfer; #[test] fn system_account_ids_are_distinct_and_non_default() { - let faucet = system_faucet_account_id(); - let bridge = system_bridge_account_id(); + let faucet = system_accounts::faucet_account_id(); + let bridge = system_accounts::bridge_account_id(); assert_ne!(faucet, AccountId::default()); assert_ne!(bridge, AccountId::default()); assert_ne!(faucet, bridge); @@ -286,12 +282,12 @@ mod tests { // (an empty diff hides the modification). let sender_key = PrivateKey::try_new([5_u8; 32]).expect("valid key"); let sender_id = AccountId::from(&PublicKey::new_from_private_key(&sender_key)); - let state = V03State::new_with_genesis_accounts(&[(sender_id, 10_000)], vec![], 0); + let state = V03State::new().with_public_account_balances([(sender_id, 10_000)]); let tx = create_transaction_native_token_transfer( sender_id, 0, - CLOCK_01_PROGRAM_ACCOUNT_ID, + system_accounts::clock_account_ids()[0], 100, &sender_key, ); diff --git a/lez/configs/docker-all-in-one/indexer_config.json b/lez/configs/docker-all-in-one/indexer_config.json index f2005ff5..c1ff65b0 100644 --- a/lez/configs/docker-all-in-one/indexer_config.json +++ b/lez/configs/docker-all-in-one/indexer_config.json @@ -1,5 +1,4 @@ { - "home": "./indexer/service", "consensus_info_polling_interval": "1s", "bedrock_config": { "addr": "http://logos-blockchain-node-0:18080" diff --git a/lez/configs/docker-all-in-one/sequencer_config.json b/lez/configs/docker-all-in-one/sequencer_config.json index edb0132a..90b5d5f3 100644 --- a/lez/configs/docker-all-in-one/sequencer_config.json +++ b/lez/configs/docker-all-in-one/sequencer_config.json @@ -13,7 +13,6 @@ "channel_id": "0101010101010101010101010101010101010101010101010101010101010101", "node_url": "http://logos-blockchain-node-0:18080" }, - "indexer_rpc_url": "ws://indexer_service:8779", "genesis": [ { "supply_bridge_account": { diff --git a/lez/cross_zone/Cargo.toml b/lez/cross_zone/Cargo.toml new file mode 100644 index 00000000..465436f1 --- /dev/null +++ b/lez/cross_zone/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cross_zone" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee.workspace = true +lee_core.workspace = true +programs.workspace = true +cross_zone_inbox_core.workspace = true +bridge_lock_core.workspace = true +ping_core.workspace = true +risc0-zkvm.workspace = true diff --git a/lez/cross_zone/src/lib.rs b/lez/cross_zone/src/lib.rs new file mode 100644 index 00000000..544d4286 --- /dev/null +++ b/lez/cross_zone/src/lib.rs @@ -0,0 +1,189 @@ +//! Host-side cross-zone helpers that need program ids (`programs`) or the state +//! machine (`lee`), kept out of the guest-pure cores. Mirrors `system_accounts`: +//! it resolves builtin program ids and bakes them into transactions and genesis +//! accounts for the watcher (sequencer) and verifier (indexer). + +use std::collections::BTreeMap; + +pub use cross_zone_inbox_core::{CrossZoneConfig, CrossZonePeer}; +use cross_zone_inbox_core::{ + CrossZoneMessage, InboxConfig, Instruction, ZoneId, inbox_config_account_id, + inbox_seen_shard_account_id, +}; +use lee_core::{ + account::{Account, AccountId}, + program::ProgramId, +}; + +/// The cross-zone emission fields a watcher or verifier reads off a source +/// transaction, common to every emitter program. +pub struct Emission { + pub target_zone: ZoneId, + pub target_program_id: ProgramId, + pub target_accounts: Vec<[u8; 32]>, + pub payload: Vec, +} + +/// Whether a program may only be invoked by sequencer-origin transactions. +/// +/// The cross-zone inbox is injected solely by the watcher; a user-submitted call +/// must be rejected at ingress, since `TransactionOrigin` is not carried in the +/// block. +#[must_use] +pub fn is_sequencer_only_program(program_id: ProgramId) -> bool { + program_id == programs::cross_zone_inbox().id() +} + +/// Extracts the cross-zone emission from a source transaction. +/// +/// Recognizes the known emitter programs (`ping_sender`, `bridge_lock`). The +/// watcher and verifier both use this so they agree on what a given source tx +/// emits. +#[must_use] +pub fn extract_emission(program_id: ProgramId, instruction_data: &[u32]) -> Option { + if program_id == programs::ping_sender().id() { + let ping_core::SenderInstruction::Send { + target_zone, + target_program_id, + target_accounts, + payload, + .. + } = risc0_zkvm::serde::from_slice(instruction_data).ok()?; + Some(Emission { + target_zone, + target_program_id, + target_accounts, + payload, + }) + } else if program_id == programs::bridge_lock().id() { + let bridge_lock_core::Instruction::Lock { + target_zone, + target_program_id, + target_accounts, + payload, + .. + } = risc0_zkvm::serde::from_slice(instruction_data).ok()?; + Some(Emission { + target_zone, + target_program_id, + target_accounts, + payload, + }) + } else { + None + } +} + +/// Builds the sequencer-origin dispatch transaction. Pure for fixed inputs, so +/// the watcher's injected tx and the indexer's re-derived tx are byte-identical. +fn build_inbox_dispatch_tx( + inbox_id: ProgramId, + msg: &CrossZoneMessage, + target_account_ids: Vec, +) -> lee::PublicTransaction { + let mut account_ids = Vec::with_capacity(target_account_ids.len().saturating_add(2)); + account_ids.push(inbox_config_account_id(inbox_id)); + account_ids.push(inbox_seen_shard_account_id( + inbox_id, + &msg.src_zone, + msg.src_block_id, + )); + account_ids.extend(target_account_ids); + + let message = lee::public_transaction::Message::try_new( + inbox_id, + account_ids, + vec![], + Instruction::Dispatch(msg.clone()), + ) + .expect("inbox dispatch instruction must serialize"); + + lee::PublicTransaction::new( + message, + lee::public_transaction::WitnessSet::from_raw_parts(vec![]), + ) +} + +/// Builds the dispatch transaction for one peer emission. +/// +/// Both the sequencer's watcher and the indexer's verifier go through this so +/// their transactions are byte-identical for the same emission (the basis of the +/// Option B check). +#[must_use] +pub fn build_dispatch_from_emission( + src_zone: ZoneId, + src_block_id: u64, + src_tx_index: u32, + src_program_id: ProgramId, + target_program_id: ProgramId, + target_accounts: &[[u8; 32]], + payload: Vec, +) -> lee::PublicTransaction { + let msg = CrossZoneMessage { + src_zone, + src_block_id, + src_tx_index, + src_program_id, + target_program_id, + payload, + l1_inclusion_witness: None, + }; + let target_ids = target_accounts + .iter() + .copied() + .map(AccountId::new) + .collect(); + build_inbox_dispatch_tx(programs::cross_zone_inbox().id(), &msg, target_ids) +} + +/// Builds the inbox config account a zone seeds into genesis state. +/// +/// Lets the inbox guest authorize inbound peer messages. The sequencer and +/// indexer seed the same account from the same config, keeping their replayed +/// state consistent. +#[must_use] +pub fn build_inbox_config_account( + self_zone: ZoneId, + cross_zone: &CrossZoneConfig, +) -> (AccountId, Account) { + let inbox_id = programs::cross_zone_inbox().id(); + + let mut allowed_targets = BTreeMap::new(); + for peer in &cross_zone.peers { + allowed_targets.insert(peer.channel_id, peer.allowed_targets.clone()); + } + let config = InboxConfig { + self_zone, + allowed_peers: BTreeMap::new(), + allowed_targets, + }; + + let account = Account { + program_owner: inbox_id, + balance: 0, + data: config + .to_bytes() + .try_into() + .expect("inbox config fits in account data"), + nonce: 0_u128.into(), + }; + (inbox_config_account_id(inbox_id), account) +} + +/// Builds the genesis holding account funding a holder's bridgeable balance. +/// +/// Owned by `bridge_lock`, data is the LE balance. Not produced by any +/// transaction, so the sequencer and indexer both seed it through this one +/// builder. +#[must_use] +pub fn build_holding_account(holder: AccountId, amount: u128) -> (AccountId, Account) { + let account = Account { + program_owner: programs::bridge_lock().id(), + data: bridge_lock_core::balance_bytes(amount) + .to_vec() + .try_into() + .expect("balance fits in account data"), + ..Default::default() + }; + (holder, account) +} diff --git a/lez/docker/risc0-base.Dockerfile b/lez/docker/risc0-base.Dockerfile new file mode 100644 index 00000000..dfc6c869 --- /dev/null +++ b/lez/docker/risc0-base.Dockerfile @@ -0,0 +1,47 @@ +# Shared build base: cargo-chef toolchain + risc0 r0vm. +# +# This is the single source of truth for the r0vm install that the sequencer +# and indexer service images depend on. It is consumed as a named build context +# called `risc0_base` (the service Dockerfiles start with `FROM risc0_base`). +# +# Wiring: +# - docker-compose: `build.additional_contexts: { risc0_base: "service:risc0_base" }` +# - CI: built first and passed via `build-contexts: risc0_base=docker-image://...` +FROM lukemathwalker/cargo-chef:latest-rust-1.94.0-slim-trixie + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + libclang-dev \ + clang \ + cmake \ + ninja-build \ + curl \ + unzip \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Install r0vm +# Use quick install for x86-64 (risczero provides binaries only for this linux platform) +# Manual build for other platforms (including arm64 Linux) +RUN ARCH=$(uname -m); \ + if [ "$ARCH" = "x86_64" ]; then \ + echo "Using quick install for $ARCH"; \ + curl -L https://risczero.com/install | bash; \ + export PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}"; \ + rzup install; \ + else \ + echo "Using manual build for $ARCH"; \ + git clone --depth 1 --branch release-3.0 https://github.com/risc0/risc0.git; \ + git clone --depth 1 --branch risc0-1.94.1 https://github.com/risc0/rust.git; \ + cd /risc0; \ + cargo install --locked --path rzup; \ + rzup build --path /rust rust --verbose; \ + cargo install --locked --path risc0/cargo-risczero; \ + fi +ENV PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}" +RUN cp "$(which r0vm)" /usr/local/bin/r0vm +RUN test -x /usr/local/bin/r0vm +RUN r0vm --version diff --git a/lez/explorer_service/docker-compose.yml b/lez/explorer_service/docker-compose.yml index 7b699a39..fa884b0a 100644 --- a/lez/explorer_service/docker-compose.yml +++ b/lez/explorer_service/docker-compose.yml @@ -2,10 +2,10 @@ services: explorer_service: image: lez/explorer_service build: - context: .. - dockerfile: explorer_service/Dockerfile + context: ../.. + dockerfile: lez/explorer_service/Dockerfile container_name: explorer_service environment: - INDEXER_RPC_URL: ${INDEXER_RPC_URL:-http://localhost:8779} + INDEXER_RPC_URL: ${INDEXER_RPC_URL:-http://host.docker.internal:8779} ports: - "8080:8080" diff --git a/lez/indexer/core/Cargo.toml b/lez/indexer/core/Cargo.toml index 3b120f48..9908c4d2 100644 --- a/lez/indexer/core/Cargo.toml +++ b/lez/indexer/core/Cargo.toml @@ -12,13 +12,14 @@ common.workspace = true logos-blockchain-zone-sdk.workspace = true lee.workspace = true lee_core.workspace = true -cross_zone_inbox_core = { workspace = true, features = ["host"] } -bridge_lock_core = { workspace = true, features = ["host"] } -ping_core.workspace = true +cross_zone.workspace = true +cross_zone_inbox_core.workspace = true +programs.workspace = true storage.workspace = true testnet_initial_state.workspace = true anyhow.workspace = true +arc-swap.workspace = true log.workspace = true serde.workspace = true humantime-serde.workspace = true @@ -35,3 +36,5 @@ hex.workspace = true [dev-dependencies] tempfile.workspace = true authenticated_transfer_core.workspace = true +ping_core.workspace = true +bridge_lock_core.workspace = true diff --git a/lez/indexer/core/src/block_store.rs b/lez/indexer/core/src/block_store.rs index 362d994f..2c734a1e 100644 --- a/lez/indexer/core/src/block_store.rs +++ b/lez/indexer/core/src/block_store.rs @@ -235,7 +235,7 @@ mod tests { // sides use, so this also guards against the two drifting. let home = tempdir().unwrap(); let holder = AccountId::new([5; 32]); - let (id, account) = bridge_lock_core::build_holding_account(holder, 42); + let (id, account) = cross_zone::build_holding_account(holder, 42); let storage = IndexerStore::open_db(home.as_ref(), vec![(id, account)]).unwrap(); diff --git a/lez/indexer/core/src/config.rs b/lez/indexer/core/src/config.rs index 4d3c9502..f920fd5b 100644 --- a/lez/indexer/core/src/config.rs +++ b/lez/indexer/core/src/config.rs @@ -1,9 +1,4 @@ -use std::{ - fs::File, - io::BufReader, - path::{Path, PathBuf}, - time::Duration, -}; +use std::{fs::File, io::BufReader, path::Path, time::Duration}; use anyhow::{Context as _, Result}; use common::config::BasicAuth; @@ -23,8 +18,6 @@ pub struct ClientConfig { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IndexerConfig { - /// Home dir of indexer storage. - pub home: PathBuf, #[serde(with = "humantime_serde")] pub consensus_info_polling_interval: Duration, pub bedrock_config: ClientConfig, diff --git a/lez/indexer/core/src/cross_zone_verifier.rs b/lez/indexer/core/src/cross_zone_verifier.rs index 1741282d..6e2a818e 100644 --- a/lez/indexer/core/src/cross_zone_verifier.rs +++ b/lez/indexer/core/src/cross_zone_verifier.rs @@ -6,13 +6,12 @@ use std::{ use anyhow::{Result, bail}; use common::{block::Block, transaction::LeeTransaction}; +use cross_zone::{build_dispatch_from_emission, extract_emission}; use cross_zone_inbox_core::{ - CrossZoneMessage, Instruction as InboxInstruction, MessageKey, ZoneId, - build_dispatch_from_emission, extract_emission, message_key, + CrossZoneMessage, Instruction as InboxInstruction, MessageKey, ZoneId, message_key, }; use futures::StreamExt as _; -use lee::{PublicKey, program::Program}; -use lee_core::program::ProgramId; +use lee::PublicKey; use log::{error, info}; use logos_blockchain_core::mantle::ops::channel::ChannelId; use logos_blockchain_zone_sdk::{ @@ -63,16 +62,15 @@ impl PeerBlocks { } } -/// The indexer-side Option B verifier. For every cross-zone dispatch in a block -/// it re-derives the transaction from the peer's finalized block and rejects it -/// if the bytes differ (a forgery) or the message was already delivered (a -/// replay), so delivery no longer relies on trusting the sequencer. +/// The indexer-side Option B verifier. +/// +/// For every cross-zone dispatch in a block it re-derives the transaction from +/// the peer's finalized block and rejects it if the bytes differ (a forgery) or +/// the message was already delivered (a replay), so delivery no longer relies on +/// trusting the sequencer. #[derive(Clone)] pub struct CrossZoneVerifier { self_zone: ZoneId, - inbox_id: ProgramId, - ping_sender_id: ProgramId, - bridge_lock_id: ProgramId, /// Pinned block-signing key per peer zone, enforced during re-derivation. peer_pubkeys: HashMap, peers: PeerBlocks, @@ -108,9 +106,6 @@ impl CrossZoneVerifier { Some(Self { self_zone, - inbox_id: Program::cross_zone_inbox().id(), - ping_sender_id: Program::ping_sender().id(), - bridge_lock_id: Program::bridge_lock().id(), peer_pubkeys, peers, seen: Arc::new(RwLock::new(HashSet::new())), @@ -121,7 +116,7 @@ impl CrossZoneVerifier { /// first forged or replayed dispatch. The caller halts ingestion on error. pub async fn verify_block(&self, block: &Block) -> Result<()> { for tx in &block.body.transactions { - let Some(msg) = self.decode_dispatch(tx) else { + let Some(msg) = Self::decode_dispatch(tx) else { continue; }; @@ -156,11 +151,11 @@ impl CrossZoneVerifier { /// Decodes a transaction into the cross-zone message it dispatches, or `None` /// if it is not an inbox dispatch. - fn decode_dispatch(&self, tx: &LeeTransaction) -> Option { + fn decode_dispatch(tx: &LeeTransaction) -> Option { let LeeTransaction::Public(public_tx) = tx else { return None; }; - if public_tx.message().program_id != self.inbox_id { + if public_tx.message().program_id != programs::cross_zone_inbox().id() { return None; } match risc0_zkvm::serde::from_slice::( @@ -180,20 +175,20 @@ impl CrossZoneVerifier { // Equivocation defense: the source block must be signed by the peer's // pinned block-signing key, not merely inscribed on the channel. - if let Some(expected) = self.peer_pubkeys.get(&msg.src_zone) { - if !peer_block.is_signed_by(expected) { - bail!( - "forged cross-zone dispatch: peer zone {} block {} is not signed by the pinned block-signing key", - hex::encode(msg.src_zone), - msg.src_block_id - ); - } + if let Some(expected) = self.peer_pubkeys.get(&msg.src_zone) + && !peer_block.is_signed_by(expected) + { + bail!( + "forged cross-zone dispatch: peer zone {} block {} is not signed by the pinned block-signing key", + hex::encode(msg.src_zone), + msg.src_block_id + ); } let emission_tx = peer_block .body .transactions - .get(msg.src_tx_index as usize) + .get(usize::try_from(msg.src_tx_index).expect("u32 index fits in usize")) .ok_or_else(|| { anyhow::anyhow!( "src_tx_index {} out of range in peer block", @@ -205,22 +200,16 @@ impl CrossZoneVerifier { bail!("peer emission transaction is not public"); }; let message = emission_tx.message(); - let emission = extract_emission( - message.program_id, - &message.instruction_data, - self.ping_sender_id, - self.bridge_lock_id, - ) - .ok_or_else(|| { - anyhow::anyhow!("peer transaction at src_tx_index is not a recognized emitter") - })?; + let emission = + extract_emission(message.program_id, &message.instruction_data).ok_or_else(|| { + anyhow::anyhow!("peer transaction at src_tx_index is not a recognized emitter") + })?; if emission.target_zone != self.self_zone { bail!("peer emission targets a different zone"); } Ok(build_dispatch_from_emission( - self.inbox_id, msg.src_zone, msg.src_block_id, msg.src_tx_index, @@ -259,7 +248,7 @@ impl CrossZoneVerifier { block_id ); } - if !waited.is_zero() && waited.as_secs() % LAG_LOG_INTERVAL.as_secs() == 0 { + if !waited.is_zero() && waited.as_secs().is_multiple_of(LAG_LOG_INTERVAL.as_secs()) { info!( "Waiting for peer zone {} to finalize block {} ({}s); reader is behind", hex::encode(zone), @@ -268,12 +257,16 @@ impl CrossZoneVerifier { ); } tokio::time::sleep(Duration::from_secs(1)).await; - waited += Duration::from_secs(1); + waited = waited.saturating_add(Duration::from_secs(1)); } } } /// Reads a peer zone's finalized blocks from Bedrock into the shared cache. +#[expect( + clippy::infinite_loop, + reason = "the peer reader runs for the lifetime of the indexer process" +)] async fn read_peer( zone_indexer: ZoneIndexer, peer_zone: ZoneId, @@ -319,7 +312,6 @@ mod tests { use common::test_utils::produce_dummy_block; use lee::{ PrivateKey, PublicKey, PublicTransaction, - program::Program, public_transaction::{Message, WitnessSet}, }; use ping_core::{SenderInstruction, ping_record_pda}; @@ -337,27 +329,24 @@ mod tests { fn verifier_with_pinned_keys(peer_pubkeys: HashMap) -> CrossZoneVerifier { CrossZoneVerifier { self_zone: SELF_ZONE, - inbox_id: Program::cross_zone_inbox().id(), - ping_sender_id: Program::ping_sender().id(), - bridge_lock_id: Program::bridge_lock().id(), peer_pubkeys, peers: PeerBlocks::default(), seen: Arc::new(RwLock::new(HashSet::new())), } } - /// A ping_sender emission addressed to `SELF_ZONE` carrying `payload`. + /// A `ping_sender` emission addressed to `SELF_ZONE` carrying `payload`. fn emission(payload: &[u8]) -> LeeTransaction { - let receiver_id = Program::ping_receiver().id(); + let receiver_id = programs::ping_receiver().id(); let send = SenderInstruction::Send { - outbox_program_id: Program::cross_zone_outbox().id(), + outbox_program_id: programs::cross_zone_outbox().id(), target_zone: SELF_ZONE, target_program_id: receiver_id, target_accounts: vec![ping_record_pda(receiver_id).into_value()], payload: payload.to_vec(), ordinal: 0, }; - let message = Message::try_new(Program::ping_sender().id(), vec![], vec![], send) + let message = Message::try_new(programs::ping_sender().id(), vec![], vec![], send) .expect("emission serializes"); LeeTransaction::Public(PublicTransaction::new( message, @@ -367,13 +356,12 @@ mod tests { /// The dispatch a watcher would inject for a `PEER_BLOCK_ID` emission of `payload`. fn dispatch(payload: &[u8]) -> LeeTransaction { - let receiver_id = Program::ping_receiver().id(); + let receiver_id = programs::ping_receiver().id(); LeeTransaction::Public(build_dispatch_from_emission( - Program::cross_zone_inbox().id(), PEER_ZONE, PEER_BLOCK_ID, 0, - Program::ping_sender().id(), + programs::ping_sender().id(), receiver_id, &[ping_record_pda(receiver_id).into_value()], payload.to_vec(), diff --git a/lez/indexer/core/src/lib.rs b/lez/indexer/core/src/lib.rs index 6395ca0d..e7f6460a 100644 --- a/lez/indexer/core/src/lib.rs +++ b/lez/indexer/core/src/lib.rs @@ -1,6 +1,7 @@ -use std::sync::Arc; +use std::{path::Path, sync::Arc}; use anyhow::Result; +use arc_swap::ArcSwap; use common::block::Block; // ToDo: Remove after testnet use futures::StreamExt as _; @@ -11,24 +12,33 @@ use logos_blockchain_zone_sdk::{ }; use crate::{ - block_store::IndexerStore, config::IndexerConfig, cross_zone_verifier::CrossZoneVerifier, + block_store::IndexerStore, + config::IndexerConfig, + cross_zone_verifier::CrossZoneVerifier, + status::{IndexerStatus, IndexerSyncStatus}, }; pub mod block_store; pub mod config; pub mod cross_zone_verifier; +pub mod status; #[derive(Clone)] pub struct IndexerCore { pub zone_indexer: Arc>, pub config: IndexerConfig, pub store: IndexerStore, - verifier: Option, + /// Live ingestion status; updated by the ingest stream, read by `status`. + pub status: Arc>, + /// Option B cross-zone verifier; `None` when cross-zone messaging is disabled. + pub verifier: Option, } impl IndexerCore { - pub fn new(config: IndexerConfig) -> Result { - let home = config.home.join("rocksdb"); + pub fn new(config: IndexerConfig, storage_dir: &Path) -> Result { + // Namespace the DB by channel so indexers on different channels can + // share a storage dir without their RocksDB state colliding. + let home = storage_dir.join(format!("rocksdb-{}", config.channel_id)); let basic_auth = config.bedrock_config.auth.clone().map(Into::into); let node = NodeHttpClient::new( @@ -44,12 +54,12 @@ impl IndexerCore { let mut genesis_seed = Vec::new(); if let Some(cross_zone) = config.cross_zone.as_ref() { let self_zone: [u8; 32] = *config.channel_id.as_ref(); - genesis_seed.push(cross_zone_inbox_core::build_inbox_config_account( + genesis_seed.push(cross_zone::build_inbox_config_account( self_zone, cross_zone, )); } for holding in &config.bridge_lock_holdings { - genesis_seed.push(bridge_lock_core::build_holding_account( + genesis_seed.push(cross_zone::build_holding_account( holding.holder, holding.amount, )); @@ -61,12 +71,32 @@ impl IndexerCore { Ok(Self { zone_indexer: Arc::new(zone_indexer), - config, store: IndexerStore::open_db(&home, genesis_seed)?, + config, + status: Arc::new(ArcSwap::from_pointee(IndexerSyncStatus::starting())), verifier, }) } + /// Snapshot of the current ingestion status (sync state + indexed tip). + /// + /// Combines the ingest loop's live status with the L2 tip read fresh from the + /// store, so callers (FFI/RPC) can tell "catching up" from "failed". + #[must_use] + pub fn status(&self) -> IndexerStatus { + let sync = IndexerSyncStatus::clone(&self.status.load()); + let indexed_block_id = self.store.get_last_block_id().ok().flatten(); + IndexerStatus { + sync, + indexed_block_id, + } + } + + /// Atomically publish a new ingestion status for readers of `status`. + fn set_status(&self, status: IndexerSyncStatus) { + self.status.store(Arc::new(status)); + } + pub fn subscribe_parse_block_stream(&self) -> impl futures::Stream> + '_ { let poll_interval = self.config.consensus_info_polling_interval; let initial_cursor = self @@ -87,14 +117,30 @@ impl IndexerCore { let stream = match self.zone_indexer.next_messages(cursor).await { Ok(s) => s, Err(err) => { + // `next_messages` reads L1 consensus info internally, so + // this also covers an unreachable/misconfigured L1 node. error!("Failed to start zone-sdk next_messages stream: {err}"); + self.set_status(IndexerSyncStatus::error(format!( + "cannot reach L1 / read channel: {err}" + ))); tokio::time::sleep(poll_interval).await; continue; } }; let mut stream = std::pin::pin!(stream); + // Flip to Syncing on the first message of this cycle (not merely on + // a successful poll) so the steady-state CaughtUp status doesn't + // flicker. Until then the state stays Starting (cold-start scan of + // empty L1 history) or CaughtUp (idle). + let mut announced_syncing = false; + while let Some((msg, slot)) = stream.next().await { + if !announced_syncing { + self.set_status(IndexerSyncStatus::syncing()); + announced_syncing = true; + } + let zone_block = match msg { ZoneMessage::Block(b) => b, // Non-block messages don't carry a cursor position; the @@ -121,14 +167,14 @@ impl IndexerCore { // Option B: re-derive and verify every cross-zone dispatch // before applying the block. A forged or replayed dispatch // halts ingestion rather than persisting an invalid state. - if let Some(verifier) = &self.verifier { - if let Err(err) = verifier.verify_block(&block).await { - error!( - "Cross-zone verification failed for block {}: {err:#}. Halting indexer ingestion.", - block.header.block_id - ); - return; - } + if let Some(verifier) = &self.verifier + && let Err(err) = verifier.verify_block(&block).await + { + error!( + "Cross-zone verification failed for block {}: {err:#}. Halting indexer ingestion.", + block.header.block_id + ); + return; } // TODO: Remove l1_header placeholder once storage layer @@ -151,7 +197,11 @@ impl IndexerCore { yield Ok(block); } - // Stream ended (caught up to LIB). Sleep then poll again. + // Stream drained: caught up to LIB as of this cycle. Clears any + // prior error (e.g. a transient L1 disconnect that left no + // backlog, so the `Syncing` branch above never ran). Sleep then + // poll again. + self.set_status(IndexerSyncStatus::caught_up()); tokio::time::sleep(poll_interval).await; } } diff --git a/lez/indexer/core/src/status.rs b/lez/indexer/core/src/status.rs new file mode 100644 index 00000000..1193e124 --- /dev/null +++ b/lez/indexer/core/src/status.rs @@ -0,0 +1,103 @@ +use serde::Serialize; + +/// Coarse lifecycle state of the indexer's ingestion loop, so a client can tell +/// "still catching up" apart from "something went wrong". +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum IndexerSyncState { + /// Booted; no ingestion cycle has run yet. + Starting, + /// Streaming finalized messages toward the L1 frontier. + Syncing, + /// Drained the stream up to LIB; idle until new blocks finalize. + CaughtUp, + /// The last cycle failed (e.g. the L1 node is unreachable). See `last_error`. + Error, +} + +/// Live ingestion status owned by the ingest loop: the coarse `state` plus the +/// reason when it is `Error`. +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IndexerSyncStatus { + pub state: IndexerSyncState, + pub last_error: Option, +} + +impl IndexerSyncStatus { + /// Initial status before any ingestion cycle has run. + pub(crate) const fn starting() -> Self { + Self { + state: IndexerSyncState::Starting, + last_error: None, + } + } + + /// Actively streaming finalized messages toward the L1 frontier. + pub(crate) const fn syncing() -> Self { + Self { + state: IndexerSyncState::Syncing, + last_error: None, + } + } + + /// Drained the stream up to LIB; idle until new blocks finalize. + pub(crate) const fn caught_up() -> Self { + Self { + state: IndexerSyncState::CaughtUp, + last_error: None, + } + } + + /// The last cycle failed; `reason` explains why. + pub(crate) const fn error(reason: String) -> Self { + Self { + state: IndexerSyncState::Error, + last_error: Some(reason), + } + } +} + +/// Full status snapshot returned to callers (FFI/RPC): the live [`IndexerSyncStatus`] +/// plus the L2 tip (`indexed_block_id`) read fresh from the store at query time. +/// +/// The tip is tracked by the store, not the ingest loop, so it lives here on the +/// returned snapshot rather than inside the shared [`IndexerSyncStatus`]. +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IndexerStatus { + #[serde(flatten)] + pub sync: IndexerSyncStatus, + pub indexed_block_id: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn indexer_status_serializes_to_flat_object() { + let status = IndexerStatus { + sync: IndexerSyncStatus::error("boom".to_owned()), + indexed_block_id: Some(7), + }; + let value = serde_json::to_value(&status).expect("serialize"); + assert_eq!( + value, + serde_json::json!({ + "state": "error", + "lastError": "boom", + "indexedBlockId": 7, + }) + ); + } + + #[test] + fn caught_up_clears_error() { + let value = serde_json::to_value(IndexerSyncStatus::caught_up()).expect("serialize"); + assert_eq!( + value, + serde_json::json!({ "state": "caught_up", "lastError": null }) + ); + } +} diff --git a/lez/indexer/ffi/Cargo.toml b/lez/indexer/ffi/Cargo.toml index 66f6a518..a1615b75 100644 --- a/lez/indexer/ffi/Cargo.toml +++ b/lez/indexer/ffi/Cargo.toml @@ -6,15 +6,14 @@ version = "0.1.0" [dependencies] lee.workspace = true -indexer_service.workspace = true -indexer_service_rpc = { workspace = true, features = ["client"] } -indexer_service_protocol.workspace = true +indexer_core.workspace = true +indexer_service_protocol = { workspace = true, features = ["convert"] } -url.workspace = true +env_logger.workspace = true log = { workspace = true } tokio = { features = ["rt-multi-thread"], workspace = true } -jsonrpsee.workspace = true -anyhow.workspace = true +futures.workspace = true +serde_json.workspace = true [build-dependencies] cbindgen = "0.29" diff --git a/lez/indexer/ffi/build.rs b/lez/indexer/ffi/build.rs index 92c95407..4d2faae8 100644 --- a/lez/indexer/ffi/build.rs +++ b/lez/indexer/ffi/build.rs @@ -6,6 +6,8 @@ fn main() { cbindgen::Builder::new() .with_crate(crate_dir) .with_language(cbindgen::Language::C) + .with_cpp_compat(true) + .with_pragma_once(true) .generate() .expect("Unable to generate bindings") .write_to_file("indexer_ffi.h"); diff --git a/lez/indexer/ffi/cbindgen.toml b/lez/indexer/ffi/cbindgen.toml deleted file mode 100644 index 79f622b7..00000000 --- a/lez/indexer/ffi/cbindgen.toml +++ /dev/null @@ -1,2 +0,0 @@ -language = "C" # For increased compatibility -no_includes = true diff --git a/lez/indexer/ffi/indexer_ffi.h b/lez/indexer/ffi/indexer_ffi.h index 84aaeae7..8347ad3c 100644 --- a/lez/indexer/ffi/indexer_ffi.h +++ b/lez/indexer/ffi/indexer_ffi.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -22,26 +24,6 @@ typedef enum FfiBedrockStatus { Finalized, } FfiBedrockStatus; -typedef struct Option_u64 Option_u64; - -typedef struct IndexerServiceFFI { - void *indexer_handle; - void *indexer_client; -} IndexerServiceFFI; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_IndexerServiceFFI__OperationStatus { - struct IndexerServiceFFI *value; - enum OperationStatus error; -} PointerResult_IndexerServiceFFI__OperationStatus; - -typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult; - typedef enum PointerKind_Tag { Owned, Borrowed, @@ -72,15 +54,19 @@ typedef struct Runtime { } Runtime; /** - * Simple wrapper around a pointer to a value or an error. + * FFI-owned indexer. * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. + * - An [`IndexerCore`] used to answer queries + * - The background task [`JoinHandle`] that drives ingestion (consuming the block stream so the + * store stays populated) + * - The [`Runtime`] used to run async queries against the store (either owned or borrowed), + * already FFI-safe. */ -typedef struct PointerResult_Runtime__OperationStatus { - struct Runtime *value; - enum OperationStatus error; -} PointerResult_Runtime__OperationStatus; +typedef struct IndexerServiceFFI { + void *core; + void *ingest_handle; + struct Runtime runtime; +} IndexerServiceFFI; /** * Simple wrapper around a pointer to a value or an error. @@ -88,10 +74,26 @@ typedef struct PointerResult_Runtime__OperationStatus { * Pointer is not guaranteed. You should check the error field before * dereferencing the pointer. */ -typedef struct PointerResult_Option_u64_____OperationStatus { - struct Option_u64 *value; +typedef struct PointerResult_IndexerServiceFFI__OperationStatus { + struct IndexerServiceFFI *value; enum OperationStatus error; -} PointerResult_Option_u64_____OperationStatus; +} PointerResult_IndexerServiceFFI__OperationStatus; + +typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult; + +/** + * Result of [`query_last_block`], returned **inline** (no heap allocation, so + * there is no corresponding `free_*` to call). + * + * `block_id` is only meaningful when `error` is `Ok` *and* `is_some` is + * `true`. An `Ok` result with `is_some == false` means the indexer has no + * finalized block yet (an empty chain) โ€” which is distinct from an error. + */ +typedef struct LastBlockIdResult { + uint64_t block_id; + bool is_some; + enum OperationStatus error; +} LastBlockIdResult; typedef uint64_t FfiBlockId; @@ -404,14 +406,22 @@ typedef struct PointerResult_FfiVec_FfiTransaction_____OperationStatus { enum OperationStatus error; } PointerResult_FfiVec_FfiTransaction_____OperationStatus; +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + /** * Creates and starts an indexer based on the provided * configuration file path. * * # Arguments * + * - `runtime`: A runtime for the indexer to run on, or null to have the indexer create and own + * one. * - `config_path`: A pointer to a string representing the path to the configuration file. - * - `port`: Number representing a port, on which indexers RPC will start. + * - `storage_dir`: A pointer to a string naming the directory under which the indexer stores its + * state (`RocksDB`), or null/empty to use the current directory. The host (e.g. a Logos module's + * instance persistence path) owns this location. * * # Returns * @@ -420,17 +430,13 @@ typedef struct PointerResult_FfiVec_FfiTransaction_____OperationStatus { * * # Safety * The caller must ensure that: - * - `runtime` is a valid pointer to a `tokio::runtime::Runtime` instance. + * - `runtime` is either null or a valid pointer to a [`Runtime`] that outlives the indexer. * - `config_path` is a valid pointer to a null-terminated C string. + * - `storage_dir` is either null or a valid pointer to a null-terminated C string. */ InitializedIndexerServiceFFIResult start_indexer(const struct Runtime *runtime, const char *config_path, - uint16_t port); - -/** - * Creates a new [`tokio::runtime::Runtime`]. - */ -struct PointerResult_Runtime__OperationStatus new_runtime(void); + const char *storage_dir); /** * Stops and frees the resources associated with the given indexer service. @@ -452,6 +458,20 @@ struct PointerResult_Runtime__OperationStatus new_runtime(void); */ enum OperationStatus stop_indexer(struct IndexerServiceFFI *indexer); +/** + * Initializes logging for the indexer at `level`. + * + * - `level` is a null-terminated string (`off`/`error`/`warn`/`info`/`debug`/ `trace`, + * case-insensitive); null or unparseable falls back to `info`. + * + * Only the `indexer_ffi` and `indexer_core` targets are enabled! + * + * # Safety + * - `level` must be a valid null-terminated C string, or null. + * - First call to this function wins; subsequent calls are no-ops. + */ +void init_logger(const char *level); + /** * # Safety * It's up to the caller to pass a proper pointer, if somehow from c/c++ side @@ -469,16 +489,40 @@ void free_cstring(char *block); * * # Returns * - * A `PointerResult, OperationStatus>` indicating success or failure. + * A [`LastBlockIdResult`] indicating success or failure. The block id is + * returned inline; nothing needs to be freed. * * # Safety * * The caller must ensure that: - * - `runtime` is a valid pointer to a [`Runtime`] instance. * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. */ -struct PointerResult_Option_u64_____OperationStatus query_last_block(const struct Runtime *runtime, - const struct IndexerServiceFFI *indexer); +struct LastBlockIdResult query_last_block(const struct IndexerServiceFFI *indexer); + +/** + * Query the indexer's current sync status as a JSON C-string. + * + * The JSON schema is owned by `indexer_core` (`IndexerStatus`): an object with + * `state` (`starting`/`syncing`/`caught_up`/`error`), `indexedBlockId`, and + * `lastError`. Lets a client distinguish "still catching up" from "something + * went wrong". + * + * # Arguments + * + * - `indexer`: A pointer to the [`IndexerServiceFFI`] instance to be queried. + * + * # Returns + * + * A heap-allocated, null-terminated JSON string that the caller MUST free with + * `free_cstring`. Returns null on error (null `indexer` pointer or a + * serialization failure). + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. + */ +char *query_status(const struct IndexerServiceFFI *indexer); /** * Query the block by id from indexer. @@ -495,15 +539,13 @@ struct PointerResult_Option_u64_____OperationStatus query_last_block(const struc * # Safety * * The caller must ensure that: - * - `runtime` is a valid pointer to a [`Runtime`] instance. * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. */ -struct PointerResult_FfiBlockOpt__OperationStatus query_block(const struct Runtime *runtime, - const struct IndexerServiceFFI *indexer, +struct PointerResult_FfiBlockOpt__OperationStatus query_block(const struct IndexerServiceFFI *indexer, FfiBlockId block_id); /** - * Query the block by id from indexer. + * Query the block by hash from indexer. * * # Arguments * @@ -517,11 +559,9 @@ struct PointerResult_FfiBlockOpt__OperationStatus query_block(const struct Runti * # Safety * * The caller must ensure that: - * - `runtime` is a valid pointer to a [`Runtime`] instance. * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. */ -struct PointerResult_FfiBlockOpt__OperationStatus query_block_by_hash(const struct Runtime *runtime, - const struct IndexerServiceFFI *indexer, +struct PointerResult_FfiBlockOpt__OperationStatus query_block_by_hash(const struct IndexerServiceFFI *indexer, FfiHashType hash); /** @@ -539,15 +579,13 @@ struct PointerResult_FfiBlockOpt__OperationStatus query_block_by_hash(const stru * # Safety * * The caller must ensure that: - * - `runtime` is a valid pointer to a [`Runtime`] instance. * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. */ -struct PointerResult_FfiAccount__OperationStatus query_account(const struct Runtime *runtime, - const struct IndexerServiceFFI *indexer, +struct PointerResult_FfiAccount__OperationStatus query_account(const struct IndexerServiceFFI *indexer, FfiAccountId account_id); /** - * Query the trasnaction by hash from indexer. + * Query the transaction by hash from indexer. * * # Arguments * @@ -562,10 +600,8 @@ struct PointerResult_FfiAccount__OperationStatus query_account(const struct Runt * * The caller must ensure that: * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. - * - `runtime` is a valid pointer to a [`Runtime`] instance. */ -struct PointerResult_FfiOption_FfiTransaction_____OperationStatus query_transaction(const struct Runtime *runtime, - const struct IndexerServiceFFI *indexer, +struct PointerResult_FfiOption_FfiTransaction_____OperationStatus query_transaction(const struct IndexerServiceFFI *indexer, FfiHashType hash); /** @@ -585,10 +621,8 @@ struct PointerResult_FfiOption_FfiTransaction_____OperationStatus query_transact * * The caller must ensure that: * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. - * - `runtime` is a valid pointer to a [`Runtime`] instance. */ -struct PointerResult_FfiVec_FfiBlock_____OperationStatus query_block_vec(const struct Runtime *runtime, - const struct IndexerServiceFFI *indexer, +struct PointerResult_FfiVec_FfiBlock_____OperationStatus query_block_vec(const struct IndexerServiceFFI *indexer, struct FfiOption_u64 before, uint64_t limit); @@ -604,16 +638,14 @@ struct PointerResult_FfiVec_FfiBlock_____OperationStatus query_block_vec(const s * * # Returns * - * A `PointerResult, OperationStatus>` indicating success or failure. + * A `PointerResult, OperationStatus>` indicating success or failure. * * # Safety * * The caller must ensure that: * - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. - * - `runtime` is a valid pointer to a [`Runtime`] instance. */ -struct PointerResult_FfiVec_FfiTransaction_____OperationStatus query_transactions_by_account(const struct Runtime *runtime, - const struct IndexerServiceFFI *indexer, +struct PointerResult_FfiVec_FfiTransaction_____OperationStatus query_transactions_by_account(const struct IndexerServiceFFI *indexer, FfiAccountId account_id, uint64_t offset, uint64_t limit); @@ -621,9 +653,14 @@ struct PointerResult_FfiVec_FfiTransaction_____OperationStatus query_transaction /** * Frees the resources associated with the given ffi account. * + * Takes ownership of the whole allocation produced by a `query_*` call: the + * outer `Box` (the `PointerResult.value` pointer) *and* its inner + * data buffer. Passing the struct by value previously freed only the inner + * buffer and leaked the outer box. + * * # Arguments * - * - `val`: An instance of `FfiAccount`. + * - `val`: The `*mut FfiAccount` returned in `PointerResult.value`. * * # Returns * @@ -632,12 +669,18 @@ struct PointerResult_FfiVec_FfiTransaction_____OperationStatus query_transaction * # Safety * * The caller must ensure that: - * - `val` is a valid instance of `FfiAccount`. + * - `val` is a pointer to an `FfiAccount` produced by this library and not yet freed. */ -void free_ffi_account(struct FfiAccount val); +void free_ffi_account(struct FfiAccount *val); /** - * Frees the resources associated with the given ffi block. + * Frees the resources owned by an `FfiBlock` value. + * + * This frees the block's transaction bodies (the only heap-owning field); the + * header/status fields are `Copy`. It operates on the struct by value because + * it is an element-level helper, used both for the vector path + * ([`free_ffi_block_vec`]) and the optional path ([`free_ffi_block_opt`]) โ€” in + * neither case is an `FfiBlock` itself wrapped in its own outer box. * * # Arguments * @@ -650,16 +693,20 @@ void free_ffi_account(struct FfiAccount val); * # Safety * * The caller must ensure that: - * - `val` is a valid instance of `FfiBlock`. + * - `val` is a valid instance of `FfiBlock` produced by this library and not yet freed. */ void free_ffi_block(struct FfiBlock val); /** * Frees the resources associated with the given ffi block option. * + * Takes ownership of the whole allocation produced by a `query_*` call: the + * outer `Box` (the `PointerResult.value` pointer), the inner + * `Box` (when present), and that block's transaction bodies. + * * # Arguments * - * - `val`: An instance of `FfiBlockOpt`. + * - `val`: The `*mut FfiBlockOpt` returned in `PointerResult.value`. * * # Returns * @@ -668,16 +715,20 @@ void free_ffi_block(struct FfiBlock val); * # Safety * * The caller must ensure that: - * - `val` is a valid instance of `FfiBlockOpt`. + * - `val` is a pointer to an `FfiBlockOpt` produced by this library and not yet freed. */ -void free_ffi_block_opt(FfiBlockOpt val); +void free_ffi_block_opt(FfiBlockOpt *val); /** * Frees the resources associated with the given ffi block vector. * + * Takes ownership of the whole allocation produced by a `query_*` call: the + * outer `Box>` (the `PointerResult.value` pointer), the + * vector's backing buffer, and every block within it. + * * # Arguments * - * - `val`: An instance of `FfiVec`. + * - `val`: The `*mut FfiVec` returned in `PointerResult.value`. * * # Returns * @@ -686,9 +737,9 @@ void free_ffi_block_opt(FfiBlockOpt val); * # Safety * * The caller must ensure that: - * - `val` is a valid instance of `FfiVec`. + * - `val` is a pointer to an `FfiVec` produced by this library and not yet freed. */ -void free_ffi_block_vec(struct FfiVec_FfiBlock val); +void free_ffi_block_vec(struct FfiVec_FfiBlock *val); /** * Frees the resources associated with the given ffi transaction. @@ -711,9 +762,13 @@ void free_ffi_transaction(struct FfiTransaction val); /** * Frees the resources associated with the given ffi transaction option. * + * Takes ownership of the whole allocation produced by a `query_*` call: the + * outer `Box>` (the `PointerResult.value` pointer), + * the inner `Box` (when present), and its body. + * * # Arguments * - * - `val`: An instance of `FfiOption`. + * - `val`: The `*mut FfiOption` returned in `PointerResult.value`. * * # Returns * @@ -722,16 +777,21 @@ void free_ffi_transaction(struct FfiTransaction val); * # Safety * * The caller must ensure that: - * - `val` is a valid instance of `FfiOption`. + * - `val` is a pointer to an `FfiOption` produced by this library and not yet + * freed. */ -void free_ffi_transaction_opt(struct FfiOption_FfiTransaction val); +void free_ffi_transaction_opt(struct FfiOption_FfiTransaction *val); /** * Frees the resources associated with the given vector of ffi transactions. * + * Takes ownership of the whole allocation produced by a `query_*` call: the + * outer `Box>` (the `PointerResult.value` pointer), the + * vector's backing buffer, and every transaction within it. + * * # Arguments * - * - `val`: An instance of `FfiVec`. + * - `val`: The `*mut FfiVec` returned in `PointerResult.value`. * * # Returns * @@ -740,10 +800,14 @@ void free_ffi_transaction_opt(struct FfiOption_FfiTransaction val); * # Safety * * The caller must ensure that: - * - `val` is a valid instance of `FfiVec`. + * - `val` is a pointer to an `FfiVec` produced by this library and not yet freed. */ -void free_ffi_transaction_vec(struct FfiVec_FfiTransaction val); +void free_ffi_transaction_vec(struct FfiVec_FfiTransaction *val); bool is_ok(const enum OperationStatus *self); bool is_error(const enum OperationStatus *self); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/lez/indexer/ffi/src/api/client.rs b/lez/indexer/ffi/src/api/client.rs deleted file mode 100644 index 825a57de..00000000 --- a/lez/indexer/ffi/src/api/client.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::net::SocketAddr; - -use url::Url; - -use crate::OperationStatus; - -#[derive(Debug, Clone, Copy)] -pub enum UrlProtocol { - Http, - Ws, -} - -impl std::fmt::Display for UrlProtocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Http => write!(f, "http"), - Self::Ws => write!(f, "ws"), - } - } -} - -pub(crate) fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result { - // Convert 0.0.0.0 to 127.0.0.1 for client connections - // When binding to port 0, the server binds to 0.0.0.0: - // but clients need to connect to 127.0.0.1: to work reliably - let url_string = if addr.ip().is_unspecified() { - format!("{protocol}://127.0.0.1:{}", addr.port()) - } else { - format!("{protocol}://{addr}") - }; - - url_string.parse().map_err(|e| { - log::error!("Could not parse indexer url: {e}"); - OperationStatus::InitializationError - }) -} diff --git a/lez/indexer/ffi/src/api/lifecycle.rs b/lez/indexer/ffi/src/api/lifecycle.rs index d124901f..f668f3ee 100644 --- a/lez/indexer/ffi/src/api/lifecycle.rs +++ b/lez/indexer/ffi/src/api/lifecycle.rs @@ -1,14 +1,9 @@ use std::{ffi::c_char, path::PathBuf}; -use crate::{ - IndexerServiceFFI, Runtime, - api::{ - PointerResult, - client::{UrlProtocol, addr_to_url}, - }, - client::{IndexerClient, IndexerClientTrait as _}, - errors::OperationStatus, -}; +use futures::StreamExt as _; +use indexer_core::{IndexerCore, config::IndexerConfig}; + +use crate::{IndexerServiceFFI, Runtime, api::PointerResult, errors::OperationStatus}; pub type InitializedIndexerServiceFFIResult = PointerResult; @@ -17,8 +12,12 @@ pub type InitializedIndexerServiceFFIResult = PointerResult InitializedIndexerServiceFFIResult { - // SAFETY: The caller must ensure the validness of the `runtime` and `config_path` pointers. - unsafe { setup_indexer(runtime, config_path, port) }.map_or_else( + // SAFETY: The caller must ensure the validness of the pointer arguments. + unsafe { setup_indexer(runtime, config_path, storage_dir) }.map_or_else( InitializedIndexerServiceFFIResult::from_error, InitializedIndexerServiceFFIResult::from_value, ) } -/// Creates a new [`tokio::runtime::Runtime`]. -#[unsafe(no_mangle)] -pub extern "C" fn new_runtime() -> PointerResult { - Runtime::new().map_or_else( - |_e| PointerResult::from_error(OperationStatus::InitializationError), - PointerResult::from_value, - ) -} - /// Initializes and starts an indexer based on the provided /// configuration file path. /// /// # Arguments /// +/// - `runtime`: A runtime for the indexer to run on, or null to create and own one. /// - `config_path`: A pointer to a string representing the path to the configuration file. -/// - `port`: Number representing a port, on which indexers RPC will start. +/// - `storage_dir`: A pointer to a string naming the storage directory, or null/empty for `.`. /// /// # Returns /// @@ -66,12 +58,13 @@ pub extern "C" fn new_runtime() -> PointerResult { /// /// # Safety /// The caller must ensure that: -/// - `runtime` is a valid pointer to a `tokio::runtime::Runtime` instance. +/// - `runtime` is either null or a valid pointer to a [`Runtime`] that outlives the indexer. /// - `config_path` is a valid pointer to a null-terminated C string. +/// - `storage_dir` is either null or a valid pointer to a null-terminated C string. unsafe fn setup_indexer( runtime: *const Runtime, config_path: *const c_char, - port: u16, + storage_dir: *const c_char, ) -> Result { let user_config_path = PathBuf::from( unsafe { std::ffi::CStr::from_ptr(config_path) } @@ -81,31 +74,64 @@ unsafe fn setup_indexer( OperationStatus::InitializationError })?, ); - let config = indexer_service::IndexerConfig::from_path(&user_config_path).map_err(|e| { + let config = IndexerConfig::from_path(&user_config_path).map_err(|e| { log::error!("Failed to read config: {e}"); OperationStatus::InitializationError })?; - // SAFETY: The caller must ensure that `runtime` is a valid pointer to a - // `tokio::runtime::Runtime` instance. - let runtime = unsafe { &*runtime }; + // The host owns where state lives. An empty/null `storage_dir` falls back to + // the current directory (matches the standalone service's `--data-dir` + // default), but a Logos module passes its instance persistence path. + let storage_dir = if storage_dir.is_null() { + PathBuf::from(".") + } else { + let storage_dir = unsafe { std::ffi::CStr::from_ptr(storage_dir) } + .to_str() + .map_err(|e| { + log::error!("Could not convert the storage dir to string: {e}"); + OperationStatus::InitializationError + })?; + if storage_dir.is_empty() { + PathBuf::from(".") + } else { + PathBuf::from(storage_dir) + } + }; - let indexer_handle = runtime - .block_on(indexer_service::run_server(config, port)) - .map_err(|e| { - log::error!("Could not start indexer service: {e}"); + // Use the caller's runtime if one was supplied, otherwise create (and own) + // our own. The `Runtime` wrapper drops the underlying tokio runtime only + // when we own it; a borrowed one is left to its external owner. + let runtime = if runtime.is_null() { + Runtime::new().map_err(|e| { + log::error!("Could not create tokio runtime: {e}"); OperationStatus::InitializationError - })?; + })? + } else { + // SAFETY: the caller guarantees `runtime` is valid and outlives the indexer. + let caller = unsafe { &*runtime }; + unsafe { Runtime::from_borrowed(caller.as_ref()) } + }; - let indexer_url = addr_to_url(UrlProtocol::Ws, indexer_handle.addr())?; - let indexer_client = runtime - .block_on(IndexerClient::new(&indexer_url)) - .map_err(|e| { - log::error!("Could not start indexer client: {e}"); - OperationStatus::InitializationError - })?; + let core = IndexerCore::new(config, &storage_dir).map_err(|e| { + log::error!("Could not initialize indexer core: {e}"); + OperationStatus::InitializationError + })?; - Ok(IndexerServiceFFI::new(indexer_handle, indexer_client)) + // The block stream writes each parsed block into the store as a side effect + // of being polled, so we spawn a task that simply drains it. There are no + // subscribers โ€” queries read the store directly via `core()`. + let ingest_core = core.clone(); + let ingest_handle = runtime.spawn(async move { + let mut block_stream = std::pin::pin!(ingest_core.subscribe_parse_block_stream()); + while let Some(result) = block_stream.next().await { + if let Err(e) = result { + log::error!("Indexer ingestion error: {e:#}"); + } + } + log::warn!("Indexer block stream ended"); + }); + + Ok(IndexerServiceFFI::new(core, ingest_handle, runtime)) } /// Stops and frees the resources associated with the given indexer service. diff --git a/lez/indexer/ffi/src/api/logging.rs b/lez/indexer/ffi/src/api/logging.rs new file mode 100644 index 00000000..06c41688 --- /dev/null +++ b/lez/indexer/ffi/src/api/logging.rs @@ -0,0 +1,32 @@ +use std::ffi::{CStr, c_char}; + +use log::LevelFilter; + +/// Initializes logging for the indexer at `level`. +/// +/// - `level` is a null-terminated string (`off`/`error`/`warn`/`info`/`debug`/ `trace`, +/// case-insensitive); null or unparseable falls back to `info`. +/// +/// Only the `indexer_ffi` and `indexer_core` targets are enabled! +/// +/// # Safety +/// - `level` must be a valid null-terminated C string, or null. +/// - First call to this function wins; subsequent calls are no-ops. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn init_logger(level: *const c_char) { + let level = if level.is_null() { + LevelFilter::Info + } else { + unsafe { CStr::from_ptr(level) } + .to_str() + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(LevelFilter::Info) + }; + + let _dontcare = env_logger::Builder::new() + .filter_level(LevelFilter::Off) + .filter_module("indexer_ffi", level) + .filter_module("indexer_core", level) + .try_init(); +} diff --git a/lez/indexer/ffi/src/api/mod.rs b/lez/indexer/ffi/src/api/mod.rs index ea2b91d7..2e4be797 100644 --- a/lez/indexer/ffi/src/api/mod.rs +++ b/lez/indexer/ffi/src/api/mod.rs @@ -1,7 +1,7 @@ pub use result::PointerResult; -pub mod client; pub mod lifecycle; +pub mod logging; pub mod memory; pub mod query; pub mod result; diff --git a/lez/indexer/ffi/src/api/query.rs b/lez/indexer/ffi/src/api/query.rs index f10de598..1943f6d4 100644 --- a/lez/indexer/ffi/src/api/query.rs +++ b/lez/indexer/ffi/src/api/query.rs @@ -1,8 +1,9 @@ -use indexer_service_protocol::{AccountId, HashType}; -use indexer_service_rpc::RpcClient as _; +use std::ffi::{CString, c_char}; + +use indexer_service_protocol::AccountId; use crate::{ - IndexerServiceFFI, Runtime, + IndexerServiceFFI, api::{ PointerResult, types::{ @@ -15,6 +16,45 @@ use crate::{ errors::OperationStatus, }; +/// Result of [`query_last_block`], returned **inline** (no heap allocation, so +/// there is no corresponding `free_*` to call). +/// +/// `block_id` is only meaningful when `error` is `Ok` *and* `is_some` is +/// `true`. An `Ok` result with `is_some == false` means the indexer has no +/// finalized block yet (an empty chain) โ€” which is distinct from an error. +#[repr(C)] +pub struct LastBlockIdResult { + pub block_id: u64, + pub is_some: bool, + pub error: OperationStatus, +} + +impl LastBlockIdResult { + const fn error(error: OperationStatus) -> Self { + Self { + block_id: 0, + is_some: false, + error, + } + } + + const fn none() -> Self { + Self { + block_id: 0, + is_some: false, + error: OperationStatus::Ok, + } + } + + const fn some(block_id: u64) -> Self { + Self { + block_id, + is_some: true, + error: OperationStatus::Ok, + } + } +} + /// Query the last block id from indexer. /// /// # Arguments @@ -23,34 +63,79 @@ use crate::{ /// /// # Returns /// -/// A `PointerResult, OperationStatus>` indicating success or failure. +/// A [`LastBlockIdResult`] indicating success or failure. The block id is +/// returned inline; nothing needs to be freed. /// /// # Safety /// /// The caller must ensure that: -/// - `runtime` is a valid pointer to a [`Runtime`] instance. /// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. #[unsafe(no_mangle)] -pub unsafe extern "C" fn query_last_block( - runtime: *const Runtime, - indexer: *const IndexerServiceFFI, -) -> PointerResult, OperationStatus> { +pub unsafe extern "C" fn query_last_block(indexer: *const IndexerServiceFFI) -> LastBlockIdResult { if indexer.is_null() { log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); + return LastBlockIdResult::error(OperationStatus::NullPointer); } let indexer = unsafe { &*indexer }; - let client = indexer.client(); - let runtime = unsafe { &*runtime }; + indexer.core().store.get_last_block_id().map_or_else( + |e| { + log::error!("Failed to query last block id: {e:#}"); + LastBlockIdResult::error(OperationStatus::ClientError) + }, + |opt| opt.map_or_else(LastBlockIdResult::none, LastBlockIdResult::some), + ) +} - runtime - .block_on(client.get_last_finalized_block_id()) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - PointerResult::from_value, - ) +/// Query the indexer's current sync status as a JSON C-string. +/// +/// The JSON schema is owned by `indexer_core` (`IndexerStatus`): an object with +/// `state` (`starting`/`syncing`/`caught_up`/`error`), `indexedBlockId`, and +/// `lastError`. Lets a client distinguish "still catching up" from "something +/// went wrong". +/// +/// # Arguments +/// +/// - `indexer`: A pointer to the [`IndexerServiceFFI`] instance to be queried. +/// +/// # Returns +/// +/// A heap-allocated, null-terminated JSON string that the caller MUST free with +/// `free_cstring`. Returns null on error (null `indexer` pointer or a +/// serialization failure). +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn query_status(indexer: *const IndexerServiceFFI) -> *mut c_char { + if indexer.is_null() { + log::error!( + "Attempted to query status on a null indexer pointer. This is a bug. Aborting." + ); + return std::ptr::null_mut(); + } + + let indexer = unsafe { &*indexer }; + let status = indexer.core().status(); + + let json = match serde_json::to_string(&status) { + Ok(json) => json, + Err(e) => { + log::error!("Failed to serialize indexer status: {e}"); + return std::ptr::null_mut(); + } + }; + + CString::new(json).map_or_else( + |e| { + log::error!("Indexer status JSON contained an interior nul byte: {e}"); + std::ptr::null_mut() + }, + CString::into_raw, + ) } /// Query the block by id from indexer. @@ -67,11 +152,9 @@ pub unsafe extern "C" fn query_last_block( /// # Safety /// /// The caller must ensure that: -/// - `runtime` is a valid pointer to a [`Runtime`] instance. /// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. #[unsafe(no_mangle)] pub unsafe extern "C" fn query_block( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, block_id: FfiBlockId, ) -> PointerResult { @@ -82,24 +165,23 @@ pub unsafe extern "C" fn query_block( let indexer = unsafe { &*indexer }; - let client = indexer.client(); - let runtime = unsafe { &*runtime }; + indexer.core().store.get_block_at_id(block_id).map_or_else( + |e| { + log::error!("Failed to query block by id: {e:#}"); + PointerResult::from_error(OperationStatus::ClientError) + }, + |block_opt| { + let block_ffi = block_opt.map_or_else(FfiBlockOpt::from_none, |block| { + let block: indexer_service_protocol::Block = block.into(); + FfiBlockOpt::from_value(block.into()) + }); - runtime - .block_on(client.get_block_by_id(block_id)) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |block_opt| { - let block_ffi = block_opt.map_or_else(FfiBlockOpt::from_none, |block| { - FfiBlockOpt::from_value(block.into()) - }); - - PointerResult::from_value(block_ffi) - }, - ) + PointerResult::from_value(block_ffi) + }, + ) } -/// Query the block by id from indexer. +/// Query the block by hash from indexer. /// /// # Arguments /// @@ -113,11 +195,9 @@ pub unsafe extern "C" fn query_block( /// # Safety /// /// The caller must ensure that: -/// - `runtime` is a valid pointer to a [`Runtime`] instance. /// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. #[unsafe(no_mangle)] pub unsafe extern "C" fn query_block_by_hash( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, hash: FfiHashType, ) -> PointerResult { @@ -128,15 +208,18 @@ pub unsafe extern "C" fn query_block_by_hash( let indexer = unsafe { &*indexer }; - let client = indexer.client(); - let runtime = unsafe { &*runtime }; - - runtime - .block_on(client.get_block_by_hash(HashType(hash.data))) + indexer + .core() + .store + .get_block_by_hash(hash.data) .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), + |e| { + log::error!("Failed to query block by hash: {e:#}"); + PointerResult::from_error(OperationStatus::ClientError) + }, |block_opt| { let block_ffi = block_opt.map_or_else(FfiBlockOpt::from_none, |block| { + let block: indexer_service_protocol::Block = block.into(); FfiBlockOpt::from_value(block.into()) }); @@ -159,11 +242,9 @@ pub unsafe extern "C" fn query_block_by_hash( /// # Safety /// /// The caller must ensure that: -/// - `runtime` is a valid pointer to a [`Runtime`] instance. /// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. #[unsafe(no_mangle)] pub unsafe extern "C" fn query_account( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, account_id: FfiAccountId, ) -> PointerResult { @@ -174,23 +255,29 @@ pub unsafe extern "C" fn query_account( let indexer = unsafe { &*indexer }; - let client = indexer.client(); - let runtime = unsafe { &*runtime }; - - runtime - .block_on(client.get_account(AccountId { - value: account_id.data, - })) + // `account_current_state` is the only async store call; drive it on the + // runtime the indexer was started on. + let account_id = AccountId { + value: account_id.data, + }; + indexer + .runtime() + .block_on( + indexer + .core() + .store + .account_current_state(&account_id.into()), + ) .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |acc| { - let acc_lee: lee::Account = acc.try_into().expect("Source is in blocks, must fit"); - PointerResult::from_value(acc_lee.into()) + |e| { + log::error!("Failed to query account: {e:#}"); + PointerResult::from_error(OperationStatus::ClientError) }, + |account| PointerResult::from_value(account.into()), ) } -/// Query the trasnaction by hash from indexer. +/// Query the transaction by hash from indexer. /// /// # Arguments /// @@ -205,10 +292,8 @@ pub unsafe extern "C" fn query_account( /// /// The caller must ensure that: /// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. -/// - `runtime` is a valid pointer to a [`Runtime`] instance. #[unsafe(no_mangle)] pub unsafe extern "C" fn query_transaction( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, hash: FfiHashType, ) -> PointerResult, OperationStatus> { @@ -219,15 +304,18 @@ pub unsafe extern "C" fn query_transaction( let indexer = unsafe { &*indexer }; - let client = indexer.client(); - let runtime = unsafe { &*runtime }; - - runtime - .block_on(client.get_transaction(HashType(hash.data))) + indexer + .core() + .store + .get_transaction_by_hash(hash.data) .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), + |e| { + log::error!("Failed to query transaction: {e:#}"); + PointerResult::from_error(OperationStatus::ClientError) + }, |tx_opt| { let tx_ffi = tx_opt.map_or_else(FfiOption::::from_none, |tx| { + let tx: indexer_service_protocol::Transaction = tx.into(); FfiOption::::from_value(tx.into()) }); @@ -252,10 +340,8 @@ pub unsafe extern "C" fn query_transaction( /// /// The caller must ensure that: /// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. -/// - `runtime` is a valid pointer to a [`Runtime`] instance. #[unsafe(no_mangle)] pub unsafe extern "C" fn query_block_vec( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, before: FfiOption, limit: u64, @@ -267,21 +353,26 @@ pub unsafe extern "C" fn query_block_vec( let indexer = unsafe { &*indexer }; - let client = indexer.client(); - let runtime = unsafe { &*runtime }; - let before_std = before.is_some.then(|| unsafe { *before.value }); - runtime - .block_on(client.get_blocks(before_std, limit)) + indexer + .core() + .store + .get_block_batch(before_std, limit) .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), + |e| { + log::error!("Failed to query block batch: {e:#}"); + PointerResult::from_error(OperationStatus::ClientError) + }, |block_vec| { PointerResult::from_value( block_vec .into_iter() - .map(Into::into) - .collect::>() + .map(|block| { + let block: indexer_service_protocol::Block = block.into(); + block.into() + }) + .collect::>() .into(), ) }, @@ -299,16 +390,14 @@ pub unsafe extern "C" fn query_block_vec( /// /// # Returns /// -/// A `PointerResult, OperationStatus>` indicating success or failure. +/// A `PointerResult, OperationStatus>` indicating success or failure. /// /// # Safety /// /// The caller must ensure that: /// - `indexer` is a valid pointer to a [`IndexerServiceFFI`] instance. -/// - `runtime` is a valid pointer to a [`Runtime`] instance. #[unsafe(no_mangle)] pub unsafe extern "C" fn query_transactions_by_account( - runtime: *const Runtime, indexer: *const IndexerServiceFFI, account_id: FfiAccountId, offset: u64, @@ -321,25 +410,24 @@ pub unsafe extern "C" fn query_transactions_by_account( let indexer = unsafe { &*indexer }; - let client = indexer.client(); - let runtime = unsafe { &*runtime }; - - runtime - .block_on(client.get_transactions_by_account( - AccountId { - value: account_id.data, - }, - offset, - limit, - )) + indexer + .core() + .store + .get_transactions_by_account(account_id.data, offset, limit) .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), + |e| { + log::error!("Failed to query transactions by account: {e:#}"); + PointerResult::from_error(OperationStatus::ClientError) + }, |tx_vec| { PointerResult::from_value( tx_vec .into_iter() - .map(Into::into) - .collect::>() + .map(|tx| { + let tx: indexer_service_protocol::Transaction = tx.into(); + tx.into() + }) + .collect::>() .into(), ) }, diff --git a/lez/indexer/ffi/src/api/types/account.rs b/lez/indexer/ffi/src/api/types/account.rs index 2309b84b..f2eb8e58 100644 --- a/lez/indexer/ffi/src/api/types/account.rs +++ b/lez/indexer/ffi/src/api/types/account.rs @@ -100,9 +100,14 @@ impl From<&FfiAccount> for indexer_service_protocol::Account { /// Frees the resources associated with the given ffi account. /// +/// Takes ownership of the whole allocation produced by a `query_*` call: the +/// outer `Box` (the `PointerResult.value` pointer) *and* its inner +/// data buffer. Passing the struct by value previously freed only the inner +/// buffer and leaked the outer box. +/// /// # Arguments /// -/// - `val`: An instance of `FfiAccount`. +/// - `val`: The `*mut FfiAccount` returned in `PointerResult.value`. /// /// # Returns /// @@ -111,9 +116,15 @@ impl From<&FfiAccount> for indexer_service_protocol::Account { /// # Safety /// /// The caller must ensure that: -/// - `val` is a valid instance of `FfiAccount`. +/// - `val` is a pointer to an `FfiAccount` produced by this library and not yet freed. #[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_account(val: FfiAccount) { - let orig_val: indexer_service_protocol::Account = val.into(); +pub unsafe extern "C" fn free_ffi_account(val: *mut FfiAccount) { + if val.is_null() { + log::error!("Trying to free a null pointer. Exiting"); + return; + } + // Reclaim the outer box, then convert to drop the inner data buffer. + let boxed = unsafe { Box::from_raw(val) }; + let orig_val: indexer_service_protocol::Account = (*boxed).into(); drop(orig_val); } diff --git a/lez/indexer/ffi/src/api/types/block.rs b/lez/indexer/ffi/src/api/types/block.rs index e7ae0760..b652a7fe 100644 --- a/lez/indexer/ffi/src/api/types/block.rs +++ b/lez/indexer/ffi/src/api/types/block.rs @@ -2,7 +2,7 @@ use indexer_service_protocol::{BedrockStatus, Block, BlockHeader, HashType, Sign use crate::api::types::{ FfiBlockId, FfiHashType, FfiOption, FfiSignature, FfiTimestamp, FfiVec, - transaction::free_ffi_transaction_vec, vectors::FfiBlockBody, + transaction::free_transaction_vec_value, vectors::FfiBlockBody, }; #[repr(C)] @@ -91,7 +91,13 @@ impl From for BedrockStatus { } } -/// Frees the resources associated with the given ffi block. +/// Frees the resources owned by an `FfiBlock` value. +/// +/// This frees the block's transaction bodies (the only heap-owning field); the +/// header/status fields are `Copy`. It operates on the struct by value because +/// it is an element-level helper, used both for the vector path +/// ([`free_ffi_block_vec`]) and the optional path ([`free_ffi_block_opt`]) โ€” in +/// neither case is an `FfiBlock` itself wrapped in its own outer box. /// /// # Arguments /// @@ -104,7 +110,7 @@ impl From for BedrockStatus { /// # Safety /// /// The caller must ensure that: -/// - `val` is a valid instance of `FfiBlock`. +/// - `val` is a valid instance of `FfiBlock` produced by this library and not yet freed. #[unsafe(no_mangle)] pub unsafe extern "C" fn free_ffi_block(val: FfiBlock) { // We don't really need all the casts, but just in case @@ -121,16 +127,18 @@ pub unsafe extern "C" fn free_ffi_block(val: FfiBlock) { #[expect(clippy::let_underscore_must_use, reason = "No use for this Copy type")] let _: BedrockStatus = val.bedrock_status.into(); - unsafe { - free_ffi_transaction_vec(ffi_tx_ffi_vec); - }; + free_transaction_vec_value(ffi_tx_ffi_vec); } /// Frees the resources associated with the given ffi block option. /// +/// Takes ownership of the whole allocation produced by a `query_*` call: the +/// outer `Box` (the `PointerResult.value` pointer), the inner +/// `Box` (when present), and that block's transaction bodies. +/// /// # Arguments /// -/// - `val`: An instance of `FfiBlockOpt`. +/// - `val`: The `*mut FfiBlockOpt` returned in `PointerResult.value`. /// /// # Returns /// @@ -139,37 +147,32 @@ pub unsafe extern "C" fn free_ffi_block(val: FfiBlock) { /// # Safety /// /// The caller must ensure that: -/// - `val` is a valid instance of `FfiBlockOpt`. +/// - `val` is a pointer to an `FfiBlockOpt` produced by this library and not yet freed. #[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_block_opt(val: FfiBlockOpt) { - if val.is_some { - let value = unsafe { Box::from_raw(val.value) }; - - // We don't really need all the casts, but just in case - // All except `ffi_tx_ffi_vec` is Copy types, so no need for Drop - let _ = BlockHeader { - block_id: value.header.block_id, - prev_block_hash: HashType(value.header.prev_block_hash.data), - hash: HashType(value.header.hash.data), - timestamp: value.header.timestamp, - signature: Signature(value.header.signature.data), - }; - let ffi_tx_ffi_vec = value.body; - - #[expect(clippy::let_underscore_must_use, reason = "No use for this Copy type")] - let _: BedrockStatus = value.bedrock_status.into(); - +pub unsafe extern "C" fn free_ffi_block_opt(val: *mut FfiBlockOpt) { + if val.is_null() { + log::error!("Trying to free a null pointer. Exiting"); + return; + } + // Reclaim the outer box, then the inner block box (if any). + let opt = unsafe { Box::from_raw(val) }; + if opt.is_some { + let block = unsafe { Box::from_raw(opt.value) }; unsafe { - free_ffi_transaction_vec(ffi_tx_ffi_vec); - }; + free_ffi_block(*block); + } } } /// Frees the resources associated with the given ffi block vector. /// +/// Takes ownership of the whole allocation produced by a `query_*` call: the +/// outer `Box>` (the `PointerResult.value` pointer), the +/// vector's backing buffer, and every block within it. +/// /// # Arguments /// -/// - `val`: An instance of `FfiVec`. +/// - `val`: The `*mut FfiVec` returned in `PointerResult.value`. /// /// # Returns /// @@ -178,10 +181,16 @@ pub unsafe extern "C" fn free_ffi_block_opt(val: FfiBlockOpt) { /// # Safety /// /// The caller must ensure that: -/// - `val` is a valid instance of `FfiVec`. +/// - `val` is a pointer to an `FfiVec` produced by this library and not yet freed. #[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_block_vec(val: FfiVec) { - let ffi_block_std_vec: Vec<_> = val.into(); +pub unsafe extern "C" fn free_ffi_block_vec(val: *mut FfiVec) { + if val.is_null() { + log::error!("Trying to free a null pointer. Exiting"); + return; + } + // Reclaim the outer box, then the backing buffer and each block. + let boxed = unsafe { Box::from_raw(val) }; + let ffi_block_std_vec: Vec<_> = (*boxed).into(); for block in ffi_block_std_vec { unsafe { free_ffi_block(block); diff --git a/lez/indexer/ffi/src/api/types/transaction.rs b/lez/indexer/ffi/src/api/types/transaction.rs index ca733ed3..d5cb9035 100644 --- a/lez/indexer/ffi/src/api/types/transaction.rs +++ b/lez/indexer/ffi/src/api/types/transaction.rs @@ -463,9 +463,13 @@ pub unsafe extern "C" fn free_ffi_transaction(val: FfiTransaction) { /// Frees the resources associated with the given ffi transaction option. /// +/// Takes ownership of the whole allocation produced by a `query_*` call: the +/// outer `Box>` (the `PointerResult.value` pointer), +/// the inner `Box` (when present), and its body. +/// /// # Arguments /// -/// - `val`: An instance of `FfiOption`. +/// - `val`: The `*mut FfiOption` returned in `PointerResult.value`. /// /// # Returns /// @@ -474,48 +478,32 @@ pub unsafe extern "C" fn free_ffi_transaction(val: FfiTransaction) { /// # Safety /// /// The caller must ensure that: -/// - `val` is a valid instance of `FfiOption`. +/// - `val` is a pointer to an `FfiOption` produced by this library and not yet +/// freed. #[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_transaction_opt(val: FfiOption) { - if val.is_some { - let value = unsafe { Box::from_raw(val.value) }; - - match value.kind { - FfiTransactionKind::Public => { - let body = unsafe { Box::from_raw(value.body.public_body) }; - let std_body: PublicTransaction = body.into(); - drop(std_body); - } - FfiTransactionKind::Private => { - let body = unsafe { Box::from_raw(value.body.private_body) }; - let std_body: PrivacyPreservingTransaction = body.into(); - drop(std_body); - } - FfiTransactionKind::ProgramDeploy => { - let body = unsafe { Box::from_raw(value.body.program_deployment_body) }; - let std_body: ProgramDeploymentTransaction = body.into(); - drop(std_body); - } +pub unsafe extern "C" fn free_ffi_transaction_opt(val: *mut FfiOption) { + if val.is_null() { + log::error!("Trying to free a null pointer. Exiting"); + return; + } + // Reclaim the outer box, then the inner transaction box (if any). + let opt = unsafe { Box::from_raw(val) }; + if opt.is_some { + let tx = unsafe { Box::from_raw(opt.value) }; + unsafe { + free_ffi_transaction(*tx); } } } -/// Frees the resources associated with the given vector of ffi transactions. +/// Frees the resources owned by an `FfiVec` value (the backing +/// buffer and each transaction), without owning an outer box. /// -/// # Arguments -/// -/// - `val`: An instance of `FfiVec`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiVec`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_transaction_vec(val: FfiVec) { +/// This is the element-level helper shared by the block free path +/// ([`crate::api::types::block::free_ffi_block`], whose body is a transaction +/// vector held by value) and the public [`free_ffi_transaction_vec`] entry +/// point (which first reclaims the outer box). +pub(crate) fn free_transaction_vec_value(val: FfiVec) { let ffi_tx_std_vec: Vec<_> = val.into(); for tx in ffi_tx_std_vec { unsafe { @@ -524,6 +512,35 @@ pub unsafe extern "C" fn free_ffi_transaction_vec(val: FfiVec) { } } +/// Frees the resources associated with the given vector of ffi transactions. +/// +/// Takes ownership of the whole allocation produced by a `query_*` call: the +/// outer `Box>` (the `PointerResult.value` pointer), the +/// vector's backing buffer, and every transaction within it. +/// +/// # Arguments +/// +/// - `val`: The `*mut FfiVec` returned in `PointerResult.value`. +/// +/// # Returns +/// +/// void. +/// +/// # Safety +/// +/// The caller must ensure that: +/// - `val` is a pointer to an `FfiVec` produced by this library and not yet freed. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free_ffi_transaction_vec(val: *mut FfiVec) { + if val.is_null() { + log::error!("Trying to free a null pointer. Exiting"); + return; + } + // Reclaim the outer box, then the backing buffer and each transaction. + let boxed = unsafe { Box::from_raw(val) }; + free_transaction_vec_value(*boxed); +} + fn cast_validity_window(window: ValidityWindow) -> [u64; 2] { [ window.0.0.unwrap_or_default(), diff --git a/lez/indexer/ffi/src/client.rs b/lez/indexer/ffi/src/client.rs deleted file mode 100644 index f05b350e..00000000 --- a/lez/indexer/ffi/src/client.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::{ops::Deref, sync::Arc}; - -use anyhow::{Context as _, Result}; -use log::info; -pub use url::Url; - -pub trait IndexerClientTrait: Clone { - async fn new(indexer_url: &Url) -> Result; -} - -#[derive(Clone)] -pub struct IndexerClient(Arc); - -impl IndexerClientTrait for IndexerClient { - async fn new(indexer_url: &Url) -> Result { - info!("Connecting to Indexer at {indexer_url}"); - - let client = jsonrpsee::ws_client::WsClientBuilder::default() - .build(indexer_url) - .await - .context("Failed to create websocket client")?; - - Ok(Self(Arc::new(client))) - } -} - -impl Deref for IndexerClient { - type Target = jsonrpsee::ws_client::WsClient; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/lez/indexer/ffi/src/indexer.rs b/lez/indexer/ffi/src/indexer.rs index e8707697..0b6b874a 100644 --- a/lez/indexer/ffi/src/indexer.rs +++ b/lez/indexer/ffi/src/indexer.rs @@ -1,95 +1,66 @@ -use std::{ffi::c_void, net::SocketAddr}; +use std::ffi::c_void; -use indexer_service::IndexerHandle; +use indexer_core::IndexerCore; +use tokio::task::JoinHandle; -use crate::client::IndexerClient; +use crate::Runtime; +/// FFI-owned indexer. +/// +/// - An [`IndexerCore`] used to answer queries +/// - The background task [`JoinHandle`] that drives ingestion (consuming the block stream so the +/// store stays populated) +/// - The [`Runtime`] used to run async queries against the store (either owned or borrowed), +/// already FFI-safe. #[repr(C)] pub struct IndexerServiceFFI { - indexer_handle: *mut c_void, - indexer_client: *mut c_void, + core: *mut c_void, + ingest_handle: *mut c_void, + runtime: Runtime, } impl IndexerServiceFFI { #[must_use] - pub fn new( - indexer_handle: indexer_service::IndexerHandle, - indexer_client: IndexerClient, - ) -> Self { + pub fn new(core: IndexerCore, ingest_handle: JoinHandle<()>, runtime: Runtime) -> Self { Self { - // Box the complex types and convert to opaque pointers - indexer_handle: Box::into_raw(Box::new(indexer_handle)).cast::(), - indexer_client: Box::into_raw(Box::new(indexer_client)).cast::(), + core: Box::into_raw(Box::new(core)).cast::(), + ingest_handle: Box::into_raw(Box::new(ingest_handle)).cast::(), + runtime, } } - /// Helper to take ownership back. + /// Borrow the [`IndexerCore`] to run a query against its store. #[must_use] - pub fn into_parts(mut self) -> (Box, Box) { - let Self { - indexer_handle, - indexer_client, - } = &mut self; - - let indexer_handle_boxed = unsafe { Box::from_raw(indexer_handle.cast::()) }; - let indexer_client_boxed = unsafe { Box::from_raw(indexer_client.cast::()) }; - - // Assigning nulls to prevent double free on drop, since ownership is transferred to caller - *indexer_handle = std::ptr::null_mut(); - *indexer_client = std::ptr::null_mut(); - - (indexer_handle_boxed, indexer_client_boxed) - } - - /// Helper to get indexer handle addr. - #[must_use] - pub const fn addr(&self) -> SocketAddr { - let indexer_handle = unsafe { - self.indexer_handle - .cast::() - .as_ref() - .expect("Indexer Handle must be non-null pointer") - }; - - indexer_handle.addr() - } - - /// Helper to get indexer handle ref. - #[must_use] - pub const fn handle(&self) -> &IndexerHandle { + pub const fn core(&self) -> &IndexerCore { unsafe { - self.indexer_handle - .cast::() + self.core + .cast::() .as_ref() - .expect("Indexer Handle must be non-null pointer") + .expect("IndexerCore must be a non-null pointer") } } - /// Helper to get indexer client ref. + /// Borrow the runtime to `block_on` an async store query. #[must_use] - pub const fn client(&self) -> &IndexerClient { - unsafe { - self.indexer_client - .cast::() - .as_ref() - .expect("Indexer Client must be non-null pointer") - } + pub const fn runtime(&self) -> &Runtime { + &self.runtime } } -// Implement Drop to prevent memory leaks impl Drop for IndexerServiceFFI { fn drop(&mut self) { - let Self { - indexer_handle, - indexer_client, - } = self; + if !self.ingest_handle.is_null() { + let handle = unsafe { Box::from_raw(self.ingest_handle.cast::>()) }; + // stop the background ingestion task before tearing down the core. + handle.abort(); + drop(handle); + } + if !self.core.is_null() { + drop(unsafe { Box::from_raw(self.core.cast::()) }); + } - if !indexer_handle.is_null() { - drop(unsafe { Box::from_raw(indexer_handle.cast::()) }); - } - if !indexer_client.is_null() { - drop(unsafe { Box::from_raw(indexer_client.cast::()) }); - } + // `runtime` field is dropped automatically on return here: + // - if runtime was owned, it is shutdown at this point + // - if it was borrowed, it continues to live within the external owner } } diff --git a/lez/indexer/ffi/src/lib.rs b/lez/indexer/ffi/src/lib.rs index 9e34b111..0ca197c7 100644 --- a/lez/indexer/ffi/src/lib.rs +++ b/lez/indexer/ffi/src/lib.rs @@ -5,7 +5,6 @@ pub use indexer::IndexerServiceFFI; pub use runtime::Runtime; pub mod api; -mod client; mod errors; mod indexer; mod runtime; diff --git a/lez/indexer/service/Dockerfile b/lez/indexer/service/Dockerfile index 3da1eb7f..7499875e 100644 --- a/lez/indexer/service/Dockerfile +++ b/lez/indexer/service/Dockerfile @@ -1,41 +1,5 @@ -# Chef stage - uses pre-built cargo-chef image -FROM lukemathwalker/cargo-chef:latest-rust-1.94.0-slim-trixie AS chef - -# Install build dependencies -RUN apt-get update && apt-get install -y \ - build-essential \ - pkg-config \ - libssl-dev \ - libclang-dev \ - clang \ - cmake \ - ninja-build \ - curl \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Install r0vm -# Use quick install for x86-64 (risczero provides binaries only for this linux platform) -# Manual build for other platforms (including arm64 Linux) -RUN ARCH=$(uname -m); \ - if [ "$ARCH" = "x86_64" ]; then \ - echo "Using quick install for $ARCH"; \ - curl -L https://risczero.com/install | bash; \ - export PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}"; \ - rzup install; \ - else \ - echo "Using manual build for $ARCH"; \ - git clone --depth 1 --branch release-3.0 https://github.com/risc0/risc0.git; \ - git clone --depth 1 --branch r0.1.91.1 https://github.com/risc0/rust.git; \ - cd /risc0; \ - cargo install --path rzup; \ - rzup build --path /rust rust --verbose; \ - cargo install --path risc0/cargo-risczero; \ - fi -ENV PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}" -RUN cp "$(which r0vm)" /usr/local/bin/r0vm -RUN test -x /usr/local/bin/r0vm -RUN r0vm --version +# Chef stage +FROM risc0_base AS chef WORKDIR /indexer_service @@ -69,6 +33,11 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry/index \ # Runtime stage - minimal image FROM debian:trixie-slim +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + # Create non-root user for security RUN useradd -m -u 1000 -s /bin/bash indexer_service_user && \ mkdir -p /indexer_service /etc/indexer_service /var/lib/indexer_service && \ @@ -102,4 +71,4 @@ ENV RUST_LOG=info USER indexer_service_user WORKDIR /indexer_service -CMD ["indexer_service", "/etc/indexer_service/indexer_config.json"] +CMD ["indexer_service", "/etc/indexer_service/indexer_config.json", "--data-dir", "/var/lib/indexer_service"] diff --git a/lez/indexer/service/configs/indexer_config.json b/lez/indexer/service/configs/debug/indexer_config.json similarity index 73% rename from lez/indexer/service/configs/indexer_config.json rename to lez/indexer/service/configs/debug/indexer_config.json index f6a0e07c..85227700 100644 --- a/lez/indexer/service/configs/indexer_config.json +++ b/lez/indexer/service/configs/debug/indexer_config.json @@ -1,8 +1,7 @@ { - "home": ".", "consensus_info_polling_interval": "1s", "bedrock_config": { - "addr": "http://localhost:8080" + "addr": "http://localhost:18080" }, "channel_id": "0101010101010101010101010101010101010101010101010101010101010101" -} \ No newline at end of file +} diff --git a/lez/indexer/service/configs/docker/indexer_config.json b/lez/indexer/service/configs/docker/indexer_config.json new file mode 100644 index 00000000..f083ca27 --- /dev/null +++ b/lez/indexer/service/configs/docker/indexer_config.json @@ -0,0 +1,7 @@ +{ + "consensus_info_polling_interval": "1s", + "bedrock_config": { + "addr": "http://host.docker.internal:18080" + }, + "channel_id": "0101010101010101010101010101010101010101010101010101010101010101" +} diff --git a/lez/indexer/service/docker-compose.yml b/lez/indexer/service/docker-compose.yml index e9189cfc..c32067de 100644 --- a/lez/indexer/service/docker-compose.yml +++ b/lez/indexer/service/docker-compose.yml @@ -1,15 +1,26 @@ services: + # Build-only: shared base image (toolchain + r0vm) referenced as the + # `risc0_base` named context below. It has no long-running command, so it + # only gets built โ€” it exits immediately if started. + risc0_base: + image: lez/risc0_base + build: + context: ../../.. + dockerfile: lez/docker/risc0-base.Dockerfile + indexer_service: image: lez/indexer_service build: - context: ../.. + context: ../../.. dockerfile: lez/indexer/service/Dockerfile + additional_contexts: + risc0_base: "service:risc0_base" container_name: indexer_service ports: - "8779:8779" volumes: # Mount configuration - - ./configs/indexer_config.json:/etc/indexer_service/indexer_config.json + - ./configs/docker/indexer_config.json:/etc/indexer_service/indexer_config.json # Mount data volume - indexer_data:/var/lib/indexer_service diff --git a/lez/indexer/service/src/lib.rs b/lez/indexer/service/src/lib.rs index b0a6e516..b1c57163 100644 --- a/lez/indexer/service/src/lib.rs +++ b/lez/indexer/service/src/lib.rs @@ -1,4 +1,4 @@ -use std::net::SocketAddr; +use std::{net::SocketAddr, path::Path}; use anyhow::{Context as _, Result}; pub use indexer_core::config::*; @@ -65,9 +65,13 @@ impl Drop for IndexerHandle { } } -pub async fn run_server(config: IndexerConfig, port: u16) -> Result { +pub async fn run_server( + config: IndexerConfig, + storage_dir: &Path, + port: u16, +) -> Result { #[cfg(feature = "mock-responses")] - let _ = config; + let _ = (config, storage_dir); let server = Server::builder() .build(SocketAddr::from(([0, 0, 0, 0], port))) @@ -82,8 +86,8 @@ pub async fn run_server(config: IndexerConfig, port: u16) -> Result Result<()> { env_logger::init(); - let Args { config_path, port } = Args::parse(); + let Args { + config_path, + port, + data_dir, + } = Args::parse(); let cancellation_token = listen_for_shutdown_signal(); let config = indexer_service::IndexerConfig::from_path(&config_path)?; - let indexer_handle = indexer_service::run_server(config, port).await?; + let indexer_handle = indexer_service::run_server(config, data_dir.as_path(), port).await?; tokio::select! { () = cancellation_token.cancelled() => { diff --git a/lez/indexer/service/src/service.rs b/lez/indexer/service/src/service.rs index a959b80c..7a8ed90f 100644 --- a/lez/indexer/service/src/service.rs +++ b/lez/indexer/service/src/service.rs @@ -1,4 +1,4 @@ -use std::{pin::pin, sync::Arc}; +use std::{path::Path, pin::pin, sync::Arc}; use anyhow::{Context as _, Result, bail}; use arc_swap::ArcSwap; @@ -19,8 +19,8 @@ pub struct IndexerService { } impl IndexerService { - pub fn new(config: IndexerConfig) -> Result { - let indexer = IndexerCore::new(config)?; + pub fn new(config: IndexerConfig, storage_dir: &Path) -> Result { + let indexer = IndexerCore::new(config, storage_dir)?; let subscription_service = SubscriptionService::spawn_new(indexer.clone()); Ok(Self { diff --git a/lez/programs/Cargo.toml b/lez/programs/Cargo.toml new file mode 100644 index 00000000..707c3d6b --- /dev/null +++ b/lez/programs/Cargo.toml @@ -0,0 +1,141 @@ +[package] +name = "programs" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[[bin]] +name = "amm" +path = "amm/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "associated_token_account" +path = "associated_token_account/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "authenticated_transfer" +path = "authenticated_transfer/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "bridge" +path = "bridge/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "clock" +path = "clock/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "faucet" +path = "faucet/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "pinata" +path = "pinata/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "pinata_token" +path = "pinata_token/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "token" +path = "token/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "vault" +path = "vault/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "cross_zone_outbox" +path = "cross_zone_outbox/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "cross_zone_inbox" +path = "cross_zone_inbox/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "ping_sender" +path = "ping_sender/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "ping_receiver" +path = "ping_receiver/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "bridge_lock" +path = "bridge_lock/src/main.rs" +required-features = ["programs"] + +[[bin]] +name = "wrapped_token" +path = "wrapped_token/src/main.rs" +required-features = ["programs"] + +[features] +# TODO: Uncomment once https://github.com/risc0/risc0/issues/3772 is resolved. +# default = ["artifacts"] + +# This feature is only usable for library target. +# Activating this will cause library to include built binary artifacts and pack them into `Program` structs. +artifacts = ["dep:build_utils", "dep:lee"] + +# Import dependencies required for program binaries. +# You don't need this if you only want to use the library target. +programs = [ + "dep:lee_core", + "dep:risc0-zkvm", + "dep:amm_program", + "dep:associated_token_account_program", + "dep:token_program", + "dep:amm_core", + "dep:associated_token_account_core", + "dep:authenticated_transfer_core", + "dep:bridge_core", + "dep:clock_core", + "dep:faucet_core", + "dep:token_core", + "dep:vault_core", + "dep:cross_zone_inbox_core", + "dep:cross_zone_outbox_core", + "dep:bridge_lock_core", + "dep:wrapped_token_core", + "dep:ping_core", +] + +[dependencies] +lee = { workspace = true, optional = true } +lee_core = { workspace = true, optional = true } +risc0-zkvm = { workspace = true, optional = true } +amm_core = { workspace = true, optional = true } +associated_token_account_core = { workspace = true, optional = true } +authenticated_transfer_core = { workspace = true, optional = true } +bridge_core = { workspace = true, optional = true } +clock_core = { workspace = true, optional = true } +faucet_core = { workspace = true, optional = true } +token_core = { workspace = true, optional = true } +vault_core = { workspace = true, optional = true } +cross_zone_inbox_core = { workspace = true, optional = true } +cross_zone_outbox_core = { workspace = true, optional = true } +bridge_lock_core = { workspace = true, optional = true } +wrapped_token_core = { workspace = true, optional = true } +ping_core = { workspace = true, optional = true } + +amm_program = { path = "amm", optional = true } +associated_token_account_program = { path = "associated_token_account", optional = true } +token_program = { path = "token", optional = true } + +[build-dependencies] +build_utils = { workspace = true, optional = true } diff --git a/lez/programs/README.md b/lez/programs/README.md new file mode 100644 index 00000000..b52f6663 --- /dev/null +++ b/lez/programs/README.md @@ -0,0 +1,22 @@ +# Programs + +This crate serves two purposes at once: + +1. Provide one entrypoint for `cargo risczero build` to build guest binaries used in LEZ in one shot. +2. Provide access to the built binaries wrapped with `Program` type. + +## Binaries + +This crate contains binaries taken from sub-directories: one per each program. This binaries are meant to be compiled with `cargo risczero build`. No other use is intended for them. + +## Library + +You may import this crate as a library but it will only make sense if you enable `artifacts` feature flag. +Enabling this flag will make crate expect that [`binaries`](#binaries) where already built and put in the right place (use `just build-artifacts` for that). + +## Why not just `risc0_build::embed_methods()` ? + +Because this will either provide non-deterministic guest build or requires Docker. +And forcing to use Docker to build the project is not an option for us especially because we also build Docker images for our services, which would mean we would have to call docker from docker (and this is not really feasible). + +`risc0_build::embed_methods()` works well when you don't need deterministic build or Docker is not a problem. This is the case for our tests and we use it there. diff --git a/programs/amm/Cargo.toml b/lez/programs/amm/Cargo.toml similarity index 90% rename from programs/amm/Cargo.toml rename to lez/programs/amm/Cargo.toml index 5a4be879..171efbd9 100644 --- a/programs/amm/Cargo.toml +++ b/lez/programs/amm/Cargo.toml @@ -14,3 +14,4 @@ amm_core.workspace = true [dev-dependencies] lee = { workspace = true, features = ["test-utils"] } +programs = { workspace = true } diff --git a/programs/amm/core/Cargo.toml b/lez/programs/amm/core/Cargo.toml similarity index 100% rename from programs/amm/core/Cargo.toml rename to lez/programs/amm/core/Cargo.toml diff --git a/programs/amm/core/src/lib.rs b/lez/programs/amm/core/src/lib.rs similarity index 100% rename from programs/amm/core/src/lib.rs rename to lez/programs/amm/core/src/lib.rs diff --git a/programs/amm/src/add.rs b/lez/programs/amm/src/add.rs similarity index 100% rename from programs/amm/src/add.rs rename to lez/programs/amm/src/add.rs diff --git a/programs/amm/src/lib.rs b/lez/programs/amm/src/lib.rs similarity index 100% rename from programs/amm/src/lib.rs rename to lez/programs/amm/src/lib.rs diff --git a/program_methods/guest/src/bin/amm.rs b/lez/programs/amm/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/amm.rs rename to lez/programs/amm/src/main.rs diff --git a/programs/amm/src/new_definition.rs b/lez/programs/amm/src/new_definition.rs similarity index 100% rename from programs/amm/src/new_definition.rs rename to lez/programs/amm/src/new_definition.rs diff --git a/programs/amm/src/remove.rs b/lez/programs/amm/src/remove.rs similarity index 100% rename from programs/amm/src/remove.rs rename to lez/programs/amm/src/remove.rs diff --git a/programs/amm/src/swap.rs b/lez/programs/amm/src/swap.rs similarity index 100% rename from programs/amm/src/swap.rs rename to lez/programs/amm/src/swap.rs diff --git a/programs/amm/src/tests.rs b/lez/programs/amm/src/tests.rs similarity index 96% rename from programs/amm/src/tests.rs rename to lez/programs/amm/src/tests.rs index d93bedb2..e98c33a0 100644 --- a/programs/amm/src/tests.rs +++ b/lez/programs/amm/src/tests.rs @@ -4,9 +4,7 @@ use amm_core::{ PoolDefinition, compute_liquidity_token_pda, compute_liquidity_token_pda_seed, compute_pool_pda, compute_vault_pda, compute_vault_pda_seed, }; -use lee::{ - PrivateKey, PublicKey, PublicTransaction, V03State, program::Program, public_transaction, -}; +use lee::{PrivateKey, PublicKey, PublicTransaction, V03State, public_transaction}; use lee_core::{ account::{Account, AccountId, AccountWithMetadata, Data}, program::{ChainedCall, ProgramId}, @@ -1295,14 +1293,14 @@ impl BalanceForExeTests { impl IdForExeTests { fn pool_definition_id() -> AccountId { amm_core::compute_pool_pda( - Program::amm().id(), + programs::amm().id(), Self::token_a_definition_id(), Self::token_b_definition_id(), ) } fn token_lp_definition_id() -> AccountId { - amm_core::compute_liquidity_token_pda(Program::amm().id(), Self::pool_definition_id()) + amm_core::compute_liquidity_token_pda(programs::amm().id(), Self::pool_definition_id()) } fn token_a_definition_id() -> AccountId { @@ -1333,7 +1331,7 @@ impl IdForExeTests { fn vault_a_id() -> AccountId { amm_core::compute_vault_pda( - Program::amm().id(), + programs::amm().id(), Self::pool_definition_id(), Self::token_a_definition_id(), ) @@ -1341,7 +1339,7 @@ impl IdForExeTests { fn vault_b_id() -> AccountId { amm_core::compute_vault_pda( - Program::amm().id(), + programs::amm().id(), Self::pool_definition_id(), Self::token_b_definition_id(), ) @@ -1351,7 +1349,7 @@ impl IdForExeTests { impl AccountsForExeTests { fn user_token_a_holding() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1363,7 +1361,7 @@ impl AccountsForExeTests { fn user_token_b_holding() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1375,7 +1373,7 @@ impl AccountsForExeTests { fn pool_definition_init() -> Account { Account { - program_owner: Program::amm().id(), + program_owner: programs::amm().id(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForExeTests::token_a_definition_id(), @@ -1395,7 +1393,7 @@ impl AccountsForExeTests { fn token_a_definition_account() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), @@ -1408,7 +1406,7 @@ impl AccountsForExeTests { fn token_b_definition_acc() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), @@ -1421,7 +1419,7 @@ impl AccountsForExeTests { fn token_lp_definition_acc() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), @@ -1434,7 +1432,7 @@ impl AccountsForExeTests { fn vault_a_init() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1446,7 +1444,7 @@ impl AccountsForExeTests { fn vault_b_init() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1458,7 +1456,7 @@ impl AccountsForExeTests { fn user_token_lp_holding() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_lp_definition_id(), @@ -1470,7 +1468,7 @@ impl AccountsForExeTests { fn vault_a_swap_1() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1482,7 +1480,7 @@ impl AccountsForExeTests { fn vault_b_swap_1() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1494,7 +1492,7 @@ impl AccountsForExeTests { fn pool_definition_swap_1() -> Account { Account { - program_owner: Program::amm().id(), + program_owner: programs::amm().id(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForExeTests::token_a_definition_id(), @@ -1514,7 +1512,7 @@ impl AccountsForExeTests { fn user_token_a_holding_swap_1() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1526,7 +1524,7 @@ impl AccountsForExeTests { fn user_token_b_holding_swap_1() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1538,7 +1536,7 @@ impl AccountsForExeTests { fn vault_a_swap_2() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1550,7 +1548,7 @@ impl AccountsForExeTests { fn vault_b_swap_2() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1562,7 +1560,7 @@ impl AccountsForExeTests { fn pool_definition_swap_2() -> Account { Account { - program_owner: Program::amm().id(), + program_owner: programs::amm().id(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForExeTests::token_a_definition_id(), @@ -1582,7 +1580,7 @@ impl AccountsForExeTests { fn user_token_a_holding_swap_2() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1594,7 +1592,7 @@ impl AccountsForExeTests { fn user_token_b_holding_swap_2() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1606,7 +1604,7 @@ impl AccountsForExeTests { fn vault_a_add() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1618,7 +1616,7 @@ impl AccountsForExeTests { fn vault_b_add() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1630,7 +1628,7 @@ impl AccountsForExeTests { fn pool_definition_add() -> Account { Account { - program_owner: Program::amm().id(), + program_owner: programs::amm().id(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForExeTests::token_a_definition_id(), @@ -1650,7 +1648,7 @@ impl AccountsForExeTests { fn user_token_a_holding_add() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1662,7 +1660,7 @@ impl AccountsForExeTests { fn user_token_b_holding_add() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1674,7 +1672,7 @@ impl AccountsForExeTests { fn user_token_lp_holding_add() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_lp_definition_id(), @@ -1686,7 +1684,7 @@ impl AccountsForExeTests { fn token_lp_definition_add() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), @@ -1699,7 +1697,7 @@ impl AccountsForExeTests { fn vault_a_remove() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1711,7 +1709,7 @@ impl AccountsForExeTests { fn vault_b_remove() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1723,7 +1721,7 @@ impl AccountsForExeTests { fn pool_definition_remove() -> Account { Account { - program_owner: Program::amm().id(), + program_owner: programs::amm().id(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForExeTests::token_a_definition_id(), @@ -1743,7 +1741,7 @@ impl AccountsForExeTests { fn user_token_a_holding_remove() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1755,7 +1753,7 @@ impl AccountsForExeTests { fn user_token_b_holding_remove() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1767,7 +1765,7 @@ impl AccountsForExeTests { fn user_token_lp_holding_remove() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_lp_definition_id(), @@ -1779,7 +1777,7 @@ impl AccountsForExeTests { fn token_lp_definition_remove() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), @@ -1792,7 +1790,7 @@ impl AccountsForExeTests { fn token_lp_definition_init_inactive() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), @@ -1805,7 +1803,7 @@ impl AccountsForExeTests { fn vault_a_init_inactive() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1817,7 +1815,7 @@ impl AccountsForExeTests { fn vault_b_init_inactive() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1829,7 +1827,7 @@ impl AccountsForExeTests { fn pool_definition_inactive() -> Account { Account { - program_owner: Program::amm().id(), + program_owner: programs::amm().id(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForExeTests::token_a_definition_id(), @@ -1849,7 +1847,7 @@ impl AccountsForExeTests { fn user_token_a_holding_new_init() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_a_definition_id(), @@ -1861,7 +1859,7 @@ impl AccountsForExeTests { fn user_token_b_holding_new_init() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_b_definition_id(), @@ -1873,7 +1871,7 @@ impl AccountsForExeTests { fn user_token_lp_holding_new_init() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_lp_definition_id(), @@ -1885,7 +1883,7 @@ impl AccountsForExeTests { fn token_lp_definition_new_init() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { name: String::from("LP Token"), @@ -1898,7 +1896,7 @@ impl AccountsForExeTests { fn pool_definition_new_init() -> Account { Account { - program_owner: Program::amm().id(), + program_owner: programs::amm().id(), balance: 0_u128, data: Data::from(&PoolDefinition { definition_token_a_id: IdForExeTests::token_a_definition_id(), @@ -1918,7 +1916,7 @@ impl AccountsForExeTests { fn user_token_lp_holding_init_zero() -> Account { Account { - program_owner: Program::token().id(), + program_owner: programs::token().id(), balance: 0_u128, data: Data::from(&TokenHolding::Fungible { definition_id: IdForExeTests::token_lp_definition_id(), @@ -3035,68 +3033,73 @@ fn new_definition_lp_symmetric_amounts() { } fn state_for_amm_tests() -> V03State { - let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); - state.force_insert_account( - IdForExeTests::pool_definition_id(), - AccountsForExeTests::pool_definition_init(), - ); - state.force_insert_account( - IdForExeTests::token_a_definition_id(), - AccountsForExeTests::token_a_definition_account(), - ); - state.force_insert_account( - IdForExeTests::token_b_definition_id(), - AccountsForExeTests::token_b_definition_acc(), - ); - state.force_insert_account( - IdForExeTests::token_lp_definition_id(), - AccountsForExeTests::token_lp_definition_acc(), - ); - state.force_insert_account( - IdForExeTests::user_token_a_id(), - AccountsForExeTests::user_token_a_holding(), - ); - state.force_insert_account( - IdForExeTests::user_token_b_id(), - AccountsForExeTests::user_token_b_holding(), - ); - state.force_insert_account( - IdForExeTests::user_token_lp_id(), - AccountsForExeTests::user_token_lp_holding(), - ); - state.force_insert_account( - IdForExeTests::vault_a_id(), - AccountsForExeTests::vault_a_init(), - ); - state.force_insert_account( - IdForExeTests::vault_b_id(), - AccountsForExeTests::vault_b_init(), - ); + let public_state = [ + ( + IdForExeTests::pool_definition_id(), + AccountsForExeTests::pool_definition_init(), + ), + ( + IdForExeTests::token_a_definition_id(), + AccountsForExeTests::token_a_definition_account(), + ), + ( + IdForExeTests::token_b_definition_id(), + AccountsForExeTests::token_b_definition_acc(), + ), + ( + IdForExeTests::token_lp_definition_id(), + AccountsForExeTests::token_lp_definition_acc(), + ), + ( + IdForExeTests::user_token_a_id(), + AccountsForExeTests::user_token_a_holding(), + ), + ( + IdForExeTests::user_token_b_id(), + AccountsForExeTests::user_token_b_holding(), + ), + ( + IdForExeTests::user_token_lp_id(), + AccountsForExeTests::user_token_lp_holding(), + ), + ( + IdForExeTests::vault_a_id(), + AccountsForExeTests::vault_a_init(), + ), + ( + IdForExeTests::vault_b_id(), + AccountsForExeTests::vault_b_init(), + ), + ]; - state + V03State::new() + .with_public_accounts(public_state) + .with_programs([programs::amm(), programs::token()]) } fn state_for_amm_tests_with_new_def() -> V03State { - let initial_data = []; - let mut state = V03State::new_with_genesis_accounts(&initial_data, vec![], 0); - state.force_insert_account( - IdForExeTests::token_a_definition_id(), - AccountsForExeTests::token_a_definition_account(), - ); - state.force_insert_account( - IdForExeTests::token_b_definition_id(), - AccountsForExeTests::token_b_definition_acc(), - ); - state.force_insert_account( - IdForExeTests::user_token_a_id(), - AccountsForExeTests::user_token_a_holding(), - ); - state.force_insert_account( - IdForExeTests::user_token_b_id(), - AccountsForExeTests::user_token_b_holding(), - ); - state + let public_state = [ + ( + IdForExeTests::token_a_definition_id(), + AccountsForExeTests::token_a_definition_account(), + ), + ( + IdForExeTests::token_b_definition_id(), + AccountsForExeTests::token_b_definition_acc(), + ), + ( + IdForExeTests::user_token_a_id(), + AccountsForExeTests::user_token_a_holding(), + ), + ( + IdForExeTests::user_token_b_id(), + AccountsForExeTests::user_token_b_holding(), + ), + ]; + + V03State::new() + .with_public_accounts(public_state) + .with_programs([programs::amm(), programs::token()]) } #[test] @@ -3110,7 +3113,7 @@ fn simple_amm_remove() { }; let message = public_transaction::Message::try_new( - Program::amm().id(), + programs::amm().id(), vec![ IdForExeTests::pool_definition_id(), IdForExeTests::vault_a_id(), @@ -3183,11 +3186,11 @@ fn simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { let instruction = amm_core::Instruction::NewDefinition { token_a_amount: BalanceForExeTests::vault_a_balance_init(), token_b_amount: BalanceForExeTests::vault_b_balance_init(), - amm_program_id: Program::amm().id(), + amm_program_id: programs::amm().id(), }; let message = public_transaction::Message::try_new( - Program::amm().id(), + programs::amm().id(), vec![ IdForExeTests::pool_definition_id(), IdForExeTests::vault_a_id(), @@ -3268,11 +3271,11 @@ fn simple_amm_new_definition_inactive_initialized_pool_init_user_lp() { let instruction = amm_core::Instruction::NewDefinition { token_a_amount: BalanceForExeTests::vault_a_balance_init(), token_b_amount: BalanceForExeTests::vault_b_balance_init(), - amm_program_id: Program::amm().id(), + amm_program_id: programs::amm().id(), }; let message = public_transaction::Message::try_new( - Program::amm().id(), + programs::amm().id(), vec![ IdForExeTests::pool_definition_id(), IdForExeTests::vault_a_id(), @@ -3340,11 +3343,11 @@ fn simple_amm_new_definition_uninitialized_pool() { let instruction = amm_core::Instruction::NewDefinition { token_a_amount: BalanceForExeTests::vault_a_balance_init(), token_b_amount: BalanceForExeTests::vault_b_balance_init(), - amm_program_id: Program::amm().id(), + amm_program_id: programs::amm().id(), }; let message = public_transaction::Message::try_new( - Program::amm().id(), + programs::amm().id(), vec![ IdForExeTests::pool_definition_id(), IdForExeTests::vault_a_id(), @@ -3407,7 +3410,7 @@ fn simple_amm_add() { }; let message = public_transaction::Message::try_new( - Program::amm().id(), + programs::amm().id(), vec![ IdForExeTests::pool_definition_id(), IdForExeTests::vault_a_id(), @@ -3469,7 +3472,7 @@ fn simple_amm_swap_1() { }; let message = public_transaction::Message::try_new( - Program::amm().id(), + programs::amm().id(), vec![ IdForExeTests::pool_definition_id(), IdForExeTests::vault_a_id(), @@ -3519,7 +3522,7 @@ fn simple_amm_swap_2() { token_definition_id_in: IdForExeTests::token_a_definition_id(), }; let message = public_transaction::Message::try_new( - Program::amm().id(), + programs::amm().id(), vec![ IdForExeTests::pool_definition_id(), IdForExeTests::vault_a_id(), diff --git a/lez/programs/associated_token_account/Cargo.toml b/lez/programs/associated_token_account/Cargo.toml new file mode 100644 index 00000000..494b179f --- /dev/null +++ b/lez/programs/associated_token_account/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "associated_token_account_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[dependencies] +lee_core.workspace = true +token_core.workspace = true +associated_token_account_core.workspace = true diff --git a/programs/associated_token_account/core/Cargo.toml b/lez/programs/associated_token_account/core/Cargo.toml similarity index 81% rename from programs/associated_token_account/core/Cargo.toml rename to lez/programs/associated_token_account/core/Cargo.toml index 9c80e897..9b2ff668 100644 --- a/programs/associated_token_account/core/Cargo.toml +++ b/lez/programs/associated_token_account/core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ata_core" +name = "associated_token_account_core" version = "0.1.0" edition = "2024" license = { workspace = true } diff --git a/programs/associated_token_account/core/src/lib.rs b/lez/programs/associated_token_account/core/src/lib.rs similarity index 100% rename from programs/associated_token_account/core/src/lib.rs rename to lez/programs/associated_token_account/core/src/lib.rs diff --git a/programs/associated_token_account/src/burn.rs b/lez/programs/associated_token_account/src/burn.rs similarity index 88% rename from programs/associated_token_account/src/burn.rs rename to lez/programs/associated_token_account/src/burn.rs index 2aae0dd2..09d1645a 100644 --- a/programs/associated_token_account/src/burn.rs +++ b/lez/programs/associated_token_account/src/burn.rs @@ -16,8 +16,12 @@ pub fn burn_from_associated_token_account( let definition_id = TokenHolding::try_from(&holder_ata.account.data) .expect("Holder ATA must hold a valid token") .definition_id(); - let seed = - ata_core::verify_ata_and_get_seed(&holder_ata, &owner, definition_id, ata_program_id); + let seed = associated_token_account_core::verify_ata_and_get_seed( + &holder_ata, + &owner, + definition_id, + ata_program_id, + ); let post_states = vec![ AccountPostState::new(owner.account.clone()), diff --git a/programs/associated_token_account/src/create.rs b/lez/programs/associated_token_account/src/create.rs similarity index 95% rename from programs/associated_token_account/src/create.rs rename to lez/programs/associated_token_account/src/create.rs index c910ab5b..4e1b2074 100644 --- a/programs/associated_token_account/src/create.rs +++ b/lez/programs/associated_token_account/src/create.rs @@ -11,7 +11,7 @@ pub fn create_associated_token_account( ) -> (Vec, Vec) { // No authorization check needed: create is idempotent, so anyone can call it safely. let token_program_id = token_definition.account.program_owner; - let ata_seed = ata_core::verify_ata_and_get_seed( + let ata_seed = associated_token_account_core::verify_ata_and_get_seed( &ata_account, &owner, token_definition.account_id, diff --git a/programs/associated_token_account/src/lib.rs b/lez/programs/associated_token_account/src/lib.rs similarity index 73% rename from programs/associated_token_account/src/lib.rs rename to lez/programs/associated_token_account/src/lib.rs index 13740f0a..fc11902f 100644 --- a/programs/associated_token_account/src/lib.rs +++ b/lez/programs/associated_token_account/src/lib.rs @@ -1,6 +1,6 @@ //! The Associated Token Account Program implementation. -pub use ata_core as core; +pub use associated_token_account_core as core; pub mod burn; pub mod create; diff --git a/program_methods/guest/src/bin/associated_token_account.rs b/lez/programs/associated_token_account/src/main.rs similarity index 85% rename from program_methods/guest/src/bin/associated_token_account.rs rename to lez/programs/associated_token_account/src/main.rs index a32bbf9b..2eaa2f3c 100644 --- a/program_methods/guest/src/bin/associated_token_account.rs +++ b/lez/programs/associated_token_account/src/main.rs @@ -1,4 +1,4 @@ -use ata_core::Instruction; +use associated_token_account_core::Instruction; use lee_core::program::{ProgramInput, ProgramOutput, read_lee_inputs}; fn main() { @@ -19,7 +19,7 @@ fn main() { let [owner, token_definition, ata_account] = pre_states .try_into() .expect("Create instruction requires exactly three accounts"); - ata_program::create::create_associated_token_account( + associated_token_account_program::create::create_associated_token_account( owner, token_definition, ata_account, @@ -33,7 +33,7 @@ fn main() { let [owner, sender_ata, recipient] = pre_states .try_into() .expect("Transfer instruction requires exactly three accounts"); - ata_program::transfer::transfer_from_associated_token_account( + associated_token_account_program::transfer::transfer_from_associated_token_account( owner, sender_ata, recipient, @@ -48,7 +48,7 @@ fn main() { let [owner, holder_ata, token_definition] = pre_states .try_into() .expect("Burn instruction requires exactly three accounts"); - ata_program::burn::burn_from_associated_token_account( + associated_token_account_program::burn::burn_from_associated_token_account( owner, holder_ata, token_definition, diff --git a/programs/associated_token_account/src/tests.rs b/lez/programs/associated_token_account/src/tests.rs similarity index 97% rename from programs/associated_token_account/src/tests.rs rename to lez/programs/associated_token_account/src/tests.rs index 7717eae1..f244f6cd 100644 --- a/programs/associated_token_account/src/tests.rs +++ b/lez/programs/associated_token_account/src/tests.rs @@ -1,6 +1,6 @@ #![cfg(test)] -use ata_core::{compute_ata_seed, get_associated_token_account_id}; +use associated_token_account_core::{compute_ata_seed, get_associated_token_account_id}; use lee_core::account::{Account, AccountId, AccountWithMetadata, Data}; use token_core::{TokenDefinition, TokenHolding}; diff --git a/programs/associated_token_account/src/transfer.rs b/lez/programs/associated_token_account/src/transfer.rs similarity index 88% rename from programs/associated_token_account/src/transfer.rs rename to lez/programs/associated_token_account/src/transfer.rs index cd76292f..dbe38803 100644 --- a/programs/associated_token_account/src/transfer.rs +++ b/lez/programs/associated_token_account/src/transfer.rs @@ -16,8 +16,12 @@ pub fn transfer_from_associated_token_account( let definition_id = TokenHolding::try_from(&sender_ata.account.data) .expect("Sender ATA must hold a valid token") .definition_id(); - let seed = - ata_core::verify_ata_and_get_seed(&sender_ata, &owner, definition_id, ata_program_id); + let seed = associated_token_account_core::verify_ata_and_get_seed( + &sender_ata, + &owner, + definition_id, + ata_program_id, + ); let post_states = vec![ AccountPostState::new(owner.account.clone()), diff --git a/lez/programs/authenticated_transfer/Cargo.toml b/lez/programs/authenticated_transfer/Cargo.toml new file mode 100644 index 00000000..d586acdc --- /dev/null +++ b/lez/programs/authenticated_transfer/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "authenticated_transfer_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[dependencies] +authenticated_transfer_core.workspace = true +lee_core.workspace = true diff --git a/programs/authenticated_transfer/core/Cargo.toml b/lez/programs/authenticated_transfer/core/Cargo.toml similarity index 100% rename from programs/authenticated_transfer/core/Cargo.toml rename to lez/programs/authenticated_transfer/core/Cargo.toml diff --git a/programs/authenticated_transfer/core/src/lib.rs b/lez/programs/authenticated_transfer/core/src/lib.rs similarity index 100% rename from programs/authenticated_transfer/core/src/lib.rs rename to lez/programs/authenticated_transfer/core/src/lib.rs diff --git a/program_methods/guest/src/bin/authenticated_transfer.rs b/lez/programs/authenticated_transfer/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/authenticated_transfer.rs rename to lez/programs/authenticated_transfer/src/main.rs diff --git a/lez/programs/bridge/Cargo.toml b/lez/programs/bridge/Cargo.toml new file mode 100644 index 00000000..d7762f1f --- /dev/null +++ b/lez/programs/bridge/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bridge_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[dependencies] +bridge_core.workspace = true +vault_core.workspace = true +authenticated_transfer_core.workspace = true +lee_core.workspace = true diff --git a/programs/bridge/core/Cargo.toml b/lez/programs/bridge/core/Cargo.toml similarity index 100% rename from programs/bridge/core/Cargo.toml rename to lez/programs/bridge/core/Cargo.toml diff --git a/programs/bridge/core/src/lib.rs b/lez/programs/bridge/core/src/lib.rs similarity index 100% rename from programs/bridge/core/src/lib.rs rename to lez/programs/bridge/core/src/lib.rs diff --git a/program_methods/guest/src/bin/bridge.rs b/lez/programs/bridge/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/bridge.rs rename to lez/programs/bridge/src/main.rs diff --git a/lez/programs/bridge_lock/Cargo.toml b/lez/programs/bridge_lock/Cargo.toml new file mode 100644 index 00000000..8a4d6c20 --- /dev/null +++ b/lez/programs/bridge_lock/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "bridge_lock_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +bridge_lock_core.workspace = true +cross_zone_outbox_core.workspace = true +wrapped_token_core.workspace = true +risc0-zkvm.workspace = true diff --git a/lez/programs/bridge_lock/core/Cargo.toml b/lez/programs/bridge_lock/core/Cargo.toml new file mode 100644 index 00000000..4dadc075 --- /dev/null +++ b/lez/programs/bridge_lock/core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bridge_lock_core" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +serde = { workspace = true, features = ["alloc"] } + diff --git a/lez/programs/bridge_lock/core/src/lib.rs b/lez/programs/bridge_lock/core/src/lib.rs new file mode 100644 index 00000000..e5a36113 --- /dev/null +++ b/lez/programs/bridge_lock/core/src/lib.rs @@ -0,0 +1,71 @@ +//! Core types for the bridge-lock program, the source side of the cross-zone +//! token bridge. A holder locks part of their balance into an escrow and emits a +//! cross-zone message minting the wrapped token on the target zone. + +use lee_core::{ + account::AccountId, + program::{PdaSeed, ProgramId}, +}; +use serde::{Deserialize, Serialize}; + +const ESCROW_SEED_DOMAIN: [u8; 32] = *b"/LEZ/v0.3/BridgeLockEscrow/0000/"; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Instruction { + /// Lock `amount` of the holder's balance and emit a cross-zone message + /// minting the wrapped token on `target_zone`. The emission fields mirror + /// `cross_zone_outbox::Instruction::Emit` so the watcher reads them directly. + /// + /// Required accounts (3): holder holding (authorized), escrow PDA, outbox PDA. + Lock { + amount: u128, + target_zone: [u8; 32], + target_program_id: ProgramId, + target_accounts: Vec<[u8; 32]>, + payload: Vec, + outbox_program_id: ProgramId, + ordinal: u32, + }, +} + +/// PDA accumulating all locked balance on this zone. +#[must_use] +pub fn escrow_account_id(bridge_lock_id: ProgramId) -> AccountId { + AccountId::for_public_pda(&bridge_lock_id, &escrow_seed()) +} + +#[must_use] +pub const fn escrow_seed() -> PdaSeed { + PdaSeed::new(ESCROW_SEED_DOMAIN) +} + +/// Reads a bridgeable balance from account data; empty data is a zero balance. +#[must_use] +pub fn read_balance(data: &[u8]) -> u128 { + if data.len() < 16 { + return 0; + } + u128::from_le_bytes(data[..16].try_into().unwrap_or_else(|_| unreachable!())) +} + +#[must_use] +pub const fn balance_bytes(amount: u128) -> [u8; 16] { + amount.to_le_bytes() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn balance_round_trips() { + assert_eq!(read_balance(&balance_bytes(7)), 7); + assert_eq!(read_balance(&[]), 0); + } + + #[test] + fn escrow_is_stable() { + let id: ProgramId = [4; 8]; + assert_eq!(escrow_account_id(id), escrow_account_id(id)); + } +} diff --git a/lez/programs/bridge_lock/src/main.rs b/lez/programs/bridge_lock/src/main.rs new file mode 100644 index 00000000..aa8b5f93 --- /dev/null +++ b/lez/programs/bridge_lock/src/main.rs @@ -0,0 +1,121 @@ +use bridge_lock_core::{Instruction, balance_bytes, escrow_account_id, escrow_seed, read_balance}; +use cross_zone_outbox_core::Instruction as OutboxInstruction; +use lee_core::{ + account::AccountWithMetadata, + program::{AccountPostState, ChainedCall, Claim, ProgramInput, ProgramOutput, read_lee_inputs}, +}; +use wrapped_token_core::Instruction as WrappedInstruction; + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_lee_inputs::(); + + assert!( + caller_program_id.is_none(), + "bridge_lock is only invoked as a top-level user transaction" + ); + + let Instruction::Lock { + amount, + target_zone, + target_program_id, + target_accounts, + payload, + outbox_program_id, + ordinal, + } = instruction; + + // Value conservation: the forwarded payload must mint exactly what is locked. + let WrappedInstruction::Mint { + amount: mint_amount, + .. + } = decode_mint(&payload); + assert_eq!( + mint_amount, amount, + "locked amount must equal the wrapped mint amount" + ); + + // pre_states: [holder holding (authorized), escrow PDA, outbox PDA]. + let [holder, escrow, outbox] = <[AccountWithMetadata; 3]>::try_from(pre_states) + .expect("Lock requires holder, escrow, and outbox accounts"); + + assert!(holder.is_authorized, "holder must authorize the lock"); + assert_eq!( + holder.account.program_owner, self_program_id, + "holder account must be a bridge_lock holding" + ); + assert_eq!( + escrow.account_id, + escrow_account_id(self_program_id), + "second account must be the escrow PDA" + ); + + let holder_new = read_balance(&holder.account.data.clone().into_inner()) + .checked_sub(amount) + .expect("insufficient balance to lock"); + let escrow_new = read_balance(&escrow.account.data.clone().into_inner()) + .checked_add(amount) + .expect("escrow balance overflow"); + + let mut holder_account = holder.account.clone(); + holder_account.data = balance_bytes(holder_new) + .to_vec() + .try_into() + .expect("balance fits in account data"); + let holder_post = AccountPostState::new(holder_account); + + let mut escrow_account = escrow.account.clone(); + escrow_account.data = balance_bytes(escrow_new) + .to_vec() + .try_into() + .expect("balance fits in account data"); + let escrow_post = + AccountPostState::new_claimed_if_default(escrow_account, Claim::Pda(escrow_seed())); + + let call = ChainedCall::new( + outbox_program_id, + vec![outbox.clone()], + &OutboxInstruction::Emit { + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + }, + ); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![holder, escrow, outbox.clone()], + vec![ + holder_post, + escrow_post, + AccountPostState::new(outbox.account), + ], + ) + .with_chained_calls(vec![call]) + .write(); +} + +/// Decodes the cross-zone payload (risc0 words, little-endian bytes) into the +/// wrapped-token instruction it carries. +fn decode_mint(payload: &[u8]) -> WrappedInstruction { + assert!( + payload.len().is_multiple_of(4), + "payload must be u32-aligned instruction words" + ); + let words: Vec = payload + .chunks_exact(4) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap_or_else(|_| unreachable!()))) + .collect(); + risc0_zkvm::serde::from_slice(&words).expect("payload decodes to a wrapped-token instruction") +} diff --git a/lez/programs/build.rs b/lez/programs/build.rs new file mode 100644 index 00000000..b26b3bda --- /dev/null +++ b/lez/programs/build.rs @@ -0,0 +1,6 @@ +fn main() -> Result<(), Box> { + #[cfg(feature = "artifacts")] + build_utils::include_artifacts("lez/programs")?; + + Ok(()) +} diff --git a/lez/programs/clock/Cargo.toml b/lez/programs/clock/Cargo.toml new file mode 100644 index 00000000..0c51a69d --- /dev/null +++ b/lez/programs/clock/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "clock_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[dependencies] +clock_core.workspace = true +lee_core.workspace = true diff --git a/programs/clock/core/Cargo.toml b/lez/programs/clock/core/Cargo.toml similarity index 100% rename from programs/clock/core/Cargo.toml rename to lez/programs/clock/core/Cargo.toml diff --git a/programs/clock/core/src/lib.rs b/lez/programs/clock/core/src/lib.rs similarity index 100% rename from programs/clock/core/src/lib.rs rename to lez/programs/clock/core/src/lib.rs diff --git a/program_methods/guest/src/bin/clock.rs b/lez/programs/clock/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/clock.rs rename to lez/programs/clock/src/main.rs diff --git a/lez/programs/cross_zone_inbox/Cargo.toml b/lez/programs/cross_zone_inbox/Cargo.toml new file mode 100644 index 00000000..6f4c651a --- /dev/null +++ b/lez/programs/cross_zone_inbox/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cross_zone_inbox_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +cross_zone_inbox_core.workspace = true diff --git a/lez/programs/cross_zone_inbox/core/Cargo.toml b/lez/programs/cross_zone_inbox/core/Cargo.toml new file mode 100644 index 00000000..6bea109f --- /dev/null +++ b/lez/programs/cross_zone_inbox/core/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cross_zone_inbox_core" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +serde = { workspace = true, features = ["alloc"] } +risc0-zkvm.workspace = true +borsh.workspace = true diff --git a/lez/programs/cross_zone_inbox/core/src/lib.rs b/lez/programs/cross_zone_inbox/core/src/lib.rs new file mode 100644 index 00000000..761a8a1c --- /dev/null +++ b/lez/programs/cross_zone_inbox/core/src/lib.rs @@ -0,0 +1,203 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use lee_core::{ + account::AccountId, + program::{PdaSeed, ProgramId}, +}; +use serde::{Deserialize, Serialize}; + +/// Source blocks per seen-set shard, so no single seen account grows without bound. +pub const EPOCH_BLOCKS: u64 = 10_000; + +const MESSAGE_KEY_DOMAIN: [u8; 32] = *b"/LEZ/v0.3/CrossZoneMsgKey/00000/"; +const INBOX_CONFIG_SEED: [u8; 32] = *b"/LEZ/v0.3/CrossZoneInboxCfg/000/"; +const INBOX_SEEN_SEED_DOMAIN: [u8; 32] = *b"/LEZ/v0.3/CrossZoneInboxSeen/00/"; + +/// Raw 32-byte zone (channel) id; the host maps it to the zone-sdk `ChannelId`. +pub type ZoneId = [u8; 32]; + +/// Block-signing public key pinned per peer zone. +pub type ExpectedPubkey = [u8; 32]; + +/// Content-addressed replay key for a delivered message. +pub type MessageKey = [u8; 32]; + +/// A peer zone whose outbox a zone watches for inbound cross-zone messages. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CrossZonePeer { + /// The peer's Bedrock channel; its 32 bytes double as the peer's zone id. + pub channel_id: ZoneId, + /// Programs on the local zone a message from this peer is allowed to target. + pub allowed_targets: Vec, + /// The peer's block-signing public key, pinned to reject blocks inscribed by + /// anyone other than that zone's sequencer. `None` skips the check (the + /// channel signer is still authenticated by the zone-sdk). + #[serde(default)] + pub expected_block_signing_pubkey: Option<[u8; 32]>, +} + +/// Cross-zone configuration shared by a zone's sequencer (watcher) and indexer +/// (verifier): the peers it reads from Bedrock and, per peer, the local programs +/// they may deliver to. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CrossZoneConfig { + pub peers: Vec, +} + +/// A finalized outbound message observed on a peer zone, addressed to a program +/// on this zone. The watcher fills it from the peer's block; it is never +/// self-reported by a user. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct CrossZoneMessage { + pub src_zone: ZoneId, + pub src_block_id: u64, + pub src_tx_index: u32, + pub src_program_id: ProgramId, + pub target_program_id: ProgramId, + pub payload: Vec, + /// Reserved for a future source-state proof; MUST be `None` in v1. + pub l1_inclusion_witness: Option>, +} + +/// Peer and per-peer target allowlists, plus this inbox's own zone id. +#[derive( + Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, +)] +pub struct InboxConfig { + pub self_zone: ZoneId, + pub allowed_peers: BTreeMap, + pub allowed_targets: BTreeMap>, +} + +impl InboxConfig { + /// Borsh-encoded form stored in the inbox config account. + #[must_use] + pub fn to_bytes(&self) -> Vec { + borsh::to_vec(self).expect("InboxConfig serializes") + } + + /// Decodes an [`InboxConfig`] from account data. + pub fn from_bytes(bytes: &[u8]) -> borsh::io::Result { + borsh::from_slice(bytes) + } +} + +/// The replay keys seen for one `(src_zone, epoch)` shard. +#[derive(Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct SeenShard(pub BTreeSet); + +impl SeenShard { + /// Decodes a shard from account data; empty data is an empty shard. + pub fn from_bytes(bytes: &[u8]) -> borsh::io::Result { + if bytes.is_empty() { + return Ok(Self::default()); + } + borsh::from_slice(bytes) + } + + #[must_use] + pub fn to_bytes(&self) -> Vec { + borsh::to_vec(self).expect("SeenShard serializes") + } + + #[must_use] + pub fn contains(&self, key: &MessageKey) -> bool { + self.0.contains(key) + } + + /// Inserts a key; returns true if it was newly inserted. + pub fn insert(&mut self, key: MessageKey) -> bool { + self.0.insert(key) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Instruction { + /// Delivers a finalized peer message to its target program. + Dispatch(CrossZoneMessage), +} + +/// Content-addressed replay key for a delivered message. +/// +/// Hashes `(src_zone, src_block_id, src_tx_index)` under a domain separator. +/// Watcher-independent and immune to proof malleability, since it keys on block +/// id plus index rather than a tx hash. +#[must_use] +pub fn message_key(src_zone: &ZoneId, src_block_id: u64, src_tx_index: u32) -> MessageKey { + use risc0_zkvm::sha::{Impl, Sha256 as _}; + + let mut bytes = [0_u8; 76]; + bytes[..32].copy_from_slice(&MESSAGE_KEY_DOMAIN); + bytes[32..64].copy_from_slice(src_zone); + bytes[64..72].copy_from_slice(&src_block_id.to_le_bytes()); + bytes[72..].copy_from_slice(&src_tx_index.to_le_bytes()); + + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .unwrap_or_else(|_| unreachable!()) +} + +/// The config account holding the allowlists. +#[must_use] +pub fn inbox_config_account_id(inbox_id: ProgramId) -> AccountId { + AccountId::for_public_pda(&inbox_id, &PdaSeed::new(INBOX_CONFIG_SEED)) +} + +/// The seen-set shard for the `(src_zone, epoch)` the message falls in. +#[must_use] +pub fn inbox_seen_shard_account_id( + inbox_id: ProgramId, + src_zone: &ZoneId, + src_block_id: u64, +) -> AccountId { + AccountId::for_public_pda(&inbox_id, &inbox_seen_shard_seed(src_zone, src_block_id)) +} + +/// Seed of the seen-shard PDA, exposed so the guest can claim the account. +#[must_use] +pub fn inbox_seen_shard_seed(src_zone: &ZoneId, src_block_id: u64) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256 as _}; + + let src_epoch = src_block_id.wrapping_div(EPOCH_BLOCKS); + let mut bytes = [0_u8; 72]; + bytes[..32].copy_from_slice(&INBOX_SEEN_SEED_DOMAIN); + bytes[32..64].copy_from_slice(src_zone); + bytes[64..].copy_from_slice(&src_epoch.to_le_bytes()); + + let seed: [u8; 32] = Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .unwrap_or_else(|_| unreachable!()); + PdaSeed::new(seed) +} +#[cfg(test)] +mod tests { + use super::*; + + fn zone(b: u8) -> ZoneId { + [b; 32] + } + + #[test] + fn message_key_is_stable_and_content_addressed() { + assert_eq!(message_key(&zone(1), 7, 3), message_key(&zone(1), 7, 3)); + assert_ne!(message_key(&zone(1), 7, 3), message_key(&zone(2), 7, 3)); + assert_ne!(message_key(&zone(1), 7, 3), message_key(&zone(1), 8, 3)); + assert_ne!(message_key(&zone(1), 7, 3), message_key(&zone(1), 7, 4)); + } + + #[test] + fn seen_shards_split_on_epoch_boundary() { + let id: ProgramId = [9; 8]; + assert_eq!( + inbox_seen_shard_account_id(id, &zone(1), 0), + inbox_seen_shard_account_id(id, &zone(1), EPOCH_BLOCKS - 1), + ); + assert_ne!( + inbox_seen_shard_account_id(id, &zone(1), EPOCH_BLOCKS - 1), + inbox_seen_shard_account_id(id, &zone(1), EPOCH_BLOCKS), + ); + } +} diff --git a/lez/programs/cross_zone_inbox/src/main.rs b/lez/programs/cross_zone_inbox/src/main.rs new file mode 100644 index 00000000..8bb06ed8 --- /dev/null +++ b/lez/programs/cross_zone_inbox/src/main.rs @@ -0,0 +1,125 @@ +use cross_zone_inbox_core::{ + InboxConfig, Instruction, SeenShard, inbox_config_account_id, inbox_seen_shard_account_id, + inbox_seen_shard_seed, message_key, +}; +use lee_core::{ + account::AccountWithMetadata, + program::{AccountPostState, ChainedCall, Claim, ProgramInput, ProgramOutput, read_lee_inputs}, +}; + +fn unchanged(pre: &AccountWithMetadata) -> AccountPostState { + AccountPostState::new(pre.account.clone()) +} + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_lee_inputs::(); + + assert!( + caller_program_id.is_none(), + "Inbox is only invoked as a top-level sequencer-origin transaction" + ); + + let Instruction::Dispatch(msg) = instruction; + + assert!( + msg.l1_inclusion_witness.is_none(), + "l1_inclusion_witness must be None in v1" + ); + + // pre_states layout: [config, seen_shard, then the target accounts]. + let mut accounts = pre_states.into_iter(); + let config = accounts.next().expect("config account required"); + let seen = accounts.next().expect("seen shard account required"); + let target_accounts: Vec = accounts.collect(); + + assert_eq!( + config.account_id, + inbox_config_account_id(self_program_id), + "First account must be the inbox config PDA" + ); + assert_eq!( + seen.account_id, + inbox_seen_shard_account_id(self_program_id, &msg.src_zone, msg.src_block_id), + "Second account must be the seen-shard PDA" + ); + + let cfg = InboxConfig::from_bytes(&config.account.data.clone().into_inner()) + .expect("inbox config decodes"); + + assert!( + msg.src_zone != cfg.self_zone, + "Source zone must not be this zone" + ); + let allowed_targets = cfg + .allowed_targets + .get(&msg.src_zone) + .expect("Source zone is not an allowed peer"); + assert!( + allowed_targets.contains(&msg.target_program_id), + "Target program is not allowed for this peer" + ); + + let key = message_key(&msg.src_zone, msg.src_block_id, msg.src_tx_index); + let mut shard = + SeenShard::from_bytes(&seen.account.data.clone().into_inner()).expect("seen shard decodes"); + let already_seen = shard.contains(&key); + + // On replay this is a no-op: the seen shard is untouched and no call is made. + let (seen_post, chained_calls) = if already_seen { + (unchanged(&seen), vec![]) + } else { + shard.insert(key); + let mut seen_account = seen.account.clone(); + seen_account.data = shard + .to_bytes() + .try_into() + .expect("seen shard fits in account data"); + let seen_post = AccountPostState::new_claimed_if_default( + seen_account, + Claim::Pda(inbox_seen_shard_seed(&msg.src_zone, msg.src_block_id)), + ); + + // The payload carries the target instruction as risc0 words, little-endian. + assert!( + msg.payload.len().is_multiple_of(4), + "payload must be u32-aligned instruction words" + ); + let instruction_data = msg + .payload + .chunks_exact(4) + .map(|c| u32::from_le_bytes(c.try_into().unwrap_or_else(|_| unreachable!()))) + .collect(); + + let call = ChainedCall { + program_id: msg.target_program_id, + pre_states: target_accounts.clone(), + instruction_data, + pda_seeds: vec![], + }; + (seen_post, vec![call]) + }; + + let mut post_states = vec![unchanged(&config), seen_post]; + post_states.extend(target_accounts.iter().map(unchanged)); + + let mut output_pre_states = vec![config, seen]; + output_pre_states.extend(target_accounts); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + output_pre_states, + post_states, + ) + .with_chained_calls(chained_calls) + .write(); +} diff --git a/lez/programs/cross_zone_outbox/Cargo.toml b/lez/programs/cross_zone_outbox/Cargo.toml new file mode 100644 index 00000000..2f236cbf --- /dev/null +++ b/lez/programs/cross_zone_outbox/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cross_zone_outbox_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +cross_zone_outbox_core.workspace = true diff --git a/lez/programs/cross_zone_outbox/core/Cargo.toml b/lez/programs/cross_zone_outbox/core/Cargo.toml new file mode 100644 index 00000000..c7876286 --- /dev/null +++ b/lez/programs/cross_zone_outbox/core/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cross_zone_outbox_core" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +serde = { workspace = true, features = ["alloc"] } +risc0-zkvm.workspace = true +borsh.workspace = true diff --git a/lez/programs/cross_zone_outbox/core/src/lib.rs b/lez/programs/cross_zone_outbox/core/src/lib.rs new file mode 100644 index 00000000..736b5fa2 --- /dev/null +++ b/lez/programs/cross_zone_outbox/core/src/lib.rs @@ -0,0 +1,93 @@ +use borsh::{BorshDeserialize, BorshSerialize}; +use lee_core::{ + account::AccountId, + program::{PdaSeed, ProgramId}, +}; +use serde::{Deserialize, Serialize}; + +const OUTBOX_SEED_DOMAIN: [u8; 32] = *b"/LEZ/v0.3/CrossZoneOutbox/00000/"; + +/// Raw 32-byte zone (channel) id; the host maps it to the zone-sdk `ChannelId`. +pub type ZoneId = [u8; 32]; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Instruction { + /// Records an outbound cross-zone message as a write to a self-owned PDA. + /// + /// Required accounts (1): + /// - Outbox PDA account + Emit { + target_zone: ZoneId, + target_program_id: ProgramId, + /// Accounts the destination inbox must hand to the target program's + /// chained call. The emitter specifies them; the watcher forwards them + /// verbatim so the inbox stays target-agnostic. + target_accounts: Vec<[u8; 32]>, + payload: Vec, + ordinal: u32, + }, +} + +/// The message as stored in an outbox PDA. The destination zone's watcher reads +/// this from the inscribed block; the source coordinates are filled by the +/// watcher, not stored here. +#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct OutboxRecord { + pub target_zone: ZoneId, + pub target_program_id: ProgramId, + pub target_accounts: Vec<[u8; 32]>, + pub payload: Vec, +} + +impl OutboxRecord { + /// Borsh-encoded form stored in the outbox PDA's account data. + #[must_use] + pub fn to_bytes(&self) -> Vec { + borsh::to_vec(self).expect("OutboxRecord serializes") + } + + /// Decodes an [`OutboxRecord`] from account data. + pub fn from_bytes(bytes: &[u8]) -> borsh::io::Result { + borsh::from_slice(bytes) + } +} + +/// PDA holding one emitted message, keyed by destination zone and a per-zone +/// ordinal. +#[must_use] +pub fn outbox_pda(outbox_id: ProgramId, target_zone: &ZoneId, ordinal: u32) -> AccountId { + AccountId::for_public_pda(&outbox_id, &outbox_pda_seed(target_zone, ordinal)) +} + +/// Seed of an outbox message PDA, exposed so the guest can claim the account. +#[must_use] +pub fn outbox_pda_seed(target_zone: &ZoneId, ordinal: u32) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256 as _}; + + let mut bytes = [0_u8; 68]; + bytes[..32].copy_from_slice(&OUTBOX_SEED_DOMAIN); + bytes[32..64].copy_from_slice(target_zone); + bytes[64..].copy_from_slice(&ordinal.to_le_bytes()); + + let seed: [u8; 32] = Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .unwrap_or_else(|_| unreachable!()); + PdaSeed::new(seed) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn outbox_pda_is_unique_per_zone_and_ordinal() { + let id: ProgramId = [3; 8]; + let zone_a = [1; 32]; + let zone_b = [2; 32]; + + assert_eq!(outbox_pda(id, &zone_a, 0), outbox_pda(id, &zone_a, 0)); + assert_ne!(outbox_pda(id, &zone_a, 0), outbox_pda(id, &zone_a, 1)); + assert_ne!(outbox_pda(id, &zone_a, 0), outbox_pda(id, &zone_b, 0)); + } +} diff --git a/lez/programs/cross_zone_outbox/src/main.rs b/lez/programs/cross_zone_outbox/src/main.rs new file mode 100644 index 00000000..432e8d9c --- /dev/null +++ b/lez/programs/cross_zone_outbox/src/main.rs @@ -0,0 +1,72 @@ +use cross_zone_outbox_core::{Instruction, OutboxRecord, outbox_pda, outbox_pda_seed}; +use lee_core::{ + account::AccountWithMetadata, + program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs}, +}; + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_lee_inputs::(); + + assert!( + caller_program_id.is_some(), + "Outbox is only callable through a chain call from a user program" + ); + + let (target_zone, target_program_id, target_accounts, payload, ordinal) = match instruction { + Instruction::Emit { + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + } => ( + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + ), + }; + + let [outbox] = + <[AccountWithMetadata; 1]>::try_from(pre_states).expect("Emit requires exactly 1 account"); + + assert_eq!( + outbox.account_id, + outbox_pda(self_program_id, &target_zone, ordinal), + "Account must be the outbox PDA for (target_zone, ordinal)" + ); + + let mut post_account = outbox.account.clone(); + post_account.data = OutboxRecord { + target_zone, + target_program_id, + target_accounts, + payload, + } + .to_bytes() + .try_into() + .expect("OutboxRecord fits in account data"); + + let post = AccountPostState::new_claimed_if_default( + post_account, + Claim::Pda(outbox_pda_seed(&target_zone, ordinal)), + ); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![outbox], + vec![post], + ) + .write(); +} diff --git a/lez/programs/faucet/Cargo.toml b/lez/programs/faucet/Cargo.toml new file mode 100644 index 00000000..c1f4f426 --- /dev/null +++ b/lez/programs/faucet/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "faucet_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[dependencies] +lee_core.workspace = true +faucet_core.workspace = true +vault_core.workspace = true +authenticated_transfer_core.workspace = true diff --git a/programs/faucet/core/Cargo.toml b/lez/programs/faucet/core/Cargo.toml similarity index 100% rename from programs/faucet/core/Cargo.toml rename to lez/programs/faucet/core/Cargo.toml diff --git a/programs/faucet/core/src/lib.rs b/lez/programs/faucet/core/src/lib.rs similarity index 100% rename from programs/faucet/core/src/lib.rs rename to lez/programs/faucet/core/src/lib.rs diff --git a/program_methods/guest/src/bin/faucet.rs b/lez/programs/faucet/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/faucet.rs rename to lez/programs/faucet/src/main.rs diff --git a/lez/programs/pinata/Cargo.toml b/lez/programs/pinata/Cargo.toml new file mode 100644 index 00000000..7a6ad121 --- /dev/null +++ b/lez/programs/pinata/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pinata_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true + +risc0-zkvm.workspace = true diff --git a/program_methods/guest/src/bin/pinata.rs b/lez/programs/pinata/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/pinata.rs rename to lez/programs/pinata/src/main.rs diff --git a/programs/associated_token_account/Cargo.toml b/lez/programs/pinata_token/Cargo.toml similarity index 63% rename from programs/associated_token_account/Cargo.toml rename to lez/programs/pinata_token/Cargo.toml index b6be7155..476409ec 100644 --- a/programs/associated_token_account/Cargo.toml +++ b/lez/programs/pinata_token/Cargo.toml @@ -1,10 +1,14 @@ [package] -name = "ata_program" +name = "pinata_token_program" version = "0.1.0" edition = "2024" license = { workspace = true } +[lints] +workspace = true + [dependencies] lee_core.workspace = true token_core.workspace = true -ata_core.workspace = true + +risc0-zkvm.workspace = true diff --git a/program_methods/guest/src/bin/pinata_token.rs b/lez/programs/pinata_token/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/pinata_token.rs rename to lez/programs/pinata_token/src/main.rs diff --git a/lez/programs/ping_core/Cargo.toml b/lez/programs/ping_core/Cargo.toml new file mode 100644 index 00000000..29870630 --- /dev/null +++ b/lez/programs/ping_core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ping_core" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +serde = { workspace = true, features = ["alloc"] } diff --git a/lez/programs/ping_core/src/lib.rs b/lez/programs/ping_core/src/lib.rs new file mode 100644 index 00000000..80b27247 --- /dev/null +++ b/lez/programs/ping_core/src/lib.rs @@ -0,0 +1,38 @@ +use lee_core::{ + account::AccountId, + program::{PdaSeed, ProgramId}, +}; +use serde::{Deserialize, Serialize}; + +const PING_RECORD_SEED: [u8; 32] = *b"/LEZ/v0.3/PingRecord/0000000000/"; + +/// Instruction delivered to `ping_receiver` by the inbox: record the payload. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum ReceiverInstruction { + Record { payload: Vec }, +} + +/// Instruction to `ping_sender`: forwarded verbatim into `cross_zone_outbox::Instruction::Emit`. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum SenderInstruction { + Send { + outbox_program_id: ProgramId, + target_zone: [u8; 32], + target_program_id: ProgramId, + target_accounts: Vec<[u8; 32]>, + payload: Vec, + ordinal: u32, + }, +} + +/// The account a `ping_receiver` records the latest delivered payload into. +#[must_use] +pub fn ping_record_pda(receiver_id: ProgramId) -> AccountId { + AccountId::for_public_pda(&receiver_id, &ping_record_seed()) +} + +/// Seed of the record PDA, exposed so the guest can claim the account. +#[must_use] +pub const fn ping_record_seed() -> PdaSeed { + PdaSeed::new(PING_RECORD_SEED) +} diff --git a/lez/programs/ping_receiver/Cargo.toml b/lez/programs/ping_receiver/Cargo.toml new file mode 100644 index 00000000..a1d88f39 --- /dev/null +++ b/lez/programs/ping_receiver/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ping_receiver_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +ping_core.workspace = true diff --git a/lez/programs/ping_receiver/src/main.rs b/lez/programs/ping_receiver/src/main.rs new file mode 100644 index 00000000..4fd9679f --- /dev/null +++ b/lez/programs/ping_receiver/src/main.rs @@ -0,0 +1,48 @@ +use lee_core::{ + account::AccountWithMetadata, + program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs}, +}; +use ping_core::{ReceiverInstruction, ping_record_pda, ping_record_seed}; + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_lee_inputs::(); + + assert!( + caller_program_id.is_some(), + "ping_receiver is only callable through a chained call" + ); + + let payload = match instruction { + ReceiverInstruction::Record { payload } => payload, + }; + + let [record] = <[AccountWithMetadata; 1]>::try_from(pre_states) + .expect("Record requires exactly 1 account"); + assert_eq!( + record.account_id, + ping_record_pda(self_program_id), + "Account must be the ping record PDA" + ); + + let mut post_account = record.account.clone(); + post_account.data = payload.try_into().expect("payload fits in account data"); + let post = + AccountPostState::new_claimed_if_default(post_account, Claim::Pda(ping_record_seed())); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![record], + vec![post], + ) + .write(); +} diff --git a/lez/programs/ping_sender/Cargo.toml b/lez/programs/ping_sender/Cargo.toml new file mode 100644 index 00000000..8567ae55 --- /dev/null +++ b/lez/programs/ping_sender/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ping_sender_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +ping_core.workspace = true +cross_zone_outbox_core.workspace = true diff --git a/lez/programs/ping_sender/src/main.rs b/lez/programs/ping_sender/src/main.rs new file mode 100644 index 00000000..d0ad04f5 --- /dev/null +++ b/lez/programs/ping_sender/src/main.rs @@ -0,0 +1,59 @@ +use cross_zone_outbox_core::Instruction as OutboxInstruction; +use lee_core::{ + account::AccountWithMetadata, + program::{AccountPostState, ChainedCall, ProgramInput, ProgramOutput, read_lee_inputs}, +}; +use ping_core::SenderInstruction; + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_lee_inputs::(); + + assert!( + caller_program_id.is_none(), + "ping_sender is only invoked as a top-level user transaction" + ); + + let SenderInstruction::Send { + outbox_program_id, + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + } = instruction; + + // The single account is the outbox PDA the chained call writes into; the + // outbox claims it, so ping_sender forwards it unchanged. + let [outbox] = + <[AccountWithMetadata; 1]>::try_from(pre_states).expect("Send requires exactly 1 account"); + + let call = ChainedCall::new( + outbox_program_id, + vec![outbox.clone()], + &OutboxInstruction::Emit { + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + }, + ); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![outbox.clone()], + vec![AccountPostState::new(outbox.account)], + ) + .with_chained_calls(vec![call]) + .write(); +} diff --git a/lez/programs/src/lib.rs b/lez/programs/src/lib.rs new file mode 100644 index 00000000..fb448038 --- /dev/null +++ b/lez/programs/src/lib.rs @@ -0,0 +1,182 @@ +//! This crate provides [`Program`]s and associated utilities used by LEZ. + +#[cfg(feature = "artifacts")] +pub use inner::*; + +#[cfg(feature = "artifacts")] +mod inner { + + use std::borrow::Cow; + + use guests::{ + AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID, + AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, BRIDGE_ELF, BRIDGE_ID, + BRIDGE_LOCK_ELF, BRIDGE_LOCK_ID, CLOCK_ELF, CLOCK_ID, CROSS_ZONE_INBOX_ELF, + CROSS_ZONE_INBOX_ID, CROSS_ZONE_OUTBOX_ELF, CROSS_ZONE_OUTBOX_ID, FAUCET_ELF, FAUCET_ID, + PINATA_ELF, PINATA_ID, PINATA_TOKEN_ELF, PINATA_TOKEN_ID, PING_RECEIVER_ELF, + PING_RECEIVER_ID, PING_SENDER_ELF, PING_SENDER_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, + VAULT_ID, WRAPPED_TOKEN_ELF, WRAPPED_TOKEN_ID, + }; + use lee::program::Program; + + mod guests { + include!(concat!(env!("OUT_DIR"), "/lez/programs/mod.rs")); + } + + #[must_use] + #[inline] + pub const fn authenticated_transfer() -> Program { + Program::new_unchecked( + AUTHENTICATED_TRANSFER_ID, + Cow::Borrowed(AUTHENTICATED_TRANSFER_ELF), + ) + } + + #[must_use] + #[inline] + pub const fn token() -> Program { + Program::new_unchecked(TOKEN_ID, Cow::Borrowed(TOKEN_ELF)) + } + + #[must_use] + #[inline] + pub const fn pinata() -> Program { + Program::new_unchecked(PINATA_ID, Cow::Borrowed(PINATA_ELF)) + } + + // TODO: Not used anywhere? + #[must_use] + #[inline] + pub const fn pinata_token() -> Program { + Program::new_unchecked(PINATA_TOKEN_ID, Cow::Borrowed(PINATA_TOKEN_ELF)) + } + + #[must_use] + #[inline] + pub const fn amm() -> Program { + Program::new_unchecked(AMM_ID, Cow::Borrowed(AMM_ELF)) + } + + #[must_use] + #[inline] + pub const fn clock() -> Program { + Program::new_unchecked(CLOCK_ID, Cow::Borrowed(CLOCK_ELF)) + } + + #[must_use] + #[inline] + pub const fn ata() -> Program { + Program::new_unchecked( + ASSOCIATED_TOKEN_ACCOUNT_ID, + Cow::Borrowed(ASSOCIATED_TOKEN_ACCOUNT_ELF), + ) + } + + #[must_use] + #[inline] + pub const fn vault() -> Program { + Program::new_unchecked(VAULT_ID, Cow::Borrowed(VAULT_ELF)) + } + + #[must_use] + #[inline] + pub const fn faucet() -> Program { + Program::new_unchecked(FAUCET_ID, Cow::Borrowed(FAUCET_ELF)) + } + + #[must_use] + #[inline] + pub const fn bridge() -> Program { + Program::new_unchecked(BRIDGE_ID, Cow::Borrowed(BRIDGE_ELF)) + } + + #[must_use] + #[inline] + pub const fn cross_zone_outbox() -> Program { + Program::new_unchecked(CROSS_ZONE_OUTBOX_ID, Cow::Borrowed(CROSS_ZONE_OUTBOX_ELF)) + } + + #[must_use] + #[inline] + pub const fn cross_zone_inbox() -> Program { + Program::new_unchecked(CROSS_ZONE_INBOX_ID, Cow::Borrowed(CROSS_ZONE_INBOX_ELF)) + } + + #[must_use] + #[inline] + pub const fn ping_sender() -> Program { + Program::new_unchecked(PING_SENDER_ID, Cow::Borrowed(PING_SENDER_ELF)) + } + + #[must_use] + #[inline] + pub const fn ping_receiver() -> Program { + Program::new_unchecked(PING_RECEIVER_ID, Cow::Borrowed(PING_RECEIVER_ELF)) + } + + #[must_use] + #[inline] + pub const fn bridge_lock() -> Program { + Program::new_unchecked(BRIDGE_LOCK_ID, Cow::Borrowed(BRIDGE_LOCK_ELF)) + } + + #[must_use] + #[inline] + pub const fn wrapped_token() -> Program { + Program::new_unchecked(WRAPPED_TOKEN_ID, Cow::Borrowed(WRAPPED_TOKEN_ELF)) + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn builtin_programs() { + let auth_transfer_program = authenticated_transfer(); + let token_program = token(); + let vault_program = vault(); + let faucet_program = faucet(); + let bridge_program = bridge(); + let pinata_program = pinata(); + + assert_eq!(auth_transfer_program.id(), AUTHENTICATED_TRANSFER_ID); + assert_eq!(auth_transfer_program.elf(), AUTHENTICATED_TRANSFER_ELF); + assert_eq!(token_program.id(), TOKEN_ID); + assert_eq!(token_program.elf(), TOKEN_ELF); + assert_eq!(vault_program.id(), VAULT_ID); + assert_eq!(vault_program.elf(), VAULT_ELF); + assert_eq!(faucet_program.id(), FAUCET_ID); + assert_eq!(faucet_program.elf(), FAUCET_ELF); + assert_eq!(bridge_program.id(), BRIDGE_ID); + assert_eq!(bridge_program.elf(), BRIDGE_ELF); + assert_eq!(pinata_program.id(), PINATA_ID); + assert_eq!(pinata_program.elf(), PINATA_ELF); + } + + #[test] + fn builtin_program_ids_match_elfs() { + let cases: &[(&[u8], [u32; 8])] = &[ + (AMM_ELF, AMM_ID), + (AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID), + (ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID), + (CLOCK_ELF, CLOCK_ID), + (FAUCET_ELF, FAUCET_ID), + (BRIDGE_ELF, BRIDGE_ID), + (PINATA_ELF, PINATA_ID), + (PINATA_TOKEN_ELF, PINATA_TOKEN_ID), + (TOKEN_ELF, TOKEN_ID), + (VAULT_ELF, VAULT_ID), + (CROSS_ZONE_OUTBOX_ELF, CROSS_ZONE_OUTBOX_ID), + (CROSS_ZONE_INBOX_ELF, CROSS_ZONE_INBOX_ID), + (PING_SENDER_ELF, PING_SENDER_ID), + (PING_RECEIVER_ELF, PING_RECEIVER_ID), + (BRIDGE_LOCK_ELF, BRIDGE_LOCK_ID), + (WRAPPED_TOKEN_ELF, WRAPPED_TOKEN_ID), + ]; + for (elf, expected_id) in cases { + let program = Program::new((*elf).into()).unwrap(); + assert_eq!(program.id(), *expected_id); + } + } + } +} diff --git a/programs/token/Cargo.toml b/lez/programs/token/Cargo.toml similarity index 100% rename from programs/token/Cargo.toml rename to lez/programs/token/Cargo.toml diff --git a/programs/token/core/Cargo.toml b/lez/programs/token/core/Cargo.toml similarity index 100% rename from programs/token/core/Cargo.toml rename to lez/programs/token/core/Cargo.toml diff --git a/programs/token/core/src/lib.rs b/lez/programs/token/core/src/lib.rs similarity index 100% rename from programs/token/core/src/lib.rs rename to lez/programs/token/core/src/lib.rs diff --git a/programs/token/src/burn.rs b/lez/programs/token/src/burn.rs similarity index 100% rename from programs/token/src/burn.rs rename to lez/programs/token/src/burn.rs diff --git a/programs/token/src/initialize.rs b/lez/programs/token/src/initialize.rs similarity index 100% rename from programs/token/src/initialize.rs rename to lez/programs/token/src/initialize.rs diff --git a/programs/token/src/lib.rs b/lez/programs/token/src/lib.rs similarity index 100% rename from programs/token/src/lib.rs rename to lez/programs/token/src/lib.rs diff --git a/program_methods/guest/src/bin/token.rs b/lez/programs/token/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/token.rs rename to lez/programs/token/src/main.rs diff --git a/programs/token/src/mint.rs b/lez/programs/token/src/mint.rs similarity index 100% rename from programs/token/src/mint.rs rename to lez/programs/token/src/mint.rs diff --git a/programs/token/src/new_definition.rs b/lez/programs/token/src/new_definition.rs similarity index 100% rename from programs/token/src/new_definition.rs rename to lez/programs/token/src/new_definition.rs diff --git a/programs/token/src/print_nft.rs b/lez/programs/token/src/print_nft.rs similarity index 100% rename from programs/token/src/print_nft.rs rename to lez/programs/token/src/print_nft.rs diff --git a/programs/token/src/tests.rs b/lez/programs/token/src/tests.rs similarity index 100% rename from programs/token/src/tests.rs rename to lez/programs/token/src/tests.rs diff --git a/programs/token/src/transfer.rs b/lez/programs/token/src/transfer.rs similarity index 100% rename from programs/token/src/transfer.rs rename to lez/programs/token/src/transfer.rs diff --git a/lez/programs/vault/Cargo.toml b/lez/programs/vault/Cargo.toml new file mode 100644 index 00000000..4faed055 --- /dev/null +++ b/lez/programs/vault/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "vault_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[dependencies] +lee_core.workspace = true +vault_core.workspace = true +authenticated_transfer_core.workspace = true diff --git a/programs/vault/core/Cargo.toml b/lez/programs/vault/core/Cargo.toml similarity index 100% rename from programs/vault/core/Cargo.toml rename to lez/programs/vault/core/Cargo.toml diff --git a/programs/vault/core/src/lib.rs b/lez/programs/vault/core/src/lib.rs similarity index 100% rename from programs/vault/core/src/lib.rs rename to lez/programs/vault/core/src/lib.rs diff --git a/program_methods/guest/src/bin/vault.rs b/lez/programs/vault/src/main.rs similarity index 100% rename from program_methods/guest/src/bin/vault.rs rename to lez/programs/vault/src/main.rs diff --git a/lez/programs/wrapped_token/Cargo.toml b/lez/programs/wrapped_token/Cargo.toml new file mode 100644 index 00000000..a80f60ff --- /dev/null +++ b/lez/programs/wrapped_token/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wrapped_token_program" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +wrapped_token_core.workspace = true diff --git a/lez/programs/wrapped_token/core/Cargo.toml b/lez/programs/wrapped_token/core/Cargo.toml new file mode 100644 index 00000000..ef0aabbc --- /dev/null +++ b/lez/programs/wrapped_token/core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "wrapped_token_core" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +serde = { workspace = true, features = ["alloc"] } +risc0-zkvm.workspace = true diff --git a/lez/programs/wrapped_token/core/src/lib.rs b/lez/programs/wrapped_token/core/src/lib.rs new file mode 100644 index 00000000..8e13567a --- /dev/null +++ b/lez/programs/wrapped_token/core/src/lib.rs @@ -0,0 +1,121 @@ +//! Core types for the wrapped-token program, the destination side of the +//! cross-zone bridge. Only the cross-zone inbox may mint; the guest enforces +//! this by reading the authorized minter from a genesis-seeded config account. + +use lee_core::{ + account::AccountId, + program::{PdaSeed, ProgramId}, +}; +use serde::{Deserialize, Serialize}; + +const CONFIG_SEED_DOMAIN: [u8; 32] = *b"/LEZ/v0.3/WrappedTokenConfig/00/"; +const HOLDING_SEED_DOMAIN: [u8; 32] = *b"/LEZ/v0.3/WrappedTokenHold/00000"; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Instruction { + /// Credit `amount` wrapped tokens to `recipient`'s holding. Delivered only by + /// the cross-zone inbox. + /// + /// Required accounts (2): the wrapped-token config PDA, then the recipient's + /// holding PDA. + Mint { recipient: [u8; 32], amount: u128 }, +} + +/// PDA holding the authorized minter program id (the cross-zone inbox), seeded at +/// genesis so the guest can pin its caller without importing the inbox image id. +#[must_use] +pub fn config_account_id(wrapped_token_id: ProgramId) -> AccountId { + AccountId::for_public_pda(&wrapped_token_id, &config_seed()) +} + +#[must_use] +pub const fn config_seed() -> PdaSeed { + PdaSeed::new(CONFIG_SEED_DOMAIN) +} + +/// PDA holding one recipient's wrapped-token balance. +#[must_use] +pub fn holding_account_id(wrapped_token_id: ProgramId, recipient: &[u8; 32]) -> AccountId { + AccountId::for_public_pda(&wrapped_token_id, &holding_seed(recipient)) +} + +#[must_use] +pub fn holding_seed(recipient: &[u8; 32]) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256 as _}; + + let mut bytes = [0_u8; 64]; + bytes[..32].copy_from_slice(&HOLDING_SEED_DOMAIN); + bytes[32..].copy_from_slice(recipient); + let seed: [u8; 32] = Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .unwrap_or_else(|_| unreachable!()); + PdaSeed::new(seed) +} + +/// Encodes the authorized minter program id for the config account's data. +#[must_use] +pub fn minter_bytes(minter: ProgramId) -> [u8; 32] { + let mut bytes = [0_u8; 32]; + for (word, chunk) in minter.iter().zip(bytes.chunks_exact_mut(4)) { + chunk.copy_from_slice(&word.to_le_bytes()); + } + bytes +} + +/// Decodes the authorized minter program id from the config account's data. +#[must_use] +pub fn read_minter(data: &[u8]) -> Option { + if data.len() < 32 { + return None; + } + let mut minter = [0_u32; 8]; + for (word, chunk) in minter.iter_mut().zip(data[..32].chunks_exact(4)) { + *word = u32::from_le_bytes(chunk.try_into().unwrap_or_else(|_| unreachable!())); + } + Some(minter) +} + +/// Reads a wrapped-token balance from account data; empty data is a zero balance. +#[must_use] +pub fn read_balance(data: &[u8]) -> u128 { + if data.len() < 16 { + return 0; + } + u128::from_le_bytes(data[..16].try_into().unwrap_or_else(|_| unreachable!())) +} + +#[must_use] +pub const fn balance_bytes(amount: u128) -> [u8; 16] { + amount.to_le_bytes() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn minter_round_trips() { + let minter: ProgramId = [1, 2, 3, 4, 5, 6, 7, 8]; + assert_eq!(read_minter(&minter_bytes(minter)), Some(minter)); + } + + #[test] + fn balance_round_trips() { + assert_eq!(read_balance(&balance_bytes(42)), 42); + assert_eq!(read_balance(&[]), 0); + } + + #[test] + fn holding_is_unique_per_recipient() { + let id: ProgramId = [9; 8]; + assert_ne!( + holding_account_id(id, &[1; 32]), + holding_account_id(id, &[2; 32]) + ); + assert_eq!( + holding_account_id(id, &[1; 32]), + holding_account_id(id, &[1; 32]) + ); + } +} diff --git a/lez/programs/wrapped_token/src/main.rs b/lez/programs/wrapped_token/src/main.rs new file mode 100644 index 00000000..d0e7595f --- /dev/null +++ b/lez/programs/wrapped_token/src/main.rs @@ -0,0 +1,70 @@ +use lee_core::{ + account::AccountWithMetadata, + program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs}, +}; +use wrapped_token_core::{ + Instruction, balance_bytes, config_account_id, holding_account_id, holding_seed, read_balance, + read_minter, +}; + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_lee_inputs::(); + + let Instruction::Mint { recipient, amount } = instruction; + + // pre_states: [config PDA, recipient holding PDA]. + let [config, holding] = <[AccountWithMetadata; 2]>::try_from(pre_states) + .expect("Mint requires the config and recipient holding accounts"); + + // The config PDA is genesis-seeded with the authorized minter (the cross-zone + // inbox). Pin the caller to it, since the guest cannot import the inbox id. + assert_eq!( + config.account_id, + config_account_id(self_program_id), + "first account must be the wrapped-token config PDA" + ); + let minter = read_minter(&config.account.data.clone().into_inner()) + .expect("config account holds an authorized minter id"); + assert_eq!( + caller_program_id, + Some(minter), + "Mint is only callable by the authorized minter (the cross-zone inbox)" + ); + + assert_eq!( + holding.account_id, + holding_account_id(self_program_id, &recipient), + "second account must be the recipient holding PDA" + ); + + let new_balance = read_balance(&holding.account.data.clone().into_inner()) + .checked_add(amount) + .expect("wrapped-token balance overflow"); + let mut holding_account = holding.account.clone(); + holding_account.data = balance_bytes(new_balance) + .to_vec() + .try_into() + .expect("balance fits in account data"); + let holding_post = AccountPostState::new_claimed_if_default( + holding_account, + Claim::Pda(holding_seed(&recipient)), + ); + let config_post = AccountPostState::new(config.account.clone()); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![config, holding], + vec![config_post, holding_post], + ) + .write(); +} diff --git a/lez/sequencer/core/Cargo.toml b/lez/sequencer/core/Cargo.toml index c69dc75d..6cd1969f 100644 --- a/lez/sequencer/core/Cargo.toml +++ b/lez/sequencer/core/Cargo.toml @@ -18,8 +18,10 @@ testnet_initial_state.workspace = true faucet_core.workspace = true bridge_core.workspace = true vault_core.workspace = true -cross_zone_inbox_core = { workspace = true, features = ["host"] } -bridge_lock_core = { workspace = true, features = ["host"] } +programs.workspace = true +system_accounts.workspace = true +cross_zone.workspace = true +cross_zone_inbox_core.workspace = true logos-blockchain-key-management-system-service.workspace = true logos-blockchain-core.workspace = true @@ -48,6 +50,7 @@ mock = [] [dev-dependencies] futures.workspace = true -test_program_methods.workspace = true +test_programs.workspace = true lee = { workspace = true, features = ["test-utils"] } key_protocol.workspace = true +token_core.workspace = true diff --git a/lez/sequencer/core/src/block_store.rs b/lez/sequencer/core/src/block_store.rs index 4e5489ff..0db05d74 100644 --- a/lez/sequencer/core/src/block_store.rs +++ b/lez/sequencer/core/src/block_store.rs @@ -219,7 +219,7 @@ mod tests { let retrieved_tx = node_store.get_transaction_by_hash(tx.hash()); assert_eq!(None, retrieved_tx); // Add the block with the transaction - let dummy_state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let dummy_state = V03State::new(); node_store .update(&block, &[], vec![], &dummy_state) .unwrap(); @@ -286,7 +286,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(1, None, vec![tx]); let block_hash = block.header.hash; - let dummy_state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let dummy_state = V03State::new(); node_store .update(&block, &[], vec![], &dummy_state) .unwrap(); @@ -324,7 +324,7 @@ mod tests { let block = common::test_utils::produce_dummy_block(1, None, vec![tx]); let block_id = block.header.block_id; - let dummy_state = V03State::new_with_genesis_accounts(&[], vec![], 0); + let dummy_state = V03State::new(); node_store .update(&block, &[], vec![], &dummy_state) .unwrap(); @@ -376,12 +376,7 @@ mod tests { // Add a new block let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]); node_store - .update( - &block, - &[], - vec![], - &V03State::new_with_genesis_accounts(&[], vec![], 0), - ) + .update(&block, &[], vec![], &V03State::new()) .unwrap(); } diff --git a/lez/sequencer/core/src/config.rs b/lez/sequencer/core/src/config.rs index e554db1f..a11a9988 100644 --- a/lez/sequencer/core/src/config.rs +++ b/lez/sequencer/core/src/config.rs @@ -8,6 +8,7 @@ use std::{ use anyhow::Result; use bytesize::ByteSize; use common::config::BasicAuth; +pub use cross_zone_inbox_core::{CrossZoneConfig, CrossZonePeer}; use humantime_serde; use lee::AccountId; use logos_blockchain_core::mantle::ops::channel::ChannelId; @@ -32,8 +33,6 @@ pub enum GenesisAction { }, } -pub use cross_zone_inbox_core::{CrossZoneConfig, CrossZonePeer}; - // TODO: Provide default values #[derive(Clone, Serialize, Deserialize)] pub struct SequencerConfig { diff --git a/lez/sequencer/core/src/cross_zone_watcher.rs b/lez/sequencer/core/src/cross_zone_watcher.rs index e97a67ad..560b1eee 100644 --- a/lez/sequencer/core/src/cross_zone_watcher.rs +++ b/lez/sequencer/core/src/cross_zone_watcher.rs @@ -1,9 +1,9 @@ use std::time::Duration; use common::{block::Block, transaction::LeeTransaction}; -use cross_zone_inbox_core::{build_dispatch_from_emission, extract_emission}; +use cross_zone::{build_dispatch_from_emission, extract_emission}; use futures::StreamExt as _; -use lee::{PublicKey, program::Program}; +use lee::PublicKey; use lee_core::program::ProgramId; use log::{error, info, warn}; use logos_blockchain_core::mantle::ops::channel::ChannelId; @@ -17,20 +17,18 @@ use crate::{ config::{BedrockConfig, CrossZoneConfig}, }; -/// Spawns one watcher task per configured peer. Each task reads the peer's -/// finalized blocks from Bedrock, recognizes outbound messages addressed to this -/// zone, and injects the matching inbox dispatch as a sequencer-origin -/// transaction into the local mempool. +/// Spawns one watcher task per configured peer. +/// +/// Each task reads the peer's finalized blocks from Bedrock, recognizes outbound +/// messages addressed to this zone, and injects the matching inbox dispatch as a +/// sequencer-origin transaction into the local mempool. pub fn spawn_watchers( bedrock_config: &BedrockConfig, cross_zone: &CrossZoneConfig, poll_interval: Duration, - mempool_handle: MemPoolHandle<(TransactionOrigin, LeeTransaction)>, + mempool_handle: &MemPoolHandle<(TransactionOrigin, LeeTransaction)>, ) { let self_zone: [u8; 32] = *bedrock_config.channel_id.as_ref(); - let inbox_id = Program::cross_zone_inbox().id(); - let ping_sender_id = Program::ping_sender().id(); - let bridge_lock_id = Program::bridge_lock().id(); for peer in cross_zone.peers.clone() { let node = NodeHttpClient::new( @@ -46,9 +44,6 @@ pub fn spawn_watchers( peer.allowed_targets, expected_pubkey, self_zone, - inbox_id, - ping_sender_id, - bridge_lock_id, poll_interval, mempool_handle.clone(), )); @@ -56,8 +51,8 @@ pub fn spawn_watchers( } #[expect( - clippy::too_many_arguments, - reason = "Each parameter is an independent piece of per-peer watcher state" + clippy::infinite_loop, + reason = "the peer watcher runs for the lifetime of the sequencer process" )] async fn watch_peer( zone_indexer: ZoneIndexer, @@ -65,9 +60,6 @@ async fn watch_peer( allowed_targets: Vec, expected_pubkey: Option, self_zone: [u8; 32], - inbox_id: ProgramId, - ping_sender_id: ProgramId, - bridge_lock_id: ProgramId, poll_interval: Duration, mempool_handle: MemPoolHandle<(TransactionOrigin, LeeTransaction)>, ) { @@ -115,9 +107,6 @@ async fn watch_peer( &block, peer_zone, self_zone, - inbox_id, - ping_sender_id, - bridge_lock_id, &allowed_targets, &mempool_handle, ) @@ -135,17 +124,10 @@ async fn watch_peer( } /// Scans one peer block for outbound messages and injects a dispatch per match. -#[expect( - clippy::too_many_arguments, - reason = "Each parameter is an independent piece of per-block delivery state" -)] async fn deliver_block( block: &Block, peer_zone: [u8; 32], self_zone: [u8; 32], - inbox_id: ProgramId, - ping_sender_id: ProgramId, - bridge_lock_id: ProgramId, allowed_targets: &[ProgramId], mempool_handle: &MemPoolHandle<(TransactionOrigin, LeeTransaction)>, ) { @@ -154,12 +136,7 @@ async fn deliver_block( continue; }; let message = public_tx.message(); - let Some(emission) = extract_emission( - message.program_id, - &message.instruction_data, - ping_sender_id, - bridge_lock_id, - ) else { + let Some(emission) = extract_emission(message.program_id, &message.instruction_data) else { continue; }; @@ -175,7 +152,6 @@ async fn deliver_block( } let dispatch = build_dispatch_from_emission( - inbox_id, peer_zone, block.header.block_id, u32::try_from(index).unwrap_or(u32::MAX), diff --git a/lez/sequencer/core/src/lib.rs b/lez/sequencer/core/src/lib.rs index 3a35c6ab..1f7d93e0 100644 --- a/lez/sequencer/core/src/lib.rs +++ b/lez/sequencer/core/src/lib.rs @@ -8,7 +8,7 @@ use common::{ transaction::{LeeTransaction, clock_invocation}, }; use config::{GenesisAction, SequencerConfig}; -use lee::{AccountId, PublicTransaction, program::Program, public_transaction::Message}; +use lee::{AccountId, PublicTransaction, public_transaction::Message}; use lee_core::GENESIS_BLOCK_ID; use log::{error, info, warn}; use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SIZE, Ed25519Key}; @@ -44,15 +44,6 @@ pub enum TransactionOrigin { Sequencer, } -/// Whether a program may only be invoked by sequencer-origin transactions. The -/// cross-zone inbox is injected solely by the watcher; a user-submitted call -/// must be rejected at ingress, because `TransactionOrigin` is not carried in -/// the block and so cannot be re-checked later by the indexer. -#[must_use] -pub fn is_sequencer_only_program(program_id: lee::ProgramId) -> bool { - program_id == Program::cross_zone_inbox().id() -} - #[derive(Clone, Debug, BorshDeserialize)] struct DepositMetadata { recipient_id: lee::AccountId, @@ -175,7 +166,7 @@ impl SequencerCore { &config.bedrock_config, cross_zone, config.block_create_timeout, - mempool_handle.clone(), + &mempool_handle, ); } @@ -599,6 +590,16 @@ fn replay_unfulfilled_deposit_events( }); } +/// Whether a program may only be invoked by sequencer-origin transactions. +/// +/// The cross-zone inbox is injected solely by the watcher; a user-submitted call +/// must be rejected at ingress, since `TransactionOrigin` is not carried in the +/// block. +#[must_use] +pub fn is_sequencer_only_program(program_id: lee::ProgramId) -> bool { + cross_zone::is_sequencer_only_program(program_id) +} + /// Builds the initial genesis state from `testnet_initial_state` plus configured genesis /// transactions. Returns the final state and the list of [`LeeTransaction`]s that should be /// committed to the genesis block so external observers can replay them. @@ -637,7 +638,7 @@ fn build_genesis_state(config: &SequencerConfig) -> (lee::V03State, Vec (lee::V03State, Vec PublicTransaction { - let faucet_program_id = Program::faucet().id(); - let vault_program_id = Program::vault().id(); + let faucet_program_id = programs::faucet().id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, *account_id); let message = Message::try_new( faucet_program_id, - vec![lee::system_faucet_account_id(), recipient_vault_id], + vec![system_accounts::faucet_account_id(), recipient_vault_id], vec![], faucet_core::Instruction::GenesisTransferVault { vault_program_id, @@ -679,12 +680,12 @@ fn build_supply_account_genesis_transaction( } fn build_supply_bridge_account_genesis_transaction(balance: u128) -> PublicTransaction { - let faucet_program_id = Program::faucet().id(); - let bridge_account_id = lee::system_bridge_account_id(); + let faucet_program_id = programs::faucet().id(); + let bridge_account_id = system_accounts::bridge_account_id(); let message = Message::try_new( faucet_program_id, - vec![lee::system_faucet_account_id(), bridge_account_id], + vec![system_accounts::faucet_account_id(), bridge_account_id], vec![], faucet_core::Instruction::GenesisTransferDirect { amount: balance }, ) @@ -708,14 +709,14 @@ fn build_bridge_deposit_tx_from_event(event: &PendingDepositEventRecord) -> Resu let metadata = DepositMetadata::decode(&event.metadata) .context("Failed to decode finalized Bedrock deposit metadata")?; - let bridge_program_id = Program::bridge().id(); - let vault_program_id = Program::vault().id(); + let bridge_program_id = programs::bridge().id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, metadata.recipient_id); let message = Message::try_new( bridge_program_id, - vec![lee::system_bridge_account_id(), recipient_vault_id], + vec![system_accounts::bridge_account_id(), recipient_vault_id], vec![], bridge_core::Instruction::Deposit { l1_deposit_op_id: event.deposit_op_id.0, @@ -740,7 +741,7 @@ fn extract_bridge_deposit_id(tx: &LeeTransaction) -> Option { }; let message = tx.message(); - if message.program_id != lee::program::Program::bridge().id() { + if message.program_id != programs::bridge().id() { return None; } @@ -763,7 +764,7 @@ fn extract_bridge_withdraw_data(tx: &LeeTransaction) -> Option { }; let message = tx.message(); - if message.program_id != lee::program::Program::bridge().id() { + if message.program_id != programs::bridge().id() { return None; } @@ -861,23 +862,23 @@ mod tests { }; use key_protocol::key_management::KeyChain; use lee::{ - Account, AccountId, Data, EphemeralPublicKey, PrivacyPreservingTransaction, - SharedSecretKey, V03State, + Account, AccountId, Data, EphemeralPublicKey, PrivacyPreservingTransaction, PrivateKey, + PublicKey, PublicTransaction, SharedSecretKey, V03State, error::LeeError, execute_and_prove, privacy_preserving_transaction::{Message, circuit::ProgramWithDependencies}, program::Program, - system_bridge_account_id, }; use lee_core::{ Commitment, EncryptedAccountData, InputAccountIdentity, Nullifier, account::{AccountWithMetadata, Nonce}, + program::PdaSeed, }; use logos_blockchain_core::mantle::ops::channel::ChannelId; use mempool::MemPoolHandle; use storage::sequencer::sequencer_cells::PendingDepositEventRecord; use tempfile::tempdir; - use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys}; + use testnet_initial_state::{initial_pub_accounts_private_keys, initial_public_user_accounts}; use crate::{ TransactionOrigin, @@ -959,7 +960,7 @@ mod tests { return false; }; - if public_tx.message.program_id != lee::program::Program::bridge().id() { + if public_tx.message.program_id != programs::bridge().id() { return false; } @@ -988,8 +989,8 @@ mod tests { assert_eq!(sequencer.chain_height, 1); assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10); - let acc1_account_id = initial_accounts()[0].account_id; - let acc2_account_id = initial_accounts()[1].account_id; + let acc1_account_id = initial_public_user_accounts()[0].account_id; + let acc2_account_id = initial_public_user_accounts()[1].account_id; let balance_acc_1 = sequencer.state.get_account_by_id(acc1_account_id).balance; let balance_acc_2 = sequencer.state.get_account_by_id(acc2_account_id).balance; @@ -1050,7 +1051,7 @@ mod tests { let config = setup_sequencer_config(); let deposit_op_id = [13_u8; 32]; let expected_amount = 1_u64; - let recipient_id = initial_accounts()[0].account_id; + let recipient_id = initial_public_user_accounts()[0].account_id; { let (_sequencer, _mempool_handle) = @@ -1120,8 +1121,8 @@ mod tests { async fn transaction_pre_check_native_transfer_valid() { let (_sequencer, _mempool_handle) = common_setup().await; - let acc1 = initial_accounts()[0].account_id; - let acc2 = initial_accounts()[1].account_id; + let acc1 = initial_public_user_accounts()[0].account_id; + let acc2 = initial_public_user_accounts()[1].account_id; let sign_key1 = create_signing_key_for_account1(); @@ -1137,8 +1138,8 @@ mod tests { async fn transaction_pre_check_native_transfer_other_signature() { let (mut sequencer, _mempool_handle) = common_setup().await; - let acc1 = initial_accounts()[0].account_id; - let acc2 = initial_accounts()[1].account_id; + let acc1 = initial_public_user_accounts()[0].account_id; + let acc2 = initial_public_user_accounts()[1].account_id; let sign_key2 = create_signing_key_for_account2(); @@ -1162,8 +1163,8 @@ mod tests { async fn transaction_pre_check_native_transfer_sent_too_much() { let (mut sequencer, _mempool_handle) = common_setup().await; - let acc1 = initial_accounts()[0].account_id; - let acc2 = initial_accounts()[1].account_id; + let acc1 = initial_public_user_accounts()[0].account_id; + let acc2 = initial_public_user_accounts()[1].account_id; let sign_key1 = create_signing_key_for_account1(); @@ -1191,8 +1192,8 @@ mod tests { async fn transaction_execute_native_transfer() { let (mut sequencer, _mempool_handle) = common_setup().await; - let acc1 = initial_accounts()[0].account_id; - let acc2 = initial_accounts()[1].account_id; + let acc1 = initial_public_user_accounts()[0].account_id; + let acc2 = initial_public_user_accounts()[1].account_id; let sign_key1 = create_signing_key_for_account1(); @@ -1258,8 +1259,8 @@ mod tests { async fn replay_transactions_are_rejected_in_the_same_block() { let (mut sequencer, mempool_handle) = common_setup().await; - let acc1 = initial_accounts()[0].account_id; - let acc2 = initial_accounts()[1].account_id; + let acc1 = initial_public_user_accounts()[0].account_id; + let acc2 = initial_public_user_accounts()[1].account_id; let sign_key1 = create_signing_key_for_account1(); @@ -1301,8 +1302,8 @@ mod tests { async fn replay_transactions_are_rejected_in_different_blocks() { let (mut sequencer, mempool_handle) = common_setup().await; - let acc1 = initial_accounts()[0].account_id; - let acc2 = initial_accounts()[1].account_id; + let acc1 = initial_public_user_accounts()[0].account_id; + let acc2 = initial_public_user_accounts()[1].account_id; let sign_key1 = create_signing_key_for_account1(); @@ -1352,8 +1353,8 @@ mod tests { #[tokio::test] async fn restart_from_storage() { let config = setup_sequencer_config(); - let acc1_account_id = initial_accounts()[0].account_id; - let acc2_account_id = initial_accounts()[1].account_id; + let acc1_account_id = initial_public_user_accounts()[0].account_id; + let acc2_account_id = initial_public_user_accounts()[1].account_id; let balance_to_move = 13; // In the following code block a transaction will be processed that moves `balance_to_move` @@ -1401,11 +1402,11 @@ mod tests { // Balances should be consistent with the stored block assert_eq!( balance_acc_1, - initial_accounts()[0].balance - balance_to_move + initial_public_user_accounts()[0].balance - balance_to_move ); assert_eq!( balance_acc_2, - initial_accounts()[1].balance + balance_to_move + initial_public_user_accounts()[1].balance + balance_to_move ); } @@ -1440,8 +1441,8 @@ mod tests { #[tokio::test] async fn produce_block_with_correct_prev_meta_after_restart() { let config = setup_sequencer_config(); - let acc1_account_id = initial_accounts()[0].account_id; - let acc2_account_id = initial_accounts()[1].account_id; + let acc1_account_id = initial_public_user_accounts()[0].account_id; + let acc2_account_id = initial_public_user_accounts()[1].account_id; // Step 1: Create initial database with some block metadata let expected_prev_meta = { @@ -1520,8 +1521,8 @@ mod tests { // be dropped because their diffs touch the clock accounts. let crafted_clock_tx = { let message = lee::public_transaction::Message::try_new( - lee::program::Program::clock().id(), - lee::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), + programs::clock().id(), + system_accounts::clock_account_ids().to_vec(), vec![], 42_u64, ) @@ -1563,11 +1564,10 @@ mod tests { async fn user_tx_that_chain_calls_clock_is_dropped() { let (mut sequencer, mempool_handle) = common_setup().await; + let clock_chain_caller = test_programs::clock_chain_caller(); // Deploy the clock_chain_caller test program. let deploy_tx = LeeTransaction::ProgramDeployment(lee::ProgramDeploymentTransaction::new( - lee::program_deployment_transaction::Message::new( - test_program_methods::CLOCK_CHAIN_CALLER_ELF.to_vec(), - ), + lee::program_deployment_transaction::Message::new(clock_chain_caller.elf().to_owned()), )); mempool_handle .push((TransactionOrigin::User, deploy_tx)) @@ -1578,16 +1578,13 @@ mod tests { // Build a user transaction that invokes clock_chain_caller, which in turn chain-calls the // clock program with the clock accounts. The sequencer should detect that the resulting // state diff modifies clock accounts and drop the transaction. - let clock_chain_caller_id = - lee::program::Program::new(test_program_methods::CLOCK_CHAIN_CALLER_ELF.to_vec()) - .unwrap() - .id(); - let clock_program_id = lee::program::Program::clock().id(); + let clock_chain_caller_id = test_programs::clock_chain_caller().id(); + let clock_program_id = programs::clock().id(); let timestamp: u64 = 0; let message = lee::public_transaction::Message::try_new( clock_chain_caller_id, - lee::CLOCK_PROGRAM_ACCOUNT_IDS.to_vec(), + system_accounts::clock_account_ids().to_vec(), vec![], // no signers (clock_program_id, timestamp), ) @@ -1623,7 +1620,7 @@ mod tests { let (mut sequencer, mempool_handle) = common_setup().await; // Corrupt the clock 01 account data so the clock program panics on deserialization. - let clock_account_id = lee::CLOCK_01_PROGRAM_ACCOUNT_ID; + let clock_account_id = system_accounts::clock_account_ids()[0]; let mut corrupted = sequencer.state.get_account_by_id(clock_account_id); corrupted.data = vec![0xff; 3].try_into().unwrap(); sequencer @@ -1651,23 +1648,21 @@ mod tests { let sender_account_id = AccountId::for_regular_private_account(&sender_keys.nullifier_public_key, 0); let sender_private_account = Account { - program_owner: Program::authenticated_transfer_program().id(), + program_owner: programs::authenticated_transfer().id(), balance: 100, nonce: Nonce(0xdead_beef), data: Data::default(), }; + let bridge_account_id = system_accounts::bridge_account_id(); - let mut state = V03State::new_with_genesis_accounts( - &[], - vec![( + let mut state = V03State::new() + .with_public_accounts([(bridge_account_id, system_accounts::bridge_account())]) + .with_private_accounts([( Commitment::new(&sender_account_id, &sender_private_account), Nullifier::for_account_initialization(&sender_account_id), - )], - 0, - ); + )]); let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account); - let bridge_account_id = system_bridge_account_id(); let sender_pre = AccountWithMetadata::new( sender_private_account, @@ -1689,10 +1684,10 @@ mod tests { .unwrap(); let program_with_deps = ProgramWithDependencies::new( - Program::bridge(), + programs::bridge(), [( - Program::authenticated_transfer_program().id(), - Program::authenticated_transfer_program(), + programs::authenticated_transfer().id(), + programs::authenticated_transfer(), )] .into(), ); @@ -1735,22 +1730,384 @@ mod tests { "Bridge withdraw invocation should be rejected in private execution" ); } + + /// Builds a [`V03State`] with the clock program and `program` registered, the three clock + /// accounts initialized, and the clock advanced to `clock_timestamp` so that reads of the + /// `CLOCK_01` account observe it. + fn state_with_clock_and_program(program: Program, clock_timestamp: u64) -> V03State { + let mut state = V03State::new().with_programs([programs::clock(), program]); + for clock_id in system_accounts::clock_account_ids() { + state.force_insert_account(clock_id, system_accounts::clock_account()); + } + state + .transition_from_public_transaction( + &clock_invocation(clock_timestamp), + 1, + clock_timestamp, + ) + .expect("Clock invocation should advance the clock"); + state + } + + fn time_locked_transfer_transaction( + from: AccountId, + from_key: &PrivateKey, + from_nonce: u128, + to: AccountId, + clock_account_id: AccountId, + amount: u128, + deadline: u64, + ) -> PublicTransaction { + let program_id = test_programs::time_locked_transfer().id(); + let message = lee::public_transaction::Message::try_new( + program_id, + vec![from, to, clock_account_id], + vec![Nonce(from_nonce)], + (amount, deadline), + ) + .unwrap(); + let witness_set = lee::public_transaction::WitnessSet::for_message(&message, &[from_key]); + PublicTransaction::new(message, witness_set) + } + + #[test] + fn time_locked_transfer_succeeds_when_deadline_has_passed() { + let clock_timestamp = 600; + let mut state = + state_with_clock_and_program(test_programs::time_locked_transfer(), clock_timestamp); + + // The recipient must be a non-default account so the program may credit it without + // claiming it. + let recipient_id = AccountId::new([42; 32]); + state.force_insert_account( + recipient_id, + Account { + program_owner: programs::authenticated_transfer().id(), + ..Account::default() + }, + ); + + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let sender_id = AccountId::from(&PublicKey::new_from_private_key(&key1)); + state.force_insert_account( + sender_id, + Account { + program_owner: test_programs::time_locked_transfer().id(), + balance: 100, + ..Account::default() + }, + ); + + let amount = 100; + // Deadline is in the past relative to the clock, so the transfer is unlocked. + let deadline = 0; + + let tx = time_locked_transfer_transaction( + sender_id, + &key1, + 0, + recipient_id, + system_accounts::clock_account_ids()[0], + amount, + deadline, + ); + + state + .transition_from_public_transaction(&tx, 2, clock_timestamp) + .unwrap(); + + // Balances changed. + assert_eq!(state.get_account_by_id(sender_id).balance, 0); + assert_eq!(state.get_account_by_id(recipient_id).balance, 100); + } + + #[test] + fn time_locked_transfer_fails_when_deadline_is_in_the_future() { + let clock_timestamp = 600; + let mut state = + state_with_clock_and_program(test_programs::time_locked_transfer(), clock_timestamp); + + let recipient_id = AccountId::new([42; 32]); + state.force_insert_account( + recipient_id, + Account { + program_owner: programs::authenticated_transfer().id(), + ..Account::default() + }, + ); + + let key1 = PrivateKey::try_new([1; 32]).unwrap(); + let sender_id = AccountId::from(&PublicKey::new_from_private_key(&key1)); + state.force_insert_account( + sender_id, + Account { + program_owner: test_programs::time_locked_transfer().id(), + balance: 100, + ..Account::default() + }, + ); + + let amount = 100; + // Far-future deadline: the program panics because the clock has not reached it. + let deadline = u64::MAX; + + let tx = time_locked_transfer_transaction( + sender_id, + &key1, + 0, + recipient_id, + system_accounts::clock_account_ids()[0], + amount, + deadline, + ); + + let result = state.transition_from_public_transaction(&tx, 2, clock_timestamp); + + assert!( + result.is_err(), + "Transfer should fail when deadline is in the future" + ); + // Balances unchanged. + assert_eq!(state.get_account_by_id(sender_id).balance, 100); + assert_eq!(state.get_account_by_id(recipient_id).balance, 0); + } + + fn pinata_cooldown_data(prize: u128, cooldown_ms: u64, last_claim_timestamp: u64) -> Vec { + let mut buf = Vec::with_capacity(32); + buf.extend_from_slice(&prize.to_le_bytes()); + buf.extend_from_slice(&cooldown_ms.to_le_bytes()); + buf.extend_from_slice(&last_claim_timestamp.to_le_bytes()); + buf + } + + fn pinata_cooldown_transaction( + pinata_id: AccountId, + winner_id: AccountId, + clock_account_id: AccountId, + ) -> PublicTransaction { + let program_id = test_programs::pinata_cooldown().id(); + let message = lee::public_transaction::Message::try_new( + program_id, + vec![pinata_id, winner_id, clock_account_id], + vec![], + (), + ) + .unwrap(); + let witness_set = lee::public_transaction::WitnessSet::for_message(&message, &[]); + PublicTransaction::new(message, witness_set) + } + + #[test] + fn pinata_cooldown_claim_succeeds_after_cooldown() { + let winner_id = AccountId::new([11; 32]); + let pinata_id = AccountId::new([99; 32]); + + let genesis_timestamp = 1000; + let prize = 50; + let cooldown_ms = 500; + // Last claim was at genesis, so any timestamp >= genesis + cooldown should work. + let last_claim_timestamp = genesis_timestamp; + + // Advance the clock so the cooldown check reads an updated timestamp. + let block_timestamp = genesis_timestamp + cooldown_ms; + let mut state = + state_with_clock_and_program(test_programs::pinata_cooldown(), block_timestamp); + + // The winner must be a non-default account so the program may credit it without claiming. + state.force_insert_account( + winner_id, + Account { + program_owner: programs::authenticated_transfer().id(), + ..Account::default() + }, + ); + state.force_insert_account( + pinata_id, + Account { + program_owner: test_programs::pinata_cooldown().id(), + balance: 1000, + data: pinata_cooldown_data(prize, cooldown_ms, last_claim_timestamp) + .try_into() + .unwrap(), + ..Account::default() + }, + ); + + let tx = pinata_cooldown_transaction( + pinata_id, + winner_id, + system_accounts::clock_account_ids()[0], + ); + + state + .transition_from_public_transaction(&tx, 2, block_timestamp) + .unwrap(); + + assert_eq!(state.get_account_by_id(pinata_id).balance, 1000 - prize); + assert_eq!(state.get_account_by_id(winner_id).balance, prize); + } + + #[test] + fn pinata_cooldown_claim_fails_during_cooldown() { + let winner_id = AccountId::new([11; 32]); + let pinata_id = AccountId::new([99; 32]); + + let genesis_timestamp = 1000; + let prize = 50; + let cooldown_ms = 500; + let last_claim_timestamp = genesis_timestamp; + + // Timestamp is only 100ms after the last claim, well within the 500ms cooldown. + let block_timestamp = genesis_timestamp + 100; + let mut state = + state_with_clock_and_program(test_programs::pinata_cooldown(), block_timestamp); + + state.force_insert_account( + winner_id, + Account { + program_owner: programs::authenticated_transfer().id(), + ..Account::default() + }, + ); + state.force_insert_account( + pinata_id, + Account { + program_owner: test_programs::pinata_cooldown().id(), + balance: 1000, + data: pinata_cooldown_data(prize, cooldown_ms, last_claim_timestamp) + .try_into() + .unwrap(), + ..Account::default() + }, + ); + + let tx = pinata_cooldown_transaction( + pinata_id, + winner_id, + system_accounts::clock_account_ids()[0], + ); + + let result = state.transition_from_public_transaction(&tx, 2, block_timestamp); + + assert!(result.is_err(), "Claim should fail during cooldown period"); + assert_eq!(state.get_account_by_id(pinata_id).balance, 1000); + assert_eq!(state.get_account_by_id(winner_id).balance, 0); + } + + #[test] + fn pda_mechanism_with_pinata_token_program() { + let pinata_token = programs::pinata_token(); + let token = programs::token(); + + let pinata_definition_id = AccountId::new([1; 32]); + let pinata_token_definition_id = AccountId::new([2; 32]); + // Total supply of pinata token will be in an account under a PDA. + let pinata_token_holding_id = + AccountId::for_public_pda(&pinata_token.id(), &PdaSeed::new([0; 32])); + let winner_token_holding_id = AccountId::new([3; 32]); + + let expected_winner_account_holding = token_core::TokenHolding::Fungible { + definition_id: pinata_token_definition_id, + balance: 150, + }; + let expected_winner_token_holding_post = Account { + program_owner: token.id(), + data: Data::from(&expected_winner_account_holding), + ..Account::default() + }; + + // Register the pinata-token and token programs and create the pinata definition account. + // This replaces the removed `add_pinata_token_program` helper. + let mut state = V03State::new().with_programs([pinata_token.clone(), token.clone()]); + state.force_insert_account( + pinata_definition_id, + Account { + program_owner: pinata_token.id(), + // Difficulty: 3 + data: vec![3; 33].try_into().unwrap(), + ..Account::default() + }, + ); + + // Set up the token accounts directly (bypassing public transactions which + // would require signers for Claim::Authorized). The focus of this test is + // the PDA mechanism in the pinata program's chained call, not token creation. + let total_supply: u128 = 10_000_000; + let token_definition = token_core::TokenDefinition::Fungible { + name: String::from("PINATA"), + total_supply, + metadata_id: None, + }; + let token_holding = token_core::TokenHolding::Fungible { + definition_id: pinata_token_definition_id, + balance: total_supply, + }; + let winner_holding = token_core::TokenHolding::Fungible { + definition_id: pinata_token_definition_id, + balance: 0, + }; + state.force_insert_account( + pinata_token_definition_id, + Account { + program_owner: token.id(), + data: Data::from(&token_definition), + ..Account::default() + }, + ); + state.force_insert_account( + pinata_token_holding_id, + Account { + program_owner: token.id(), + data: Data::from(&token_holding), + ..Account::default() + }, + ); + state.force_insert_account( + winner_token_holding_id, + Account { + program_owner: token.id(), + data: Data::from(&winner_holding), + ..Account::default() + }, + ); + + // Submit a solution to the pinata program to claim the prize + let solution: u128 = 989_106; + let message = lee::public_transaction::Message::try_new( + pinata_token.id(), + vec![ + pinata_definition_id, + pinata_token_holding_id, + winner_token_holding_id, + ], + vec![], + solution, + ) + .unwrap(); + let witness_set = lee::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx, 1, 0).unwrap(); + + let winner_token_holding_post = state.get_account_by_id(winner_token_holding_id); + assert_eq!( + winner_token_holding_post, + expected_winner_token_holding_post + ); + } } #[cfg(test)] mod sequencer_only_program_tests { - use lee::program::Program; - use super::is_sequencer_only_program; #[test] fn only_the_cross_zone_inbox_is_sequencer_only() { - assert!(is_sequencer_only_program(Program::cross_zone_inbox().id())); + assert!(is_sequencer_only_program(programs::cross_zone_inbox().id())); assert!(!is_sequencer_only_program( - Program::cross_zone_outbox().id() + programs::cross_zone_outbox().id() )); - assert!(!is_sequencer_only_program(Program::wrapped_token().id())); - assert!(!is_sequencer_only_program(Program::ping_sender().id())); - assert!(!is_sequencer_only_program(Program::clock().id())); + assert!(!is_sequencer_only_program(programs::wrapped_token().id())); + assert!(!is_sequencer_only_program(programs::ping_sender().id())); + assert!(!is_sequencer_only_program(programs::clock().id())); } } diff --git a/lez/sequencer/service/Cargo.toml b/lez/sequencer/service/Cargo.toml index 4390f0d8..3427dc22 100644 --- a/lez/sequencer/service/Cargo.toml +++ b/lez/sequencer/service/Cargo.toml @@ -14,6 +14,7 @@ mempool.workspace = true sequencer_core = { workspace = true, features = ["testnet"] } sequencer_service_protocol.workspace = true sequencer_service_rpc = { workspace = true, features = ["server"] } +programs.workspace = true clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true diff --git a/lez/sequencer/service/Dockerfile b/lez/sequencer/service/Dockerfile index 5b5d3686..1919f775 100644 --- a/lez/sequencer/service/Dockerfile +++ b/lez/sequencer/service/Dockerfile @@ -1,41 +1,5 @@ -# Chef stage - uses pre-built cargo-chef image -FROM lukemathwalker/cargo-chef:latest-rust-1.94.0-slim-trixie AS chef - -# Install dependencies -RUN apt-get update && apt-get install -y \ - build-essential \ - pkg-config \ - libssl-dev \ - libclang-dev \ - clang \ - cmake \ - ninja-build \ - curl \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Install r0vm -# Use quick install for x86-64 (risczero provides binaries only for this linux platform) -# Manual build for other platforms (including arm64 Linux) -RUN ARCH=$(uname -m); \ - if [ "$ARCH" = "x86_64" ]; then \ - echo "Using quick install for $ARCH"; \ - curl -L https://risczero.com/install | bash; \ - export PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}"; \ - rzup install; \ - else \ - echo "Using manual build for $ARCH"; \ - git clone --depth 1 --branch release-3.0 https://github.com/risc0/risc0.git; \ - git clone --depth 1 --branch r0.1.91.0 https://github.com/risc0/rust.git; \ - cd /risc0; \ - cargo install --path rzup; \ - rzup build --path /rust rust --verbose; \ - cargo install --path risc0/cargo-risczero; \ - fi -ENV PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}" -RUN cp "$(which r0vm)" /usr/local/bin/r0vm -RUN test -x /usr/local/bin/r0vm -RUN r0vm --version +# Chef stage +FROM risc0_base AS chef WORKDIR /sequencer_service @@ -81,16 +45,21 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry/index \ # Runtime stage - minimal image FROM debian:trixie-slim +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + # Create non-root user for security -RUN useradd -m -u 1000 -s /bin/bash sequencer_user && \ +RUN useradd -m -u 1000 -s /bin/bash sequencer_service_user && \ mkdir -p /sequencer_service /etc/sequencer_service /var/lib/sequencer_service && \ - chown -R sequencer_user:sequencer_user /sequencer_service /etc/sequencer_service /var/lib/sequencer_service + chown -R sequencer_service_user:sequencer_service_user /sequencer_service /etc/sequencer_service /var/lib/sequencer_service # Copy binary from builder -COPY --from=builder --chown=sequencer_user:sequencer_user /usr/local/bin/sequencer_service /usr/local/bin/sequencer_service +COPY --from=builder --chown=sequencer_service_user:sequencer_service_user /usr/local/bin/sequencer_service /usr/local/bin/sequencer_service # Copy r0vm binary from builder -COPY --from=builder --chown=sequencer_user:sequencer_user /usr/local/bin/r0vm /usr/local/bin/r0vm +COPY --from=builder --chown=sequencer_service_user:sequencer_service_user /usr/local/bin/r0vm /usr/local/bin/r0vm VOLUME /var/lib/sequencer_service @@ -103,7 +72,7 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ -H "Content-Type: application/json" \ -d "{ \ \"jsonrpc\": \"2.0\", \ - \"method\": \"hello\", \ + \"method\": \"checkHealth\", \ \"params\": {}, \ \"id\": 1 \ }" || exit 1 @@ -114,7 +83,7 @@ ENV RUST_LOG=info # Set explicit location for r0vm binary ENV RISC0_SERVER_PATH=/usr/local/bin/r0vm -USER sequencer_user +USER sequencer_service_user WORKDIR /sequencer_service CMD ["sequencer_service", "/etc/sequencer_service/sequencer_config.json"] diff --git a/lez/sequencer/service/configs/debug/sequencer_config.json b/lez/sequencer/service/configs/debug/sequencer_config.json index 359c84f4..7ea85b48 100644 --- a/lez/sequencer/service/configs/debug/sequencer_config.json +++ b/lez/sequencer/service/configs/debug/sequencer_config.json @@ -11,9 +11,8 @@ "max_retries": 5 }, "channel_id": "0101010101010101010101010101010101010101010101010101010101010101", - "node_url": "http://localhost:8080" + "node_url": "http://localhost:18080" }, - "indexer_rpc_url": "ws://localhost:8779", "genesis": [ { "supply_bridge_account": { diff --git a/lez/sequencer/service/configs/docker/sequencer_config.json b/lez/sequencer/service/configs/docker/sequencer_config.json index 69238fe7..24184ea8 100644 --- a/lez/sequencer/service/configs/docker/sequencer_config.json +++ b/lez/sequencer/service/configs/docker/sequencer_config.json @@ -11,9 +11,8 @@ "max_retries": 5 }, "channel_id": "0101010101010101010101010101010101010101010101010101010101010101", - "node_url": "http://localhost:18080" + "node_url": "http://host.docker.internal:18080" }, - "indexer_rpc_url": "ws://localhost:8779", "genesis": [ { "supply_bridge_account": { diff --git a/lez/sequencer/service/docker-compose.yml b/lez/sequencer/service/docker-compose.yml index d9c7c2be..477072ad 100644 --- a/lez/sequencer/service/docker-compose.yml +++ b/lez/sequencer/service/docker-compose.yml @@ -1,9 +1,20 @@ services: + # Build-only: shared base image (toolchain + r0vm) referenced as the + # `risc0_base` named context below. It has no long-running command, so it + # only gets built โ€” it exits immediately if started. + risc0_base: + image: lez/risc0_base + build: + context: ../../.. + dockerfile: lez/docker/risc0-base.Dockerfile + sequencer_service: image: lez/sequencer_service build: - context: ../.. + context: ../../.. dockerfile: lez/sequencer/service/Dockerfile + additional_contexts: + risc0_base: "service:risc0_base" container_name: sequencer_service ports: - "3040:3040" diff --git a/lez/sequencer/service/src/service.rs b/lez/sequencer/service/src/service.rs index 38c1e5c9..c51b745b 100644 --- a/lez/sequencer/service/src/service.rs +++ b/lez/sequencer/service/src/service.rs @@ -5,7 +5,7 @@ use jsonrpsee::{ core::async_trait, types::{ErrorCode, ErrorObjectOwned}, }; -use lee::{self, program::Program}; +use lee; use log::warn; use mempool::MemPoolHandle; use sequencer_core::{ @@ -77,15 +77,14 @@ impl sequencer_service_rpc::RpcServer // watcher; a user must not invoke them top-level, or anyone could forge // an inbound cross-zone delivery. Chained user calls are already rejected // by the inbox guest's caller-is-none assertion. - if let LeeTransaction::Public(public_tx) = &authenticated_tx { - if sequencer_core::is_sequencer_only_program(public_tx.message().program_id) { - return Err(ErrorObjectOwned::owned( - ErrorCode::InvalidParams.code(), - "Program is sequencer-only and cannot be invoked by a user transaction" - .to_string(), - None::<()>, - )); - } + if let LeeTransaction::Public(public_tx) = &authenticated_tx + && sequencer_core::is_sequencer_only_program(public_tx.message().program_id) + { + return Err(ErrorObjectOwned::owned( + ErrorCode::InvalidParams.code(), + "Program is sequencer-only and cannot be invoked by a user transaction".to_owned(), + None::<()>, + )); } self.mempool_handle @@ -176,14 +175,15 @@ impl sequencer_service_rpc::RpcServer } async fn get_program_ids(&self) -> Result, ErrorObjectOwned> { + // TODO: Get programs from state let mut program_ids = BTreeMap::new(); program_ids.insert( "authenticated_transfer".to_owned(), - Program::authenticated_transfer_program().id(), + programs::authenticated_transfer().id(), ); - program_ids.insert("token".to_owned(), Program::token().id()); - program_ids.insert("pinata".to_owned(), Program::pinata().id()); - program_ids.insert("amm".to_owned(), Program::amm().id()); + program_ids.insert("token".to_owned(), programs::token().id()); + program_ids.insert("pinata".to_owned(), programs::pinata().id()); + program_ids.insert("amm".to_owned(), programs::amm().id()); program_ids.insert( "privacy_preserving_circuit".to_owned(), lee::PRIVACY_PRESERVING_CIRCUIT_ID, diff --git a/lez/storage/Cargo.toml b/lez/storage/Cargo.toml index 4588aab3..8767b525 100644 --- a/lez/storage/Cargo.toml +++ b/lez/storage/Cargo.toml @@ -15,3 +15,7 @@ thiserror.workspace = true borsh.workspace = true rocksdb.workspace = true tempfile.workspace = true + +[dev-dependencies] +programs.workspace = true +system_accounts.workspace = true diff --git a/lez/storage/src/indexer/mod.rs b/lez/storage/src/indexer/mod.rs index b682a4d7..a753a71a 100644 --- a/lez/storage/src/indexer/mod.rs +++ b/lez/storage/src/indexer/mod.rs @@ -263,7 +263,7 @@ fn closest_breakpoint_id(block_id: u64) -> u64 { #[cfg(test)] mod tests { use common::test_utils::produce_dummy_block; - use lee::{AccountId, PublicKey}; + use lee::{Account, AccountId, PublicKey}; use tempfile::tempdir; use super::*; @@ -288,20 +288,35 @@ mod tests { AccountId::from(&PublicKey::new_from_private_key(&acc2_sign_key())) } + fn initial_state() -> lee::V03State { + let mut public_accounts = [(acc1(), 10000), (acc2(), 20000)] + .into_iter() + .map(|(id, balance)| { + ( + id, + Account { + program_owner: programs::authenticated_transfer().id(), + balance, + ..Account::default() + }, + ) + }) + .collect::>(); + for clock_id in system_accounts::clock_account_ids() { + public_accounts.push((clock_id, system_accounts::clock_account())); + } + + lee::V03State::new() + .with_public_accounts(public_accounts) + .with_programs([programs::authenticated_transfer(), programs::clock()]) + } + #[test] fn start_db() { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create( - temdir_path, - &lee::V03State::new_with_genesis_accounts( - &[(acc1(), 10000), (acc2(), 20000)], - vec![], - 0, - ), - ) - .unwrap(); + let dbio = RocksDBIO::open_or_create(temdir_path, &initial_state()).unwrap(); let last_id = dbio.get_meta_last_block_id_in_db().unwrap(); let first_id = dbio.get_meta_first_block_id_in_db().unwrap(); @@ -333,15 +348,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create( - temdir_path, - &lee::V03State::new_with_genesis_accounts( - &[(acc1(), 10000), (acc2(), 20000)], - vec![], - 0, - ), - ) - .unwrap(); + let dbio = RocksDBIO::open_or_create(temdir_path, &initial_state()).unwrap(); let genesis_block = genesis_block(); dbio.put_block(&genesis_block, [0; 32]).unwrap(); @@ -392,15 +399,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create( - temdir_path, - &lee::V03State::new_with_genesis_accounts( - &[(acc1(), 10000), (acc2(), 20000)], - vec![], - 0, - ), - ) - .unwrap(); + let dbio = RocksDBIO::open_or_create(temdir_path, &initial_state()).unwrap(); let from = acc1(); let to = acc2(); @@ -464,15 +463,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create( - temdir_path, - &lee::V03State::new_with_genesis_accounts( - &[(acc1(), 10000), (acc2(), 20000)], - vec![], - 0, - ), - ) - .unwrap(); + let dbio = RocksDBIO::open_or_create(temdir_path, &initial_state()).unwrap(); let from = acc1(); let to = acc2(); @@ -546,15 +537,7 @@ mod tests { let mut block_res = vec![]; - let dbio = RocksDBIO::open_or_create( - temdir_path, - &lee::V03State::new_with_genesis_accounts( - &[(acc1(), 10000), (acc2(), 20000)], - vec![], - 0, - ), - ) - .unwrap(); + let dbio = RocksDBIO::open_or_create(temdir_path, &initial_state()).unwrap(); let from = acc1(); let to = acc2(); @@ -641,15 +624,7 @@ mod tests { let temp_dir = tempdir().unwrap(); let temdir_path = temp_dir.path(); - let dbio = RocksDBIO::open_or_create( - temdir_path, - &lee::V03State::new_with_genesis_accounts( - &[(acc1(), 10000), (acc2(), 20000)], - vec![], - 0, - ), - ) - .unwrap(); + let dbio = RocksDBIO::open_or_create(temdir_path, &initial_state()).unwrap(); let from = acc1(); let to = acc2(); diff --git a/lez/system_accounts/Cargo.toml b/lez/system_accounts/Cargo.toml new file mode 100644 index 00000000..093e6455 --- /dev/null +++ b/lez/system_accounts/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "system_accounts" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +lee_core.workspace = true +faucet_core.workspace = true +bridge_core.workspace = true +clock_core.workspace = true +programs.workspace = true diff --git a/lez/system_accounts/src/lib.rs b/lez/system_accounts/src/lib.rs new file mode 100644 index 00000000..3b6dd5af --- /dev/null +++ b/lez/system_accounts/src/lib.rs @@ -0,0 +1,71 @@ +//! This crate provides system accounts used by LEZ. + +use std::str::FromStr as _; + +use clock_core::ClockAccountData; +use lee_core::account::{Account, AccountId, Nonce}; + +#[must_use] +pub fn pinata_account_id() -> AccountId { + // TODO: Use derivation from a public key? + AccountId::from_str("EfQhKQAkX2FJiwNii2WFQsGndjvF1Mzd7RuVe7QdPLw7") + .expect("Pinata program id should be valid") +} + +#[must_use] +pub fn pinata_account() -> Account { + Account { + program_owner: programs::pinata().id(), + balance: 1_500_000, + // Difficulty: 3 + data: vec![3; 33].try_into().expect("Should fit"), + nonce: Nonce::default(), + } +} + +#[must_use] +pub fn faucet_account_id() -> AccountId { + faucet_core::compute_faucet_account_id(programs::faucet().id()) +} + +#[must_use] +pub fn faucet_account() -> Account { + Account { + program_owner: programs::authenticated_transfer().id(), + balance: u128::MAX, + ..Account::default() + } +} + +#[must_use] +pub fn bridge_account_id() -> AccountId { + bridge_core::compute_bridge_account_id(programs::bridge().id()) +} + +#[must_use] +pub fn bridge_account() -> Account { + Account { + program_owner: programs::authenticated_transfer().id(), + ..Account::default() + } +} + +#[must_use] +pub const fn clock_account_ids() -> [AccountId; 3] { + clock_core::CLOCK_PROGRAM_ACCOUNT_IDS +} + +#[must_use] +pub fn clock_account() -> Account { + Account { + program_owner: programs::clock().id(), + data: ClockAccountData { + block_id: 0, + timestamp: 0, + } + .to_bytes() + .try_into() + .expect("Clock account data should fit"), + ..Account::default() + } +} diff --git a/lez/testnet_initial_state/Cargo.toml b/lez/testnet_initial_state/Cargo.toml index c2e694b4..c0899ea9 100644 --- a/lez/testnet_initial_state/Cargo.toml +++ b/lez/testnet_initial_state/Cargo.toml @@ -8,7 +8,9 @@ license.workspace = true key_protocol.workspace = true lee.workspace = true lee_core.workspace = true -common.workspace = true +system_accounts.workspace = true +programs.workspace = true +wrapped_token_core.workspace = true serde.workspace = true diff --git a/lez/testnet_initial_state/src/lib.rs b/lez/testnet_initial_state/src/lib.rs index 5bb6e1b4..b608bb59 100644 --- a/lez/testnet_initial_state/src/lib.rs +++ b/lez/testnet_initial_state/src/lib.rs @@ -1,10 +1,11 @@ -use common::PINATA_BASE58; +use std::collections::HashMap; + use key_protocol::key_management::{ KeyChain, key_tree::chain_index::ChainIndex, secret_holders::{PrivateKeyHolder, SecretSpendingKey, ViewingSecretKey}, }; -use lee::{Account, AccountId, Data, PrivateKey, PublicKey, V03State}; +use lee::{Account, AccountId, Data, PrivateKey, PublicKey, V03State, program::Program}; use lee_core::{NullifierPublicKey, encryption::ViewingPublicKey}; use serde::{Deserialize, Serialize}; @@ -130,8 +131,7 @@ pub fn initial_pub_accounts_private_keys() -> Vec Vec { +fn initial_priv_accounts_private_keys() -> Vec { let key_chain_1 = KeyChain { secret_spending_key: SecretSpendingKey(SSK_PRIV_ACC_A), private_key_holder: PrivateKeyHolder { @@ -178,8 +178,7 @@ pub fn initial_priv_accounts_private_keys() -> Vec Vec { +fn initial_commitments() -> Vec { initial_priv_accounts_private_keys() .into_iter() .map(|data| PrivateAccountPublicInitialData { @@ -189,8 +188,27 @@ pub fn initial_commitments() -> Vec { .collect() } +fn initial_private_accounts() -> Vec<(lee_core::Commitment, lee_core::Nullifier)> { + initial_commitments() + .iter() + .map(|init_comm_data| { + let npk = &init_comm_data.npk; + let account_id = lee::AccountId::for_regular_private_account(npk, 0); + + let mut acc = init_comm_data.account.clone(); + + acc.program_owner = programs::authenticated_transfer().id(); + + ( + lee_core::Commitment::new(&account_id, &acc), + lee_core::Nullifier::for_account_initialization(&account_id), + ) + }) + .collect() +} + #[must_use] -pub fn initial_accounts() -> Vec { +pub fn initial_public_user_accounts() -> Vec { let initial_account_ids = initial_pub_accounts_private_keys() .into_iter() .map(|data| data.account_id) @@ -208,41 +226,100 @@ pub fn initial_accounts() -> Vec { ] } +fn initial_public_accounts() -> HashMap { + initial_public_user_accounts() + .iter() + .map(|acc_data| { + ( + acc_data.account_id, + Account { + program_owner: programs::authenticated_transfer().id(), + balance: acc_data.balance, + ..Default::default() + }, + ) + }) + .chain([ + ( + system_accounts::faucet_account_id(), + system_accounts::faucet_account(), + ), + ( + system_accounts::bridge_account_id(), + system_accounts::bridge_account(), + ), + ]) + .chain( + system_accounts::clock_account_ids() + .into_iter() + .map(|clock_id| (clock_id, system_accounts::clock_account())), + ) + .chain(std::iter::once(wrapped_token_config_account())) + .collect() +} + +/// The wrapped-token config account. +/// +/// Seeded so the `wrapped_token` guest can pin its authorized minter (the +/// cross-zone inbox) without importing the inbox id. Fixed for every zone, so it +/// lives in the shared initial state. +fn wrapped_token_config_account() -> (AccountId, Account) { + let wrapped_token_id = programs::wrapped_token().id(); + ( + wrapped_token_core::config_account_id(wrapped_token_id), + Account { + program_owner: wrapped_token_id, + data: wrapped_token_core::minter_bytes(programs::cross_zone_inbox().id()) + .to_vec() + .try_into() + .expect("minter id fits in account data"), + ..Default::default() + }, + ) +} + +fn initial_programs() -> Vec { + vec![ + programs::authenticated_transfer(), + programs::token(), + programs::amm(), + programs::clock(), + programs::ata(), + programs::vault(), + programs::faucet(), + programs::bridge(), + programs::cross_zone_outbox(), + programs::cross_zone_inbox(), + programs::ping_sender(), + programs::ping_receiver(), + programs::bridge_lock(), + programs::wrapped_token(), + ] +} + #[must_use] pub fn initial_state() -> V03State { - let initial_private_accounts: Vec<(lee_core::Commitment, lee_core::Nullifier)> = - initial_commitments() - .iter() - .map(|init_comm_data| { - let npk = &init_comm_data.npk; - let account_id = lee::AccountId::for_regular_private_account(npk, 0); - - let mut acc = init_comm_data.account.clone(); - - acc.program_owner = lee::program::Program::authenticated_transfer_program().id(); - - ( - lee_core::Commitment::new(&account_id, &acc), - lee_core::Nullifier::for_account_initialization(&account_id), - ) - }) - .collect(); - - let init_accs: Vec<(lee::AccountId, u128)> = initial_accounts() - .iter() - .map(|acc_data| (acc_data.account_id, acc_data.balance)) - .collect(); - - lee::V03State::new_with_genesis_accounts(&init_accs, initial_private_accounts, 0) + lee::V03State::new() + .with_public_accounts(initial_public_accounts()) + .with_private_accounts(initial_private_accounts()) + .with_programs(initial_programs()) } #[must_use] pub fn initial_state_testnet() -> V03State { - let mut state = initial_state(); + let mut initial_public_accounts = initial_public_accounts(); + initial_public_accounts.insert( + system_accounts::pinata_account_id(), + system_accounts::pinata_account(), + ); - state.add_pinata_program(PINATA_BASE58.parse().unwrap()); + let mut programs = initial_programs(); + programs.push(programs::pinata()); - state + V03State::new() + .with_public_accounts(initial_public_accounts) + .with_private_accounts(initial_private_accounts()) + .with_programs(programs) } #[cfg(test)] @@ -260,7 +337,7 @@ mod tests { #[test] fn pub_state_consistency() { let init_accs_private_data = initial_pub_accounts_private_keys(); - let init_accs_pub_data = initial_accounts(); + let init_accs_pub_data = initial_public_user_accounts(); assert_eq!( init_accs_private_data[0].account_id, @@ -412,4 +489,33 @@ mod tests { } ); } + + #[test] + fn genesis_system_accounts_have_expected_contents() { + // System-account IDs must be distinct and non-default, and the genesis + // faucet/bridge accounts must carry their expected field values. Catches + // mutations that replace `system_faucet_account`/`system_bridge_account` + // with `Default::default()`, delete their `balance`/`program_owner` + // fields, or replace `system_bridge_account_id` with `Default::default()`. + let faucet_id = system_accounts::faucet_account_id(); + let bridge_id = system_accounts::bridge_account_id(); + assert_ne!(bridge_id, AccountId::default()); + assert_ne!(faucet_id, bridge_id); + + let state = initial_state(); + let default_owner = Account::default().program_owner; + + let faucet = state.get_account_by_id(faucet_id); + assert_eq!(faucet.balance, u128::MAX, "faucet must hold u128::MAX"); + assert_ne!( + faucet.program_owner, default_owner, + "faucet must have a non-default program_owner" + ); + + let bridge = state.get_account_by_id(bridge_id); + assert_ne!( + bridge.program_owner, default_owner, + "bridge must have a non-default program_owner" + ); + } } diff --git a/lez/wallet-ffi/Cargo.toml b/lez/wallet-ffi/Cargo.toml index 1e8b6395..47d18c53 100644 --- a/lez/wallet-ffi/Cargo.toml +++ b/lez/wallet-ffi/Cargo.toml @@ -16,11 +16,14 @@ lee.workspace = true lee_core.workspace = true sequencer_service_rpc = { workspace = true, features = ["client"] } common.workspace = true +programs.workspace = true tokio.workspace = true key_protocol.workspace = true serde_json.workspace = true risc0-zkvm.workspace = true +bip39.workspace = true +vault_core.workspace = true [build-dependencies] cbindgen = "0.29" diff --git a/lez/wallet-ffi/src/bridge.rs b/lez/wallet-ffi/src/bridge.rs new file mode 100644 index 00000000..fca71b1c --- /dev/null +++ b/lez/wallet-ffi/src/bridge.rs @@ -0,0 +1,91 @@ +//! Bridge program functions (deposit/withdraw between L1 Bedrock and L2). + +use std::{ffi::CString, ptr}; + +use lee::AccountId; +use wallet::program_facades::bridge::Bridge; + +use crate::{ + block_on, + error::{print_error, WalletFfiError}, + map_execution_error, + types::{FfiBytes32, FfiTransferResult, WalletHandle}, + wallet::get_wallet, +}; + +/// Withdraw native tokens from a public account to Bedrock (L1) through the bridge. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `from`: Source public account ID (must be owned by this wallet). Bridge withdrawals only +/// support public sender accounts. +/// - `amount`: Amount of native tokens to withdraw +/// - `bedrock_account_pk`: Recipient's Bedrock (L1) public key, 32 bytes +/// - `out_result`: Output pointer for the withdraw result +/// +/// # Returns +/// - `Success` if the withdraw transaction was submitted successfully +/// - `InsufficientFunds` if the source account doesn't have enough balance +/// - `KeyNotFound` if the source account's signing key is not in this wallet +/// - Error code on other failures +/// +/// # Memory +/// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `from` must be a valid pointer to a `FfiBytes32` struct +/// - `bedrock_account_pk` must be a valid pointer to a `FfiBytes32` struct +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_bridge_withdraw( + handle: *mut WalletHandle, + from: *const FfiBytes32, + amount: u64, + bedrock_account_pk: *const FfiBytes32, + out_result: *mut FfiTransferResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if from.is_null() || bedrock_account_pk.is_null() || out_result.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + let from_id = AccountId::new(unsafe { (*from).data }); + let bedrock_account_pk = unsafe { (*bedrock_account_pk).data }; + + let bridge = Bridge(&wallet); + + match block_on(bridge.send_withdraw(from_id, amount, bedrock_account_pk)) { + Ok(tx_hash) => { + let tx_hash = CString::new(tx_hash.to_string()) + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + } + WalletFfiError::Success + } + Err(e) => { + print_error(format!("Bridge withdraw failed: {e:?}")); + unsafe { + (*out_result).tx_hash = ptr::null_mut(); + (*out_result).success = false; + } + map_execution_error(e) + } + } +} diff --git a/lez/wallet-ffi/src/generic_transaction.rs b/lez/wallet-ffi/src/generic_transaction.rs index f8f344ad..7be6ddaf 100644 --- a/lez/wallet-ffi/src/generic_transaction.rs +++ b/lez/wallet-ffi/src/generic_transaction.rs @@ -10,7 +10,7 @@ use crate::{ error::{print_error, WalletFfiError}, map_execution_error, wallet::get_wallet, - FfiAccountIdentity, FfiBytes32, WalletHandle, + FfiAccountIdentity, FfiBytes32, FfiProgramId, WalletHandle, }; #[repr(C)] @@ -48,7 +48,7 @@ impl TryFrom<&FfiProgram> for Program { elf.push(unsafe { *value.elf_data.add(i) }); } - Self::new(elf).map_err(|err| { + Self::new(elf.into()).map_err(|err| { print_error(format!("Invalid program bytecode, err: {err}")); WalletFfiError::InvalidBytecode }) @@ -214,7 +214,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( account_identities_size: usize, instruction_words: *const u32, instruction_words_size: usize, - program_with_dependencies: *const FfiProgramWithDependencies, + program_id: FfiProgramId, out_result: *mut FfiTransactionResult, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { @@ -260,12 +260,7 @@ pub unsafe extern "C" fn wallet_ffi_send_generic_public_transaction( } } - let program = match unsafe { &*program_with_dependencies }.try_into() { - Ok(v) => v, - Err(err) => return err, - }; - - match block_on(wallet.send_pub_tx(accounts, instruction_data.to_vec(), &program)) { + match block_on(wallet.send_pub_tx(accounts, instruction_data.to_vec(), program_id.into())) { Ok(tx_hash) => { let tx_hash = CString::new(tx_hash.to_string()) .map_or(std::ptr::null_mut(), std::ffi::CString::into_raw); @@ -445,13 +440,11 @@ pub unsafe extern "C" fn wallet_ffi_free_instruction_words(words: *mut FfiInstru #[cfg(test)] mod tests { - use lee::program::Program; - use crate::generic_transaction::FfiProgram; #[test] fn program_cast_consistency() { - let prog = Program::amm(); + let prog = programs::amm(); let first_5_bytes = prog.elf()[..5].to_vec(); diff --git a/lez/wallet-ffi/src/lib.rs b/lez/wallet-ffi/src/lib.rs index 6f4c1808..6c86b0c8 100644 --- a/lez/wallet-ffi/src/lib.rs +++ b/lez/wallet-ffi/src/lib.rs @@ -42,6 +42,7 @@ pub use types::*; use crate::error::print_error; pub mod account; +pub mod bridge; pub mod error; pub mod generic_transaction; pub mod keys; @@ -50,6 +51,7 @@ pub mod program_deployment; pub mod sync; pub mod transfer; pub mod types; +pub mod vault; pub mod wallet; static TOKIO_RUNTIME: OnceLock = OnceLock::new(); diff --git a/lez/wallet-ffi/src/program_deployment.rs b/lez/wallet-ffi/src/program_deployment.rs index 8820a935..2086fa5c 100644 --- a/lez/wallet-ffi/src/program_deployment.rs +++ b/lez/wallet-ffi/src/program_deployment.rs @@ -1,7 +1,7 @@ use std::{ffi::CString, ptr, slice}; use common::transaction::LeeTransaction; -use lee::{program::Program, ProgramDeploymentTransaction}; +use lee::ProgramDeploymentTransaction; use sequencer_service_rpc::RpcClient as _; use crate::{ @@ -112,7 +112,7 @@ pub unsafe extern "C" fn wallet_ffi_transfer_elf(ffi_program: *mut FfiProgram) - return WalletFfiError::NullPointer; } - let elf = Program::authenticated_transfer_program().elf().to_vec(); + let elf = programs::authenticated_transfer().elf().to_vec(); let (raw_elf_data, raw_elf_size, _) = elf.into_raw_parts(); @@ -147,7 +147,7 @@ pub unsafe extern "C" fn wallet_ffi_token_elf(ffi_program: *mut FfiProgram) -> W return WalletFfiError::NullPointer; } - let elf = Program::token().elf().to_vec(); + let elf = programs::token().elf().to_vec(); let (raw_elf_data, raw_elf_size, _) = elf.into_raw_parts(); @@ -182,7 +182,7 @@ pub unsafe extern "C" fn wallet_ffi_amm_elf(ffi_program: *mut FfiProgram) -> Wal return WalletFfiError::NullPointer; } - let elf = Program::amm().elf().to_vec(); + let elf = programs::amm().elf().to_vec(); let (raw_elf_data, raw_elf_size, _) = elf.into_raw_parts(); @@ -217,7 +217,7 @@ pub unsafe extern "C" fn wallet_ffi_ata_elf(ffi_program: *mut FfiProgram) -> Wal return WalletFfiError::NullPointer; } - let elf = Program::ata().elf().to_vec(); + let elf = programs::ata().elf().to_vec(); let (raw_elf_data, raw_elf_size, _) = elf.into_raw_parts(); diff --git a/lez/wallet-ffi/src/types.rs b/lez/wallet-ffi/src/types.rs index ad366b91..cbe9fab7 100644 --- a/lez/wallet-ffi/src/types.rs +++ b/lez/wallet-ffi/src/types.rs @@ -7,7 +7,7 @@ use std::{ str::FromStr as _, }; -use lee::{Data, SharedSecretKey}; +use lee::{Data, ProgramId, SharedSecretKey}; use lee_core::{encryption::MlKem768EncapsulationKey, NullifierPublicKey}; use wallet::AccountIdentity; @@ -581,6 +581,18 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { } } +impl From for FfiProgramId { + fn from(value: ProgramId) -> Self { + Self { data: value } + } +} + +impl From for ProgramId { + fn from(value: FfiProgramId) -> Self { + value.data + } +} + #[cfg(test)] mod tests { use lee::{AccountId, PrivateKey, PublicKey}; diff --git a/lez/wallet-ffi/src/vault.rs b/lez/wallet-ffi/src/vault.rs new file mode 100644 index 00000000..6db1fa66 --- /dev/null +++ b/lez/wallet-ffi/src/vault.rs @@ -0,0 +1,219 @@ +//! Bridge vault claim functions. +//! +//! L1 Bedrock deposits are minted into a per-owner vault PDA account by the bridge program, not +//! directly into the owner's account (see `bridge.rs`). The owner must separately claim the +//! deposited funds from their vault into their own account with a signed transaction. + +use std::{ffi::CString, ptr}; + +use lee::AccountId; +use wallet::program_facades::vault::Vault; + +use crate::{ + block_on, + error::{print_error, WalletFfiError}, + map_execution_error, + types::{FfiBytes32, FfiTransferResult, WalletHandle}, + wallet::get_wallet, +}; + +/// Get the claimable balance held in an account's bridge vault. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `owner`: The account ID whose vault balance to query +/// - `out_balance`: Output for balance as little-endian [u8; 16] +/// +/// # Returns +/// - `Success` on successful query +/// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `owner` must be a valid pointer to a `FfiBytes32` struct +/// - `out_balance` must be a valid pointer to a `[u8; 16]` array +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_get_vault_balance( + handle: *mut WalletHandle, + owner: *const FfiBytes32, + out_balance: *mut [u8; 16], +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if owner.is_null() || out_balance.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + let owner_id = AccountId::new(unsafe { (*owner).data }); + let vault_id = vault_core::compute_vault_account_id(programs::vault().id(), owner_id); + + let balance = match block_on(wallet.get_account_balance(vault_id)) { + Ok(b) => b, + Err(e) => { + print_error(format!("Failed to get vault balance: {e}")); + return WalletFfiError::NetworkError; + } + }; + + unsafe { + *out_balance = balance.to_le_bytes(); + } + + WalletFfiError::Success +} + +/// Claim native tokens from a public owner's vault into their account. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `owner`: Owner account ID (must be owned by this wallet, public) +/// - `amount`: Amount to claim as little-endian [u8; 16] +/// - `out_result`: Output pointer for the claim result +/// +/// # Returns +/// - `Success` if the claim was submitted successfully +/// - `InsufficientFunds` if the vault doesn't have enough balance +/// - `KeyNotFound` if the owner's signing key is not in this wallet +/// - Error code on other failures +/// +/// # Memory +/// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `owner` must be a valid pointer to a `FfiBytes32` struct +/// - `amount` must be a valid pointer to a `[u8; 16]` array +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_vault_claim( + handle: *mut WalletHandle, + owner: *const FfiBytes32, + amount: *const [u8; 16], + out_result: *mut FfiTransferResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if owner.is_null() || amount.is_null() || out_result.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + let owner_id = AccountId::new(unsafe { (*owner).data }); + let amount = u128::from_le_bytes(unsafe { *amount }); + + match block_on(Vault(&wallet).send_claim(owner_id, amount)) { + Ok(tx_hash) => { + let tx_hash = CString::new(tx_hash.to_string()) + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + } + WalletFfiError::Success + } + Err(e) => { + print_error(format!("Vault claim failed: {e:?}")); + unsafe { + (*out_result).tx_hash = ptr::null_mut(); + (*out_result).success = false; + } + map_execution_error(e) + } + } +} + +/// Claim native tokens from a private owner's vault into their account. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `owner`: Owner account ID (must be owned by this wallet, private) +/// - `amount`: Amount to claim as little-endian [u8; 16] +/// - `out_result`: Output pointer for the claim result +/// +/// # Returns +/// - `Success` if the claim was submitted successfully +/// - `InsufficientFunds` if the vault doesn't have enough balance +/// - `KeyNotFound` if the owner's signing key is not in this wallet +/// - Error code on other failures +/// +/// # Memory +/// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `owner` must be a valid pointer to a `FfiBytes32` struct +/// - `amount` must be a valid pointer to a `[u8; 16]` array +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_vault_claim_private( + handle: *mut WalletHandle, + owner: *const FfiBytes32, + amount: *const [u8; 16], + out_result: *mut FfiTransferResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if owner.is_null() || amount.is_null() || out_result.is_null() { + print_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + let owner_id = AccountId::new(unsafe { (*owner).data }); + let amount = u128::from_le_bytes(unsafe { *amount }); + + match block_on(Vault(&wallet).send_claim_private_owner(owner_id, amount)) { + Ok((tx_hash, _shared_key)) => { + let tx_hash = CString::new(tx_hash.to_string()) + .map_or(ptr::null_mut(), std::ffi::CString::into_raw); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + } + WalletFfiError::Success + } + Err(e) => { + print_error(format!("Vault claim failed: {e:?}")); + unsafe { + (*out_result).tx_hash = ptr::null_mut(); + (*out_result).success = false; + } + map_execution_error(e) + } + } +} diff --git a/lez/wallet-ffi/src/wallet.rs b/lez/wallet-ffi/src/wallet.rs index 7aabaa2d..b19aaae5 100644 --- a/lez/wallet-ffi/src/wallet.rs +++ b/lez/wallet-ffi/src/wallet.rs @@ -1,16 +1,18 @@ //! Wallet lifecycle management functions. use std::{ - ffi::{c_char, CStr}, + ffi::{c_char, CStr, CString}, path::PathBuf, ptr, + str::FromStr as _, sync::Mutex, }; -use wallet::WalletCore; +use bip39::Mnemonic; +use wallet::{cli::execute_keys_restoration, WalletCore}; use crate::{ - c_str_to_string, + block_on, c_str_to_string, error::{print_error, WalletFfiError}, types::WalletHandle, }; @@ -20,6 +22,22 @@ pub(crate) struct WalletWrapper { pub core: Mutex, } +#[repr(C)] +pub struct FfiCreateWalletOutput { + pub wallet: *mut WalletHandle, + /// C compatible(null terminated) string. + pub mnemonic: *mut c_char, +} + +impl Default for FfiCreateWalletOutput { + fn default() -> Self { + Self { + wallet: std::ptr::null_mut(), + mnemonic: std::ptr::null_mut(), + } + } +} + /// Helper to get the wallet wrapper from an opaque handle. pub(crate) fn get_wallet( handle: *mut WalletHandle, @@ -71,8 +89,8 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result *mut WalletHandle { +) -> FfiCreateWalletOutput { let Ok(config_path) = c_str_to_path(config_path, "config_path") else { - return ptr::null_mut(); + return FfiCreateWalletOutput::default(); }; let Ok(storage_path) = c_str_to_path(storage_path, "storage_path") else { - return ptr::null_mut(); + return FfiCreateWalletOutput::default(); }; let Ok(password) = c_str_to_string(password, "password") else { - return ptr::null_mut(); + return FfiCreateWalletOutput::default(); }; match WalletCore::new_init_storage(config_path, storage_path, None, &password) { - Ok((core, _mnemonic)) => { + Ok((core, mnemonic)) => { let wrapper = Box::new(WalletWrapper { core: Mutex::new(core), }); - Box::into_raw(wrapper).cast::() + let handle = Box::into_raw(wrapper).cast::(); + + let Ok(c_mnemonic_string) = CString::new(mnemonic.to_string()) else { + return FfiCreateWalletOutput::default(); + }; + + let raw_pointer = CString::into_raw(c_mnemonic_string); + + FfiCreateWalletOutput { + wallet: handle, + mnemonic: raw_pointer, + } } Err(e) => { print_error(format!("Failed to create wallet: {e}")); - ptr::null_mut() + FfiCreateWalletOutput::default() } } } @@ -204,6 +233,81 @@ pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfi } } +/// Restore wallet data from mnemonic and password. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `mnemonic`: Valid pointer to instance of `* char`, provided by `wallet_ffi_create_new` +/// - `password`: Valid pointer to C string. +/// - `depth`: Depth of a reconstructed tree +/// +/// # Returns +/// - `Success` on successful restoration +/// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `mnemonic` must be a valid pointer to instance of `* char`, provided by +/// `wallet_ffi_create_new` +/// - `password` must be a valid pointer to C string. +/// - `depth` parameter induces exponential growth in execution time, be aware of it. +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_restore_data( + handle: *mut WalletHandle, + mnemonic: *const c_char, + password: *const c_char, + depth: u32, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + let mut wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + let Ok(password) = c_str_to_string(password, "password") else { + return WalletFfiError::NullPointer; + }; + + let Ok(mnemonic) = c_str_to_string(mnemonic, "mnemonic") else { + return WalletFfiError::NullPointer; + }; + + let mnemonic = match Mnemonic::from_str(&mnemonic) { + Ok(mn) => mn, + Err(e) => { + print_error(format!("Failed to parse mnemonic: {e}")); + return WalletFfiError::SerializationError; + } + }; + + let res = match wallet.restore_storage(&mnemonic, &password) { + Ok(()) => WalletFfiError::Success, + Err(e) => { + print_error(format!("Failed to restore wallet data: {e}")); + WalletFfiError::StorageError + } + }; + + if res == WalletFfiError::Success { + match block_on(execute_keys_restoration(&mut wallet, depth)) { + Ok(()) => WalletFfiError::Success, + Err(err) => { + print_error(format!("Failed to restore wallet data: {err}")); + WalletFfiError::StorageError + } + } + } else { + res + } +} + /// Get the sequencer address from the wallet configuration. /// /// # Parameters diff --git a/lez/wallet-ffi/wallet_ffi.h b/lez/wallet-ffi/wallet_ffi.h index 58a39b47..d83c520e 100644 --- a/lez/wallet-ffi/wallet_ffi.h +++ b/lez/wallet-ffi/wallet_ffi.h @@ -219,6 +219,20 @@ typedef struct FfiAccount { struct FfiU128 nonce; } FfiAccount; +/** + * Result of a transfer operation. + */ +typedef struct FfiTransferResult { + /** + * Transaction hash (null-terminated string, or null on failure). + */ + char *tx_hash; + /** + * Whether the transfer succeeded. + */ + bool success; +} FfiTransferResult; + typedef struct FfiInstructionWords { uint32_t *instruction_words; uintptr_t instruction_words_size; @@ -242,23 +256,6 @@ typedef struct FfiAccountIdentity { struct FfiU128 identifier; } FfiAccountIdentity; -/** - * Intended to be created manually. - */ -typedef struct FfiProgram { - const uint8_t *elf_data; - uintptr_t elf_size; -} FfiProgram; - -/** - * Intended to be created manually. - */ -typedef struct FfiProgramWithDependencies { - struct FfiProgram program; - const struct FfiProgram *deps; - uintptr_t deps_size; -} FfiProgramWithDependencies; - /** * Result of a generic transaction operation. */ @@ -278,6 +275,23 @@ typedef struct FfiTransactionResult { uintptr_t secrets_size; } FfiTransactionResult; +/** + * Intended to be created manually. + */ +typedef struct FfiProgram { + const uint8_t *elf_data; + uintptr_t elf_size; +} FfiProgram; + +/** + * Intended to be created manually. + */ +typedef struct FfiProgramWithDependencies { + struct FfiProgram program; + const struct FfiProgram *deps; + uintptr_t deps_size; +} FfiProgramWithDependencies; + /** * Public key info for a public account. */ @@ -285,19 +299,13 @@ typedef struct FfiPublicAccountKey { struct FfiBytes32 public_key; } FfiPublicAccountKey; -/** - * Result of a transfer operation. - */ -typedef struct FfiTransferResult { +typedef struct FfiCreateWalletOutput { + struct WalletHandle *wallet; /** - * Transaction hash (null-terminated string, or null on failure). + * C compatible(null terminated) string. */ - char *tx_hash; - /** - * Whether the transfer succeeded. - */ - bool success; -} FfiTransferResult; + char *mnemonic; +} FfiCreateWalletOutput; /** * Create a new public account. @@ -532,6 +540,38 @@ enum WalletFfiError wallet_ffi_import_private_account(struct WalletHandle *handl const struct FfiU128 *identifier, const char *account_state_json); +/** + * Withdraw native tokens from a public account to Bedrock (L1) through the bridge. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `from`: Source public account ID (must be owned by this wallet). Bridge withdrawals only + * support public sender accounts. + * - `amount`: Amount of native tokens to withdraw + * - `bedrock_account_pk`: Recipient's Bedrock (L1) public key, 32 bytes + * - `out_result`: Output pointer for the withdraw result + * + * # Returns + * - `Success` if the withdraw transaction was submitted successfully + * - `InsufficientFunds` if the source account doesn't have enough balance + * - `KeyNotFound` if the source account's signing key is not in this wallet + * - Error code on other failures + * + * # Memory + * The result must be freed with `wallet_ffi_free_transfer_result()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `from` must be a valid pointer to a `FfiBytes32` struct + * - `bedrock_account_pk` must be a valid pointer to a `FfiBytes32` struct + * - `out_result` must be a valid pointer to a `FfiTransferResult` struct + */ +enum WalletFfiError wallet_ffi_bridge_withdraw(struct WalletHandle *handle, + const struct FfiBytes32 *from, + uint64_t amount, + const struct FfiBytes32 *bedrock_account_pk, + struct FfiTransferResult *out_result); + /** * Serialize sequence of bytes into RISC0 readable words. * @@ -573,7 +613,7 @@ enum WalletFfiError wallet_ffi_send_generic_public_transaction(struct WalletHand uintptr_t account_identities_size, const uint32_t *instruction_words, uintptr_t instruction_words_size, - const struct FfiProgramWithDependencies *program_with_dependencies, + struct FfiProgramId program_id, struct FfiTransactionResult *out_result); /** @@ -1329,6 +1369,85 @@ enum WalletFfiError wallet_ffi_register_private_account(struct WalletHandle *han */ void wallet_ffi_free_transfer_result(struct FfiTransferResult *result); +/** + * Get the claimable balance held in an account's bridge vault. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `owner`: The account ID whose vault balance to query + * - `out_balance`: Output for balance as little-endian [u8; 16] + * + * # Returns + * - `Success` on successful query + * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `owner` must be a valid pointer to a `FfiBytes32` struct + * - `out_balance` must be a valid pointer to a `[u8; 16]` array + */ +enum WalletFfiError wallet_ffi_get_vault_balance(struct WalletHandle *handle, + const struct FfiBytes32 *owner, + uint8_t (*out_balance)[16]); + +/** + * Claim native tokens from a public owner's vault into their account. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `owner`: Owner account ID (must be owned by this wallet, public) + * - `amount`: Amount to claim as little-endian [u8; 16] + * - `out_result`: Output pointer for the claim result + * + * # Returns + * - `Success` if the claim was submitted successfully + * - `InsufficientFunds` if the vault doesn't have enough balance + * - `KeyNotFound` if the owner's signing key is not in this wallet + * - Error code on other failures + * + * # Memory + * The result must be freed with `wallet_ffi_free_transfer_result()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `owner` must be a valid pointer to a `FfiBytes32` struct + * - `amount` must be a valid pointer to a `[u8; 16]` array + * - `out_result` must be a valid pointer to a `FfiTransferResult` struct + */ +enum WalletFfiError wallet_ffi_vault_claim(struct WalletHandle *handle, + const struct FfiBytes32 *owner, + const uint8_t (*amount)[16], + struct FfiTransferResult *out_result); + +/** + * Claim native tokens from a private owner's vault into their account. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `owner`: Owner account ID (must be owned by this wallet, private) + * - `amount`: Amount to claim as little-endian [u8; 16] + * - `out_result`: Output pointer for the claim result + * + * # Returns + * - `Success` if the claim was submitted successfully + * - `InsufficientFunds` if the vault doesn't have enough balance + * - `KeyNotFound` if the owner's signing key is not in this wallet + * - Error code on other failures + * + * # Memory + * The result must be freed with `wallet_ffi_free_transfer_result()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `owner` must be a valid pointer to a `FfiBytes32` struct + * - `amount` must be a valid pointer to a `[u8; 16]` array + * - `out_result` must be a valid pointer to a `FfiTransferResult` struct + */ +enum WalletFfiError wallet_ffi_vault_claim_private(struct WalletHandle *handle, + const struct FfiBytes32 *owner, + const uint8_t (*amount)[16], + struct FfiTransferResult *out_result); + /** * Create a new wallet with fresh storage. * @@ -1341,15 +1460,15 @@ void wallet_ffi_free_transfer_result(struct FfiTransferResult *result); * - `password`: Password for encrypting the wallet seed * * # Returns - * - Opaque wallet handle on success - * - Null pointer on error (call `wallet_ffi_get_last_error()` for details) + * - Result, which contains opaque wallet handle and mnemonic words on success + * - Result with null pointers on error (call `wallet_ffi_get_last_error()` for details) * * # Safety * All string parameters must be valid null-terminated UTF-8 strings. */ -struct WalletHandle *wallet_ffi_create_new(const char *config_path, - const char *storage_path, - const char *password); +struct FfiCreateWalletOutput wallet_ffi_create_new(const char *config_path, + const char *storage_path, + const char *password); /** * Open an existing wallet from storage. @@ -1399,6 +1518,31 @@ void wallet_ffi_destroy(struct WalletHandle *handle); */ enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); +/** + * Restore wallet data from mnemonic and password. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `mnemonic`: Valid pointer to instance of `* char`, provided by `wallet_ffi_create_new` + * - `password`: Valid pointer to C string. + * - `depth`: Depth of a reconstructed tree + * + * # Returns + * - `Success` on successful restoration + * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `mnemonic` must be a valid pointer to instance of `* char`, provided by + * `wallet_ffi_create_new` + * - `password` must be a valid pointer to C string. + * - `depth` parameter induces exponential growth in execution time, be aware of it. + */ +enum WalletFfiError wallet_ffi_restore_data(struct WalletHandle *handle, + const char *mnemonic, + const char *password, + uint32_t depth); + /** * Get the sequencer address from the wallet configuration. * diff --git a/lez/wallet/Cargo.toml b/lez/wallet/Cargo.toml index e04b0d91..974d6a71 100644 --- a/lez/wallet/Cargo.toml +++ b/lez/wallet/Cargo.toml @@ -17,10 +17,12 @@ sequencer_service_rpc = { workspace = true, features = ["client"] } amm_core.workspace = true testnet_initial_state.workspace = true token_core.workspace = true -ata_core.workspace = true vault_core.workspace = true bridge_core.workspace = true keycard_wallet.workspace = true +programs.workspace = true +system_accounts.workspace = true +associated_token_account_core.workspace = true bip39.workspace = true pyo3.workspace = true diff --git a/lez/wallet/src/cli/account.rs b/lez/wallet/src/cli/account.rs index e0ec2d0f..fb0db2cb 100644 --- a/lez/wallet/src/cli/account.rs +++ b/lez/wallet/src/cli/account.rs @@ -2,7 +2,7 @@ use anyhow::{Context as _, Result}; use clap::Subcommand; use itertools::Itertools as _; use key_protocol::key_management::{KeyChain, key_tree::chain_index::ChainIndex}; -use lee::{Account, PublicKey, program::Program}; +use lee::{Account, PublicKey}; use lee_core::Identifier; use token_core::{TokenDefinition, TokenHolding}; @@ -545,8 +545,8 @@ impl WalletSubcommand for ImportSubcommand { /// Formats account details for display, returning (description, `json_view`). fn format_account_details(account: &Account) -> (String, String) { - let auth_tr_prog_id = Program::authenticated_transfer_program().id(); - let token_prog_id = Program::token().id(); + let auth_tr_prog_id = programs::authenticated_transfer().id(); + let token_prog_id = programs::token().id(); match &account.program_owner { o if *o == auth_tr_prog_id => { diff --git a/lez/wallet/src/cli/keycard.rs b/lez/wallet/src/cli/keycard.rs index a2eae73c..37a83948 100644 --- a/lez/wallet/src/cli/keycard.rs +++ b/lez/wallet/src/cli/keycard.rs @@ -1,3 +1,8 @@ +#![expect( + clippy::print_stderr, + reason = "This is a CLI application, printing to stderr is expected and convenient" +)] + use anyhow::Result; use clap::Subcommand; use keycard_wallet::{KeycardWallet, clear_pairing, python_path}; diff --git a/lez/wallet/src/cli/mod.rs b/lez/wallet/src/cli/mod.rs index 8a91d3ae..aeadf426 100644 --- a/lez/wallet/src/cli/mod.rs +++ b/lez/wallet/src/cli/mod.rs @@ -6,7 +6,7 @@ use clap::{Parser, Subcommand}; use common::{HashType, transaction::LeeTransaction}; use derive_more::Display; use futures::TryFutureExt as _; -use lee::{ProgramDeploymentTransaction, program::Program}; +use lee::ProgramDeploymentTransaction; use sequencer_service_rpc::RpcClient as _; pub use crate::helperfunctions::{read_mnemonic, read_pin}; @@ -228,14 +228,14 @@ pub async fn execute_subcommand( panic!("Missing authenticated transfer ID from remote"); }; assert!( - authenticated_transfer_id == &Program::authenticated_transfer_program().id(), + authenticated_transfer_id == &::programs::authenticated_transfer().id(), "Local ID for authenticated transfer program is different from remote" ); let Some(token_id) = remote_program_ids.get("token") else { panic!("Missing token program ID from remote"); }; assert!( - token_id == &Program::token().id(), + token_id == &::programs::token().id(), "Local ID for token program is different from remote" ); let Some(circuit_id) = remote_program_ids.get("privacy_preserving_circuit") else { @@ -249,7 +249,7 @@ pub async fn execute_subcommand( panic!("Missing AMM program ID from remote"); }; assert!( - amm_id == &Program::amm().id(), + amm_id == &::programs::amm().id(), "Local ID for AMM program is different from remote" ); diff --git a/lez/wallet/src/cli/programs/ata.rs b/lez/wallet/src/cli/programs/ata.rs index b77ff61f..7afda06f 100644 --- a/lez/wallet/src/cli/programs/ata.rs +++ b/lez/wallet/src/cli/programs/ata.rs @@ -1,7 +1,7 @@ use anyhow::Result; use clap::Subcommand; use common::transaction::LeeTransaction; -use lee::{Account, AccountId, program::Program}; +use lee::{Account, AccountId}; use token_core::TokenHolding; use crate::{ @@ -79,10 +79,10 @@ impl WalletSubcommand for AtaSubcommand { owner, token_definition, } => { - let ata_program_id = Program::ata().id(); - let ata_id = ata_core::get_associated_token_account_id( + let ata_program_id = programs::ata().id(); + let ata_id = associated_token_account_core::get_associated_token_account_id( &ata_program_id, - &ata_core::compute_ata_seed(owner, token_definition), + &associated_token_account_core::compute_ata_seed(owner, token_definition), ); println!("{ata_id}"); Ok(SubcommandReturnValue::Empty) @@ -218,12 +218,12 @@ impl WalletSubcommand for AtaSubcommand { owner, token_definition, } => { - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); for def in &token_definition { - let ata_id = ata_core::get_associated_token_account_id( + let ata_id = associated_token_account_core::get_associated_token_account_id( &ata_program_id, - &ata_core::compute_ata_seed(owner, *def), + &associated_token_account_core::compute_ata_seed(owner, *def), ); let account = wallet_core.get_account_public(ata_id).await?; diff --git a/lez/wallet/src/cli/programs/pinata.rs b/lez/wallet/src/cli/programs/pinata.rs index 7ef92270..c08a33b8 100644 --- a/lez/wallet/src/cli/programs/pinata.rs +++ b/lez/wallet/src/cli/programs/pinata.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; use clap::Subcommand; -use common::{PINATA_BASE58, transaction::LeeTransaction}; +use common::transaction::LeeTransaction; use lee::{Account, AccountId}; use crate::{ @@ -33,13 +33,13 @@ impl WalletSubcommand for PinataProgramAgnosticSubcommand { match to { AccountIdWithPrivacy::Public(to) => { PinataProgramSubcommand::Public(PinataProgramSubcommandPublic::Claim { - pinata_account_id: PINATA_BASE58.parse()?, + pinata_account_id: system_accounts::pinata_account_id(), winner_account_id: to, }) } AccountIdWithPrivacy::Private(to) => PinataProgramSubcommand::Private( PinataProgramSubcommandPrivate::ClaimPrivateOwned { - pinata_account_id: PINATA_BASE58.parse()?, + pinata_account_id: system_accounts::pinata_account_id(), winner_account_id: to, }, ), diff --git a/lez/wallet/src/lib.rs b/lez/wallet/src/lib.rs index 7dece16a..80b42e17 100644 --- a/lez/wallet/src/lib.rs +++ b/lez/wallet/src/lib.rs @@ -1,6 +1,5 @@ #![expect( clippy::print_stdout, - clippy::print_stderr, reason = "This is a CLI application, printing to stdout and stderr is expected and convenient" )] #![expect( @@ -17,7 +16,7 @@ use common::{HashType, transaction::LeeTransaction}; use config::WalletConfig; use key_protocol::key_management::key_tree::chain_index::ChainIndex; use lee::{ - Account, AccountId, PrivacyPreservingTransaction, + Account, AccountId, PrivacyPreservingTransaction, ProgramId, privacy_preserving_transaction::{ circuit::ProgramWithDependencies, message::EncryptedAccountData, }, @@ -620,9 +619,9 @@ impl WalletCore { &self, accounts: Vec, instruction_data: InstructionData, - program: &ProgramWithDependencies, + program_id: ProgramId, ) -> Result { - self.send_pub_tx_with_pre_check(accounts, instruction_data, program, |_| Ok(())) + self.send_pub_tx_with_pre_check(accounts, instruction_data, program_id, |_| Ok(())) .await } @@ -630,7 +629,7 @@ impl WalletCore { &self, accounts: Vec, instruction_data: InstructionData, - program: &ProgramWithDependencies, + program_id: ProgramId, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, ) -> Result { // Public transaction, all accounts must be public @@ -653,7 +652,6 @@ impl WalletCore { )?; let account_ids = acc_manager.public_account_ids(); - let program_id = program.program.id(); let nonces = acc_manager.public_account_nonces(); let message = lee::public_transaction::Message::new_preserialized( @@ -872,3 +870,26 @@ impl WalletCore { &self.config_overrides } } + +#[cfg(test)] +mod tests { + use std::{ffi::CString, str::FromStr as _}; + + use bip39::Mnemonic; + + #[test] + fn mnemonic_roundtrip() { + let mnemonic = + Mnemonic::from_entropy(&[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap(); + + let c_mnemonic_string = CString::new(mnemonic.to_string()).unwrap(); + let c_mnemonic_string_raw = c_mnemonic_string.into_raw(); + // Safety: Will be safe, pointer is created from CString + let c_str = unsafe { CString::from_raw(c_mnemonic_string_raw) }; + let mn_string = c_str.to_str().unwrap(); + + let mn_ret = Mnemonic::from_str(mn_string).unwrap(); + + assert_eq!(mnemonic, mn_ret); + } +} diff --git a/lez/wallet/src/program_facades/amm.rs b/lez/wallet/src/program_facades/amm.rs index e1261b26..bc5c5d62 100644 --- a/lez/wallet/src/program_facades/amm.rs +++ b/lez/wallet/src/program_facades/amm.rs @@ -22,8 +22,7 @@ impl Amm<'_> { .public_account_id() .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let program = Program::amm(); - let amm_program_id = Program::amm().id(); + let amm_program_id = programs::amm().id(); let user_a_acc = self .0 .get_account_public(a_id) @@ -67,7 +66,7 @@ impl Amm<'_> { user_holding_lp, ], instruction_data, - &program.into(), + amm_program_id, ) .await } @@ -87,8 +86,7 @@ impl Amm<'_> { .public_account_id() .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let program = Program::amm(); - let amm_program_id = Program::amm().id(); + let amm_program_id = programs::amm().id(); let user_a_acc = self .0 .get_account_public(a_id) @@ -149,7 +147,7 @@ impl Amm<'_> { user_b_signing_identity, ], instruction_data, - &program.into(), + amm_program_id, ) .await } @@ -169,8 +167,7 @@ impl Amm<'_> { .public_account_id() .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let program = Program::amm(); - let amm_program_id = Program::amm().id(); + let amm_program_id = programs::amm().id(); let user_a_acc = self .0 .get_account_public(a_id) @@ -231,7 +228,7 @@ impl Amm<'_> { user_b_signing_identity, ], instruction_data, - &program.into(), + amm_program_id, ) .await } @@ -252,8 +249,7 @@ impl Amm<'_> { .public_account_id() .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let program = Program::amm(); - let amm_program_id = Program::amm().id(); + let amm_program_id = programs::amm().id(); let user_a_acc = self .0 .get_account_public(a_id) @@ -297,7 +293,7 @@ impl Amm<'_> { user_holding_lp, ], instruction_data, - &program.into(), + amm_program_id, ) .await } @@ -311,8 +307,7 @@ impl Amm<'_> { min_amount_to_remove_token_a: u128, min_amount_to_remove_token_b: u128, ) -> Result { - let program = Program::amm(); - let amm_program_id = Program::amm().id(); + let amm_program_id = programs::amm().id(); let user_a_acc = self .0 .get_account_public(user_holding_a) @@ -356,7 +351,7 @@ impl Amm<'_> { user_holding_lp, ], instruction_data, - &program.into(), + amm_program_id, ) .await } diff --git a/lez/wallet/src/program_facades/ata.rs b/lez/wallet/src/program_facades/ata.rs index e6b8fc29..6c9ec568 100644 --- a/lez/wallet/src/program_facades/ata.rs +++ b/lez/wallet/src/program_facades/ata.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use ata_core::{compute_ata_seed, get_associated_token_account_id}; +use associated_token_account_core::{compute_ata_seed, get_associated_token_account_id}; use common::HashType; use lee::{ AccountId, privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program, @@ -21,13 +21,12 @@ impl Ata<'_> { .public_account_id() .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let program = Program::ata(); - let ata_program_id = program.id(); + let ata_program_id = programs::ata().id(); let ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_id, definition_id), ); - let instruction = ata_core::Instruction::Create { ata_program_id }; + let instruction = associated_token_account_core::Instruction::Create { ata_program_id }; let instruction_data = Program::serialize_instruction(instruction).expect("Instruction should serialize"); @@ -39,7 +38,7 @@ impl Ata<'_> { AccountIdentity::PublicNoSign(ata_id), ], instruction_data, - &program.into(), + ata_program_id, ) .await } @@ -55,13 +54,12 @@ impl Ata<'_> { .public_account_id() .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let program = Program::ata(); - let ata_program_id = program.id(); + let ata_program_id = programs::ata().id(); let sender_ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_id, definition_id), ); - let instruction = ata_core::Instruction::Transfer { + let instruction = associated_token_account_core::Instruction::Transfer { ata_program_id, amount, }; @@ -76,7 +74,7 @@ impl Ata<'_> { AccountIdentity::PublicNoSign(recipient_id), ], instruction_data, - &program.into(), + ata_program_id, ) .await } @@ -91,13 +89,12 @@ impl Ata<'_> { .public_account_id() .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let program = Program::ata(); - let ata_program_id = program.id(); + let ata_program_id = programs::ata().id(); let holder_ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_id, definition_id), ); - let instruction = ata_core::Instruction::Burn { + let instruction = associated_token_account_core::Instruction::Burn { ata_program_id, amount, }; @@ -112,7 +109,7 @@ impl Ata<'_> { AccountIdentity::PublicNoSign(definition_id), ], instruction_data, - &program.into(), + ata_program_id, ) .await } @@ -122,13 +119,13 @@ impl Ata<'_> { owner_id: AccountId, definition_id: AccountId, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_id, definition_id), ); - let instruction = ata_core::Instruction::Create { ata_program_id }; + let instruction = associated_token_account_core::Instruction::Create { ata_program_id }; let instruction_data = Program::serialize_instruction(instruction).expect("Instruction should serialize"); @@ -156,13 +153,13 @@ impl Ata<'_> { recipient_id: AccountId, amount: u128, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let sender_ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_id, definition_id), ); - let instruction = ata_core::Instruction::Transfer { + let instruction = associated_token_account_core::Instruction::Transfer { ata_program_id, amount, }; @@ -192,13 +189,13 @@ impl Ata<'_> { definition_id: AccountId, amount: u128, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let ata_program_id = Program::ata().id(); + let ata_program_id = programs::ata().id(); let holder_ata_id = get_associated_token_account_id( &ata_program_id, &compute_ata_seed(owner_id, definition_id), ); - let instruction = ata_core::Instruction::Burn { + let instruction = associated_token_account_core::Instruction::Burn { ata_program_id, amount, }; @@ -224,8 +221,8 @@ impl Ata<'_> { } fn ata_with_token_dependency() -> ProgramWithDependencies { - let token = Program::token(); + let token = programs::token(); let mut deps = HashMap::new(); deps.insert(token.id(), token); - ProgramWithDependencies::new(Program::ata(), deps) + ProgramWithDependencies::new(programs::ata(), deps) } diff --git a/lez/wallet/src/program_facades/bridge.rs b/lez/wallet/src/program_facades/bridge.rs index 60ad9483..c9aa585e 100644 --- a/lez/wallet/src/program_facades/bridge.rs +++ b/lez/wallet/src/program_facades/bridge.rs @@ -12,8 +12,7 @@ impl Bridge<'_> { amount: u64, bedrock_account_pk: [u8; 32], ) -> Result { - let program = Program::bridge(); - let bridge_account_id = lee::system_bridge_account_id(); + let bridge_account_id = system_accounts::bridge_account_id(); let instruction = bridge_core::Instruction::Withdraw { amount, bedrock_account_pk, @@ -28,7 +27,7 @@ impl Bridge<'_> { AccountIdentity::PublicNoSign(bridge_account_id), ], instruction_data, - &program.into(), + programs::bridge().id(), ) .await } diff --git a/lez/wallet/src/program_facades/native_token_transfer/mod.rs b/lez/wallet/src/program_facades/native_token_transfer/mod.rs index 74f9d7b7..c4e673fa 100644 --- a/lez/wallet/src/program_facades/native_token_transfer/mod.rs +++ b/lez/wallet/src/program_facades/native_token_transfer/mod.rs @@ -26,7 +26,6 @@ fn auth_transfer_preparation( amount: balance_to_move, }) .unwrap(); - let program = Program::authenticated_transfer_program(); // TODO: handle large Err-variant properly let tx_pre_check = move |accounts: &[&Account]| { @@ -38,5 +37,9 @@ fn auth_transfer_preparation( } }; - (instruction_data, program, tx_pre_check) + ( + instruction_data, + programs::authenticated_transfer(), + tx_pre_check, + ) } diff --git a/lez/wallet/src/program_facades/native_token_transfer/private.rs b/lez/wallet/src/program_facades/native_token_transfer/private.rs index 6937ee5a..712d6774 100644 --- a/lez/wallet/src/program_facades/native_token_transfer/private.rs +++ b/lez/wallet/src/program_facades/native_token_transfer/private.rs @@ -23,7 +23,7 @@ impl NativeTokenTransfer<'_> { .send_privacy_preserving_tx( vec![account], Program::serialize_instruction(instruction).unwrap(), - &Program::authenticated_transfer_program().into(), + &programs::authenticated_transfer().into(), ) .await .map(|(resp, secrets)| { diff --git a/lez/wallet/src/program_facades/native_token_transfer/public.rs b/lez/wallet/src/program_facades/native_token_transfer/public.rs index 47cbf2c0..ee01d9c8 100644 --- a/lez/wallet/src/program_facades/native_token_transfer/public.rs +++ b/lez/wallet/src/program_facades/native_token_transfer/public.rs @@ -21,7 +21,7 @@ impl NativeTokenTransfer<'_> { .send_pub_tx_with_pre_check( vec![from, to], instruction_data, - &program.into(), + program.id(), tx_pre_check, ) .await @@ -31,11 +31,14 @@ impl NativeTokenTransfer<'_> { &self, account: AccountIdentity, ) -> Result { - let program = Program::authenticated_transfer_program(); let instruction_data = Program::serialize_instruction(AuthTransferInstruction::Initialize)?; self.0 - .send_pub_tx(vec![account], instruction_data, &program.into()) + .send_pub_tx( + vec![account], + instruction_data, + programs::authenticated_transfer().id(), + ) .await } } diff --git a/lez/wallet/src/program_facades/pinata.rs b/lez/wallet/src/program_facades/pinata.rs index 8da5f1a5..3a1ff898 100644 --- a/lez/wallet/src/program_facades/pinata.rs +++ b/lez/wallet/src/program_facades/pinata.rs @@ -13,7 +13,6 @@ impl Pinata<'_> { winner_account_id: AccountId, solution: u128, ) -> Result { - let program = Program::pinata(); let instruction = solution; let instruction_data = Program::serialize_instruction(instruction).expect("Instruction should serialize"); @@ -25,7 +24,7 @@ impl Pinata<'_> { AccountIdentity::PublicNoSign(winner_account_id), ], instruction_data, - &program.into(), + programs::pinata().id(), ) .await } @@ -61,7 +60,7 @@ impl Pinata<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], lee::program::Program::serialize_instruction(solution).unwrap(), - &lee::program::Program::pinata().into(), + &programs::pinata().into(), ) .await .map(|(resp, secrets)| { diff --git a/lez/wallet/src/program_facades/token.rs b/lez/wallet/src/program_facades/token.rs index 2170c046..d634976f 100644 --- a/lez/wallet/src/program_facades/token.rs +++ b/lez/wallet/src/program_facades/token.rs @@ -15,13 +15,16 @@ impl Token<'_> { name: String, total_supply: u128, ) -> Result { - let program = Program::token(); let instruction = Instruction::NewFungibleDefinition { name, total_supply }; let instruction_data = Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 - .send_pub_tx(vec![definition, supply], instruction_data, &program.into()) + .send_pub_tx( + vec![definition, supply], + instruction_data, + programs::token().id(), + ) .await } @@ -45,7 +48,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -77,7 +80,7 @@ impl Token<'_> { AccountIdentity::Public(supply_account_id), ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -111,7 +114,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -128,7 +131,6 @@ impl Token<'_> { recipient: AccountIdentity, amount: u128, ) -> Result { - let program = Program::token(); let instruction = Instruction::Transfer { amount_to_transfer: amount, }; @@ -136,7 +138,11 @@ impl Token<'_> { Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 - .send_pub_tx(vec![sender, recipient], instruction_data, &program.into()) + .send_pub_tx( + vec![sender, recipient], + instruction_data, + programs::token().id(), + ) .await } @@ -163,7 +169,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -201,7 +207,7 @@ impl Token<'_> { }, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -233,7 +239,7 @@ impl Token<'_> { AccountIdentity::Public(recipient_account_id), ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -265,7 +271,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -301,7 +307,7 @@ impl Token<'_> { }, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -319,7 +325,6 @@ impl Token<'_> { holder: AccountIdentity, amount: u128, ) -> Result { - let program = Program::token(); let instruction = Instruction::Burn { amount_to_burn: amount, }; @@ -330,7 +335,7 @@ impl Token<'_> { .send_pub_tx( vec![AccountIdentity::PublicNoSign(definition_account_id), holder], instruction_data, - &program.into(), + programs::token().id(), ) .await } @@ -358,7 +363,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -390,7 +395,7 @@ impl Token<'_> { AccountIdentity::Public(holder_account_id), ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -423,7 +428,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -441,7 +446,6 @@ impl Token<'_> { holder: AccountIdentity, amount: u128, ) -> Result { - let program = Program::token(); let instruction = Instruction::Mint { amount_to_mint: amount, }; @@ -449,7 +453,11 @@ impl Token<'_> { Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 - .send_pub_tx(vec![definition, holder], instruction_data, &program.into()) + .send_pub_tx( + vec![definition, holder], + instruction_data, + programs::token().id(), + ) .await } @@ -476,7 +484,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -514,7 +522,7 @@ impl Token<'_> { }, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -546,7 +554,7 @@ impl Token<'_> { AccountIdentity::Public(holder_account_id), ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -579,7 +587,7 @@ impl Token<'_> { .ok_or(ExecutionFailureKind::KeyNotFoundError)?, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { @@ -616,7 +624,7 @@ impl Token<'_> { }, ], instruction_data, - &Program::token().into(), + &programs::token().into(), ) .await .map(|(resp, secrets)| { diff --git a/lez/wallet/src/program_facades/vault.rs b/lez/wallet/src/program_facades/vault.rs index bccee4f2..dcb5c5e0 100644 --- a/lez/wallet/src/program_facades/vault.rs +++ b/lez/wallet/src/program_facades/vault.rs @@ -17,8 +17,7 @@ impl Vault<'_> { recipient_id: AccountId, amount: u128, ) -> Result { - let program = Program::vault(); - let vault_program_id = program.id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id); @@ -36,7 +35,7 @@ impl Vault<'_> { AccountIdentity::PublicNoSign(recipient_vault_id), ], instruction_data, - &program.into(), + vault_program_id, ) .await } @@ -47,7 +46,7 @@ impl Vault<'_> { recipient_id: AccountId, amount: u128, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let vault_program_id = Program::vault().id(); + let vault_program_id = programs::vault().id(); let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id); let instruction = vault_core::Instruction::Transfer { @@ -80,8 +79,7 @@ impl Vault<'_> { owner_id: AccountId, amount: u128, ) -> Result { - let program = Program::vault(); - let vault_program_id = program.id(); + let vault_program_id = programs::vault().id(); let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id); let instruction = vault_core::Instruction::Claim { amount }; @@ -95,7 +93,7 @@ impl Vault<'_> { AccountIdentity::PublicNoSign(owner_vault_id), ], instruction_data, - &program.into(), + vault_program_id, ) .await } @@ -105,7 +103,7 @@ impl Vault<'_> { owner_id: AccountId, amount: u128, ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { - let vault_program_id = Program::vault().id(); + let vault_program_id = programs::vault().id(); let owner_vault_id = vault_core::compute_vault_account_id(vault_program_id, owner_id); let instruction = vault_core::Instruction::Claim { amount }; @@ -132,8 +130,8 @@ impl Vault<'_> { } fn vault_with_auth_dependency() -> ProgramWithDependencies { - let auth_transfer = Program::authenticated_transfer_program(); + let auth_transfer = programs::authenticated_transfer(); let mut deps = HashMap::new(); deps.insert(auth_transfer.id(), auth_transfer); - ProgramWithDependencies::new(Program::vault(), deps) + ProgramWithDependencies::new(programs::vault(), deps) } diff --git a/lez/wallet/src/storage.rs b/lez/wallet/src/storage.rs index d1415c2b..a79996f8 100644 --- a/lez/wallet/src/storage.rs +++ b/lez/wallet/src/storage.rs @@ -94,6 +94,7 @@ impl Storage { self.key_chain = UserKeyChain::new_with_accounts(public_tree, private_tree); self.labels = BTreeMap::new(); + self.last_synced_block = 0; Ok(()) } diff --git a/program_methods/guest/Cargo.toml b/program_methods/guest/Cargo.toml deleted file mode 100644 index 3db42314..00000000 --- a/program_methods/guest/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "programs" -version = "0.1.0" -edition = "2024" -license = { workspace = true } - -[lints] -workspace = true - -[dependencies] -lee_core.workspace = true -authenticated_transfer_core.workspace = true -clock_core.workspace = true -token_core.workspace = true -token_program.workspace = true -amm_core.workspace = true -amm_program.workspace = true -ata_core.workspace = true -ata_program.workspace = true -faucet_core.workspace = true -bridge_core.workspace = true -vault_core.workspace = true -cross_zone_outbox_core.workspace = true -cross_zone_inbox_core.workspace = true -ping_core.workspace = true -wrapped_token_core.workspace = true -bridge_lock_core.workspace = true -risc0-zkvm.workspace = true -serde = { workspace = true, default-features = false } diff --git a/test_fixtures/Cargo.toml b/test_fixtures/Cargo.toml index 54cb6733..71f887b6 100644 --- a/test_fixtures/Cargo.toml +++ b/test_fixtures/Cargo.toml @@ -18,6 +18,7 @@ sequencer_core = { workspace = true, features = ["default", "testnet"] } sequencer_service.workspace = true sequencer_service_rpc = { workspace = true, features = ["client"] } wallet.workspace = true +programs.workspace = true anyhow.workspace = true bytesize.workspace = true diff --git a/test_fixtures/src/config.rs b/test_fixtures/src/config.rs index 08eb952f..ea52bf5b 100644 --- a/test_fixtures/src/config.rs +++ b/test_fixtures/src/config.rs @@ -168,12 +168,10 @@ pub fn wallet_config(sequencer_addr: SocketAddr) -> Result { pub fn indexer_config( bedrock_addr: SocketAddr, - home: PathBuf, channel_id: ChannelId, cross_zone: Option, ) -> Result { Ok(IndexerConfig { - home, consensus_info_polling_interval: Duration::from_secs(1), bedrock_config: ClientConfig { addr: addr_to_url(UrlProtocol::Http, bedrock_addr) @@ -208,9 +206,8 @@ pub fn bedrock_channel_id() -> ChannelId { ChannelId::from(channel_id) } -/// Second channel on the same Bedrock node, for two-zone tests. -/// Distinct from [`bedrock_channel_id`] so two zones settle independently on -/// one shared L1. +/// A second zone's channel id, distinct from [`bedrock_channel_id`] so two zones +/// settle independently on one shared Bedrock node in the cross-zone tests. #[must_use] pub fn bedrock_channel_id_b() -> ChannelId { let channel_id: [u8; 32] = [0_u8, 2] diff --git a/test_fixtures/src/lib.rs b/test_fixtures/src/lib.rs index 283240d9..5edc64b3 100644 --- a/test_fixtures/src/lib.rs +++ b/test_fixtures/src/lib.rs @@ -32,9 +32,6 @@ pub mod setup; // TODO: Remove this and control time from tests pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; -pub const LEE_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin"; -pub const LEE_PROGRAM_FOR_TEST_NOOP: &str = "noop.bin"; -pub const LEE_PROGRAM_FOR_TEST_PDA_SPEND_PROXY: &str = "pda_spend_proxy.bin"; pub(crate) const BEDROCK_SERVICE_WITH_OPEN_PORT: &str = "logos-blockchain-node-0"; pub(crate) const BEDROCK_SERVICE_PORT: u16 = 18080; diff --git a/test_fixtures/src/setup.rs b/test_fixtures/src/setup.rs index dd8753f3..27d6a079 100644 --- a/test_fixtures/src/setup.rs +++ b/test_fixtures/src/setup.rs @@ -102,15 +102,10 @@ pub async fn setup_indexer( temp_indexer_dir.path().display() ); - let indexer_config = config::indexer_config( - bedrock_addr, - temp_indexer_dir.path().to_owned(), - channel_id, - cross_zone, - ) - .context("Failed to create Indexer config")?; + let indexer_config = config::indexer_config(bedrock_addr, channel_id, cross_zone) + .context("Failed to create Indexer config")?; - indexer_service::run_server(indexer_config, 0) + indexer_service::run_server(indexer_config, temp_indexer_dir.path(), 0) .await .context("Failed to run Indexer Service") .map(|handle| (handle, temp_indexer_dir)) diff --git a/test_program_methods/src/lib.rs b/test_program_methods/src/lib.rs deleted file mode 100644 index 1bdb3085..00000000 --- a/test_program_methods/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/test_program_methods/Cargo.toml b/test_programs/Cargo.toml similarity index 76% rename from test_program_methods/Cargo.toml rename to test_programs/Cargo.toml index 9b4934e2..8499f5aa 100644 --- a/test_program_methods/Cargo.toml +++ b/test_programs/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "test_program_methods" +name = "test_programs" version = "0.1.0" edition = "2024" license = { workspace = true } @@ -7,6 +7,9 @@ license = { workspace = true } [lints] workspace = true +[dependencies] +lee.workspace = true + [build-dependencies] risc0-build.workspace = true diff --git a/test_program_methods/build.rs b/test_programs/build.rs similarity index 100% rename from test_program_methods/build.rs rename to test_programs/build.rs diff --git a/test_program_methods/guest/Cargo.toml b/test_programs/guest/Cargo.toml similarity index 77% rename from test_program_methods/guest/Cargo.toml rename to test_programs/guest/Cargo.toml index f08b520b..1d6f927d 100644 --- a/test_program_methods/guest/Cargo.toml +++ b/test_programs/guest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "test_programs" +name = "test_program_guests" version = "0.1.0" edition = "2024" license = { workspace = true } @@ -14,4 +14,3 @@ clock_core.workspace = true faucet_core.workspace = true risc0-zkvm.workspace = true -serde = { workspace = true, default-features = false } diff --git a/test_program_methods/guest/src/bin/chain_caller.rs b/test_programs/guest/src/bin/chain_caller.rs similarity index 100% rename from test_program_methods/guest/src/bin/chain_caller.rs rename to test_programs/guest/src/bin/chain_caller.rs diff --git a/test_programs/guest/src/bin/claimer.rs b/test_programs/guest/src/bin/claimer.rs new file mode 100644 index 00000000..b0efe992 --- /dev/null +++ b/test_programs/guest/src/bin/claimer.rs @@ -0,0 +1,30 @@ +use lee_core::program::{AccountPostState, Claim, ProgramInput, ProgramOutput, read_lee_inputs}; + +type Instruction = (); + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction: (), + }, + instruction_words, + ) = read_lee_inputs::(); + + let Ok([pre]) = <[_; 1]>::try_from(pre_states) else { + return; + }; + + let account_post = AccountPostState::new_claimed(pre.account.clone(), Claim::Authorized); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![pre], + vec![account_post], + ) + .write(); +} diff --git a/test_program_methods/guest/src/bin/clock_chain_caller.rs b/test_programs/guest/src/bin/clock_chain_caller.rs similarity index 100% rename from test_program_methods/guest/src/bin/clock_chain_caller.rs rename to test_programs/guest/src/bin/clock_chain_caller.rs diff --git a/test_program_methods/guest/src/bin/faucet_chain_caller.rs b/test_programs/guest/src/bin/faucet_chain_caller.rs similarity index 100% rename from test_program_methods/guest/src/bin/faucet_chain_caller.rs rename to test_programs/guest/src/bin/faucet_chain_caller.rs diff --git a/test_program_methods/guest/src/bin/pda_spend_proxy.rs b/test_programs/guest/src/bin/pda_spend_proxy.rs similarity index 100% rename from test_program_methods/guest/src/bin/pda_spend_proxy.rs rename to test_programs/guest/src/bin/pda_spend_proxy.rs diff --git a/test_program_methods/guest/src/bin/pinata_cooldown.rs b/test_programs/guest/src/bin/pinata_cooldown.rs similarity index 100% rename from test_program_methods/guest/src/bin/pinata_cooldown.rs rename to test_programs/guest/src/bin/pinata_cooldown.rs diff --git a/test_program_methods/guest/src/bin/time_locked_transfer.rs b/test_programs/guest/src/bin/time_locked_transfer.rs similarity index 100% rename from test_program_methods/guest/src/bin/time_locked_transfer.rs rename to test_programs/guest/src/bin/time_locked_transfer.rs diff --git a/test_programs/src/lib.rs b/test_programs/src/lib.rs new file mode 100644 index 00000000..c13a1028 --- /dev/null +++ b/test_programs/src/lib.rs @@ -0,0 +1,88 @@ +#![expect( + clippy::no_effect_underscore_binding, + reason = "This way we can remove warnings about unused path constants" +)] + +use std::borrow::Cow; + +use lee::program::Program; + +mod guests { + include!(concat!(env!("OUT_DIR"), "/methods.rs")); +} + +#[must_use] +#[inline] +pub const fn chain_caller() -> Program { + use guests::{CHAIN_CALLER_ELF, CHAIN_CALLER_ID, CHAIN_CALLER_PATH}; + + let _unused = CHAIN_CALLER_PATH; + + Program::new_unchecked(CHAIN_CALLER_ID, Cow::Borrowed(CHAIN_CALLER_ELF)) +} + +#[must_use] +#[inline] +pub const fn claimer() -> Program { + use guests::{CLAIMER_ELF, CLAIMER_ID, CLAIMER_PATH}; + + let _unused = CLAIMER_PATH; + + Program::new_unchecked(CLAIMER_ID, Cow::Borrowed(CLAIMER_ELF)) +} + +#[must_use] +#[inline] +pub const fn pda_spend_proxy() -> Program { + use guests::{PDA_SPEND_PROXY_ELF, PDA_SPEND_PROXY_ID, PDA_SPEND_PROXY_PATH}; + + let _unused = PDA_SPEND_PROXY_PATH; + + Program::new_unchecked(PDA_SPEND_PROXY_ID, Cow::Borrowed(PDA_SPEND_PROXY_ELF)) +} + +#[must_use] +#[inline] +pub const fn time_locked_transfer() -> Program { + use guests::{TIME_LOCKED_TRANSFER_ELF, TIME_LOCKED_TRANSFER_ID, TIME_LOCKED_TRANSFER_PATH}; + + let _unused = TIME_LOCKED_TRANSFER_PATH; + + Program::new_unchecked( + TIME_LOCKED_TRANSFER_ID, + Cow::Borrowed(TIME_LOCKED_TRANSFER_ELF), + ) +} + +#[must_use] +#[inline] +pub const fn pinata_cooldown() -> Program { + use guests::{PINATA_COOLDOWN_ELF, PINATA_COOLDOWN_ID, PINATA_COOLDOWN_PATH}; + + let _unused = PINATA_COOLDOWN_PATH; + + Program::new_unchecked(PINATA_COOLDOWN_ID, Cow::Borrowed(PINATA_COOLDOWN_ELF)) +} + +#[must_use] +#[inline] +pub const fn faucet_chain_caller() -> Program { + use guests::{FAUCET_CHAIN_CALLER_ELF, FAUCET_CHAIN_CALLER_ID, FAUCET_CHAIN_CALLER_PATH}; + + let _unused = FAUCET_CHAIN_CALLER_PATH; + + Program::new_unchecked( + FAUCET_CHAIN_CALLER_ID, + Cow::Borrowed(FAUCET_CHAIN_CALLER_ELF), + ) +} + +#[must_use] +#[inline] +pub const fn clock_chain_caller() -> Program { + use guests::{CLOCK_CHAIN_CALLER_ELF, CLOCK_CHAIN_CALLER_ID, CLOCK_CHAIN_CALLER_PATH}; + + let _unused = CLOCK_CHAIN_CALLER_PATH; + + Program::new_unchecked(CLOCK_CHAIN_CALLER_ID, Cow::Borrowed(CLOCK_CHAIN_CALLER_ELF)) +} diff --git a/tools/cycle_bench/Cargo.toml b/tools/cycle_bench/Cargo.toml index 03491c98..22d034df 100644 --- a/tools/cycle_bench/Cargo.toml +++ b/tools/cycle_bench/Cargo.toml @@ -20,7 +20,9 @@ authenticated_transfer_core.workspace = true clock_core.workspace = true token_core.workspace = true amm_core.workspace = true -ata_core.workspace = true +associated_token_account_core.workspace = true +programs.workspace = true +test_programs.workspace = true risc0-zkvm.workspace = true borsh.workspace = true diff --git a/tools/cycle_bench/benches/verify.rs b/tools/cycle_bench/benches/verify.rs index 648262f1..09d817f8 100644 --- a/tools/cycle_bench/benches/verify.rs +++ b/tools/cycle_bench/benches/verify.rs @@ -11,7 +11,7 @@ use std::{hint::black_box, time::Duration}; use anyhow::Context as _; use criterion::{Criterion, criterion_group, criterion_main}; use cycle_bench::ppe::prove_auth_transfer_in_ppe; -use lee::program_methods::PRIVACY_PRESERVING_CIRCUIT_ID; +use lee::PRIVACY_PRESERVING_CIRCUIT_ID; use risc0_zkvm::{InnerReceipt, Receipt}; fn bench_verify(c: &mut Criterion) { diff --git a/tools/cycle_bench/src/main.rs b/tools/cycle_bench/src/main.rs index bed61f92..5be5f367 100644 --- a/tools/cycle_bench/src/main.rs +++ b/tools/cycle_bench/src/main.rs @@ -24,18 +24,14 @@ use std::{path::PathBuf, time::Instant}; use amm_core::{PoolDefinition, compute_liquidity_token_pda, compute_pool_pda, compute_vault_pda}; use anyhow::Result; -use ata_core::{compute_ata_seed, get_associated_token_account_id}; +use associated_token_account_core::{compute_ata_seed, get_associated_token_account_id}; use clap::Parser; use clock_core::{ CLOCK_01_PROGRAM_ACCOUNT_ID, CLOCK_10_PROGRAM_ACCOUNT_ID, CLOCK_50_PROGRAM_ACCOUNT_ID, ClockAccountData, }; use cycle_bench::{ppe, stats::Stats}; -use lee::program_methods::{ - AMM_ELF, AMM_ID, ASSOCIATED_TOKEN_ACCOUNT_ELF, ASSOCIATED_TOKEN_ACCOUNT_ID, - AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, CLOCK_ELF, CLOCK_ID, TOKEN_ELF, - TOKEN_ID, -}; +use lee::program::Program; use lee_core::{ Timestamp, account::{Account, AccountId, AccountWithMetadata, Data}, @@ -66,7 +62,7 @@ struct Cli { #[derive(Debug, Serialize)] struct BenchResult { - program: &'static str, + program_name: &'static str, instruction: &'static str, user_cycles: u64, segments: usize, @@ -175,28 +171,25 @@ impl Calibration { } struct Case { - program: &'static str, + program_name: &'static str, instruction_label: &'static str, - elf: &'static [u8], - self_program_id: ProgramId, + program: Program, pre_states: Vec, instruction_words: InstructionData, } impl Case { fn new( - program: &'static str, + program_name: &'static str, instruction_label: &'static str, - elf: &'static [u8], - self_program_id: ProgramId, + program: Program, pre_states: Vec, instruction: &I, ) -> Result { Ok(Self { - program, + program_name, instruction_label, - elf, - self_program_id, + program, pre_states, instruction_words: risc0_zkvm::serde::to_vec(instruction)?, }) @@ -204,10 +197,9 @@ impl Case { fn run(self, prove: bool, exec_iters: usize) -> Result { let Self { - program, + program_name, instruction_label, - elf, - self_program_id, + program, pre_states, instruction_words, } = self; @@ -222,14 +214,14 @@ impl Case { for iter in 0..total { let mut env_builder = ExecutorEnv::builder(); env_builder - .write(&self_program_id)? + .write(&program.id())? .write(&caller_program_id)? .write(&pre_states)? .write(&instruction_words)?; let env = env_builder.build()?; let started = Instant::now(); - let info = default_executor().execute(env, elf)?; + let info = default_executor().execute(env, program.elf())?; let elapsed_ms = started.elapsed().as_secs_f64() * 1_000.0; if iter > 0 { @@ -248,7 +240,7 @@ impl Case { if prove { let mut env_builder = ExecutorEnv::builder(); env_builder - .write(&self_program_id)? + .write(&program.id())? .write(&caller_program_id)? .write(&pre_states)? .write(&instruction_words)?; @@ -256,7 +248,7 @@ impl Case { let started = Instant::now(); let prove_info = default_prover() - .prove(env, elf) + .prove(env, program.elf()) .map_err(|e| anyhow::anyhow!("prove failed: {e}"))?; let prove_ms = started.elapsed().as_secs_f64() * 1_000.0; prove_stats = Some(Stats::from_samples(&[prove_ms])); @@ -265,7 +257,7 @@ impl Case { prove_paging_cycles = Some(prove_info.stats.paging_cycles); prove_segments = Some(prove_info.stats.segments); eprintln!( - " prove({program}/{instruction_label}): {prove_ms:.1} ms ({:.1}s), total_cycles={}, segments={}", + " prove({program_name}/{instruction_label}): {prove_ms:.1} ms ({:.1}s), total_cycles={}, segments={}", prove_ms / 1_000.0, prove_info.stats.total_cycles, prove_info.stats.segments, @@ -273,7 +265,7 @@ impl Case { } Ok(BenchResult { - program, + program_name, instruction: instruction_label, user_cycles: info.cycles(), segments: info.segments.len(), @@ -322,7 +314,7 @@ fn token_holding( ) -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: TOKEN_ID, + program_owner: programs::token().id(), balance: 0, data: Data::from(&TokenHolding::Fungible { definition_id, @@ -342,7 +334,7 @@ fn token_definition( ) -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: TOKEN_ID, + program_owner: programs::token().id(), balance: 0, data: Data::from(&TokenDefinition::Fungible { name: String::from("test"), @@ -380,7 +372,7 @@ fn token_burn_pre_states() -> Vec { fn clock_account(account_id: AccountId, block_id: u64) -> AccountWithMetadata { AccountWithMetadata { account: Account { - program_owner: CLOCK_ID, + program_owner: programs::clock().id(), balance: 0, data: ClockAccountData { block_id, @@ -411,16 +403,20 @@ fn amm_token_b_def_id() -> AccountId { AccountId::new([43; 32]) } fn amm_pool_id() -> AccountId { - compute_pool_pda(AMM_ID, amm_token_a_def_id(), amm_token_b_def_id()) + compute_pool_pda( + programs::amm().id(), + amm_token_a_def_id(), + amm_token_b_def_id(), + ) } fn amm_vault_a_id() -> AccountId { - compute_vault_pda(AMM_ID, amm_pool_id(), amm_token_a_def_id()) + compute_vault_pda(programs::amm().id(), amm_pool_id(), amm_token_a_def_id()) } fn amm_vault_b_id() -> AccountId { - compute_vault_pda(AMM_ID, amm_pool_id(), amm_token_b_def_id()) + compute_vault_pda(programs::amm().id(), amm_pool_id(), amm_token_b_def_id()) } fn amm_lp_def_id() -> AccountId { - compute_liquidity_token_pda(AMM_ID, amm_pool_id()) + compute_liquidity_token_pda(programs::amm().id(), amm_pool_id()) } /// Pool seeded with reserves `1_000` / `500`, lp supply `sqrt(1000*500) = 707`. @@ -430,7 +426,7 @@ fn amm_pool_account() -> AccountWithMetadata { let lp_supply = (reserve_a * reserve_b).isqrt(); AccountWithMetadata { account: Account { - program_owner: AMM_ID, + program_owner: programs::amm().id(), balance: 0, data: Data::from(&PoolDefinition { definition_token_a_id: amm_token_a_def_id(), @@ -482,7 +478,7 @@ fn ata_create_pre_states() -> Vec { }; let token_def = token_definition(definition_id, 100_000, false); let seed = compute_ata_seed(owner_id, definition_id); - let ata_id = get_associated_token_account_id(&ASSOCIATED_TOKEN_ACCOUNT_ID, &seed); + let ata_id = get_associated_token_account_id(&programs::ata().id(), &seed); let ata_account = AccountWithMetadata { account: Account::default(), is_authorized: false, @@ -503,24 +499,21 @@ fn main() -> Result<()> { Case::new( "authenticated_transfer", "Transfer", - AUTHENTICATED_TRANSFER_ELF, - AUTHENTICATED_TRANSFER_ID, + programs::authenticated_transfer(), authenticated_transfer_transfer(), &authenticated_transfer_core::Instruction::Transfer { amount: 5_000 }, )?, Case::new( "authenticated_transfer", "Initialize", - AUTHENTICATED_TRANSFER_ELF, - AUTHENTICATED_TRANSFER_ID, + programs::authenticated_transfer(), authenticated_transfer_init(), &authenticated_transfer_core::Instruction::Initialize, )?, Case::new( "token", "Transfer", - TOKEN_ELF, - TOKEN_ID, + programs::token(), token_transfer_pre_states(), &token_core::Instruction::Transfer { amount_to_transfer: 5_000, @@ -529,8 +522,7 @@ fn main() -> Result<()> { Case::new( "token", "Mint", - TOKEN_ELF, - TOKEN_ID, + programs::token(), token_mint_pre_states(), &token_core::Instruction::Mint { amount_to_mint: 5_000, @@ -539,8 +531,7 @@ fn main() -> Result<()> { Case::new( "token", "Burn", - TOKEN_ELF, - TOKEN_ID, + programs::token(), token_burn_pre_states(), &token_core::Instruction::Burn { amount_to_burn: 500, @@ -549,16 +540,14 @@ fn main() -> Result<()> { Case::new( "clock", "Tick (block_id+1, no multiples)", - CLOCK_ELF, - CLOCK_ID, + programs::clock(), clock_pre_states_tick_at(0), &Timestamp::from(1_700_000_000_u64), )?, Case::new( "amm", "SwapExactInput", - AMM_ELF, - AMM_ID, + programs::amm(), amm_swap_pre_states(), &amm_core::Instruction::SwapExactInput { swap_amount_in: 200, @@ -569,8 +558,7 @@ fn main() -> Result<()> { Case::new( "amm", "AddLiquidity", - AMM_ELF, - AMM_ID, + programs::amm(), amm_add_liquidity_pre_states(), &amm_core::Instruction::AddLiquidity { min_amount_liquidity: 1, @@ -581,11 +569,10 @@ fn main() -> Result<()> { Case::new( "ata", "Create", - ASSOCIATED_TOKEN_ACCOUNT_ELF, - ASSOCIATED_TOKEN_ACCOUNT_ID, + programs::ata(), ata_create_pre_states(), - &ata_core::Instruction::Create { - ata_program_id: ASSOCIATED_TOKEN_ACCOUNT_ID, + &associated_token_account_core::Instruction::Create { + ata_program_id: programs::ata().id(), }, )?, ]; @@ -661,7 +648,7 @@ fn print_calibration(cal: &Calibration) { fn print_table(results: &[BenchResult], prove: bool) { let pw = results .iter() - .map(|r| r.program.len()) + .map(|r| r.program_name.len()) .max() .unwrap_or(0) .max("program".len()); @@ -701,7 +688,7 @@ fn print_table(results: &[BenchResult], prove: bool) { .map_or_else(|| "-".to_owned(), |v| format!("{v:.2}")); println!( "{:cw$} {:>sw$} {:dw$} {:>dw$}", - r.program, r.instruction, r.user_cycles, r.segments, r.exec_stats, calib, net, + r.program_name, r.instruction, r.user_cycles, r.segments, r.exec_stats, calib, net, ); } @@ -728,7 +715,7 @@ fn print_table(results: &[BenchResult], prove: bool) { .map_or_else(|| "-".to_owned(), |s| s.to_string()); println!( "{:pcw$} {:>pwallw$} {:>psw$}", - r.program, r.instruction, total, pms, psegs, + r.program_name, r.instruction, total, pms, psegs, ); } } @@ -744,7 +731,7 @@ mod tests { /// `user_cycles` (x) and `exec_stats.best_ms` (y). fn point(user_cycles: u64, best_ms: f64) -> BenchResult { BenchResult { - program: "test", + program_name: "test", instruction: "test", user_cycles, segments: 1, diff --git a/tools/cycle_bench/src/ppe/ppe_impl.rs b/tools/cycle_bench/src/ppe/ppe_impl.rs index 4d47bebc..e85c95f2 100644 --- a/tools/cycle_bench/src/ppe/ppe_impl.rs +++ b/tools/cycle_bench/src/ppe/ppe_impl.rs @@ -8,25 +8,15 @@ use std::{collections::HashMap, time::Instant}; use lee::{ execute_and_prove, privacy_preserving_transaction::circuit::{ProgramWithDependencies, Proof}, - program::Program, }; use lee_core::{ InputAccountIdentity, PrivacyPreservingCircuitOutput, account::{Account, AccountId, AccountWithMetadata}, - program::ProgramId, }; use risc0_zkvm::serde::to_vec; use super::PpeBenchResult; -const AUTH_TRANSFER_ID: ProgramId = lee::program_methods::AUTHENTICATED_TRANSFER_ID; -const AUTH_TRANSFER_ELF: &[u8] = lee::program_methods::AUTHENTICATED_TRANSFER_ELF; - -/// `chain_caller` bytecode shipped at `artifacts/test_program_methods/chain_caller.bin`. -/// Loaded at compile time so we don't need a dev-dependency on `test_program_methods`. -const CHAIN_CALLER_ELF: &[u8] = - include_bytes!("../../../../artifacts/test_program_methods/chain_caller.bin"); - pub fn run_auth_transfer_in_ppe() -> PpeBenchResult { let label = "auth_transfer Transfer in PPE".to_owned(); let started = Instant::now(); @@ -52,15 +42,16 @@ pub fn run_auth_transfer_in_ppe() -> PpeBenchResult { } pub fn prove_auth_transfer_in_ppe() -> anyhow::Result<(PrivacyPreservingCircuitOutput, Proof)> { - let program = Program::new(AUTH_TRANSFER_ELF.to_vec())?; - let pwd = ProgramWithDependencies::from(program); + let auth_transfer = programs::authenticated_transfer(); + let auth_transfer_id = auth_transfer.id(); + let pwd = ProgramWithDependencies::from(auth_transfer); // For PPE to allow the sender's balance to be decremented by this // program, the sender must already be claimed by auth_transfer. // Recipient stays default-owned so the first call can claim it. let sender = AccountWithMetadata { account: Account { - program_owner: AUTH_TRANSFER_ID, + program_owner: auth_transfer_id, balance: 1_000_000, ..Account::default() }, @@ -114,10 +105,11 @@ pub fn run_chain_caller(depth: u32) -> PpeBenchResult { fn prove_chain_caller( num_chain_calls: u32, ) -> anyhow::Result<(PrivacyPreservingCircuitOutput, Proof)> { - let chain_caller = Program::new(CHAIN_CALLER_ELF.to_vec())?; - let auth_transfer = Program::new(AUTH_TRANSFER_ELF.to_vec())?; + let chain_caller = test_programs::chain_caller(); + let auth_transfer = programs::authenticated_transfer(); + let auth_transfer_id = auth_transfer.id(); let mut deps = HashMap::new(); - deps.insert(AUTH_TRANSFER_ID, auth_transfer); + deps.insert(auth_transfer.id(), auth_transfer); let pwd = ProgramWithDependencies::new(chain_caller, deps); // Both accounts pre-claimed by auth_transfer. chain_caller doesn't @@ -125,7 +117,7 @@ fn prove_chain_caller( // would cause a state mismatch on subsequent chained calls. let recipient_pre = AccountWithMetadata { account: Account { - program_owner: AUTH_TRANSFER_ID, + program_owner: auth_transfer_id, ..Account::default() }, is_authorized: true, @@ -133,7 +125,7 @@ fn prove_chain_caller( }; let sender_pre = AccountWithMetadata { account: Account { - program_owner: AUTH_TRANSFER_ID, + program_owner: auth_transfer_id, balance: 1_000_000, ..Account::default() }, @@ -145,7 +137,7 @@ fn prove_chain_caller( let balance: u128 = 1; let pda_seed: Option = None; - let instruction = (balance, AUTH_TRANSFER_ID, num_chain_calls, pda_seed); + let instruction = (balance, auth_transfer_id, num_chain_calls, pda_seed); let instruction_data = to_vec(&instruction)?; let account_identities = vec![InputAccountIdentity::Public; pre_states.len()];