diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..0fbe460c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +# Build artifacts +target/ +**/target/ + +# RocksDB data +rocksdb/ +**/rocksdb/ + +# Git +.git/ +.gitignore + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# CI/CD +.github/ +ci_scripts/ + +# Documentation +*.md +!README.md + +# Configs (copy selectively if needed) +configs/ + +# License +LICENSE diff --git a/.github/actions/install-risc0/action.yml b/.github/actions/install-risc0/action.yml new file mode 100644 index 00000000..fef3a467 --- /dev/null +++ b/.github/actions/install-risc0/action.yml @@ -0,0 +1,10 @@ +name: Install risc0 +description: Installs risc0 in the environment +runs: + using: "composite" + steps: + - name: Install risc0 + run: | + curl -L https://risczero.com/install | bash + /home/runner/.risc0/bin/rzup install + shell: bash diff --git a/.github/actions/install-system-deps/action.yml b/.github/actions/install-system-deps/action.yml new file mode 100644 index 00000000..28c4b41c --- /dev/null +++ b/.github/actions/install-system-deps/action.yml @@ -0,0 +1,10 @@ +name: Install system dependencies +description: Installs system dependencies in the environment +runs: + using: "composite" + steps: + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential clang libclang-dev libssl-dev pkg-config + shell: bash diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c29c281f..18064c62 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,10 +10,10 @@ TO COMPLETE TO COMPLETE -[ ] Change ... -[ ] Add ... -[ ] Fix ... -[ ] ... +- [ ] Change ... +- [ ] Add ... +- [ ] Fix ... +- [ ] ... ## ๐Ÿงช How to Test @@ -37,7 +37,7 @@ TO COMPLETE IF APPLICABLE *Mark only completed items. A complete PR should have all boxes ticked.* -[ ] Complete PR description -[ ] Implement the core functionality -[ ] Add/update tests -[ ] Add/update documentation and inline comments +- [ ] Complete PR description +- [ ] Implement the core functionality +- [ ] Add/update tests +- [ ] Add/update documentation and inline comments diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ebfca72..38161a0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,22 +14,142 @@ on: name: General jobs: - ubuntu-latest-pipeline: + fmt-rs: runs-on: ubuntu-latest - timeout-minutes: 120 - - name: ubuntu-latest-pipeline steps: - - uses: actions/checkout@v3 - - - name: Install active toolchain - run: rustup install + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} - name: Install nightly toolchain for rustfmt run: rustup install nightly --profile minimal --component rustfmt - - name: lint - ubuntu-latest - run: chmod 777 ./ci_scripts/lint-ubuntu.sh && ./ci_scripts/lint-ubuntu.sh - - name: test ubuntu-latest - if: success() || failure() - run: chmod 777 ./ci_scripts/test-ubuntu.sh && ./ci_scripts/test-ubuntu.sh \ No newline at end of file + - name: Check Rust files are formatted + run: cargo +nightly fmt --check + + fmt-toml: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + + - name: Install taplo-cli + run: cargo install --locked taplo-cli + + - name: Check TOML files are formatted + run: taplo fmt --check . + + machete: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + + - name: Install active toolchain + run: rustup install + + - name: Install cargo-machete + run: cargo install cargo-machete + + - name: Check for unused dependencies + run: cargo machete + + lint: + runs-on: ubuntu-latest + timeout-minutes: 60 + + name: lint + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + + - uses: ./.github/actions/install-system-deps + + - uses: ./.github/actions/install-risc0 + + - name: Install active toolchain + run: rustup install + + - name: Lint workspace + env: + RISC0_SKIP_BUILD: "1" + run: cargo clippy --workspace --all-targets --all-features -- -D warnings + + - name: Lint programs + env: + RISC0_SKIP_BUILD: "1" + run: cargo clippy -p "*programs" -- -D warnings + + tests: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + + - uses: ./.github/actions/install-system-deps + + - uses: ./.github/actions/install-risc0 + + - name: Install active toolchain + run: rustup install + + - name: Install nextest + run: cargo install cargo-nextest + + - name: Run tests + env: + RISC0_DEV_MODE: "1" + RUST_LOG: "info" + run: cargo nextest run --no-fail-fast -- --skip tps_test + + valid-proof-test: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + + - uses: ./.github/actions/install-system-deps + + - uses: ./.github/actions/install-risc0 + + - name: Install active toolchain + run: rustup install + + - name: Test valid proof + env: + RUST_LOG: "info" + run: cargo test -p integration_tests -- --exact private::private_transfer_to_owned_account + + artifacts: + runs-on: ubuntu-latest + timeout-minutes: 60 + + name: artifacts + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + + - uses: ./.github/actions/install-risc0 + + - name: Install just + run: cargo install just + + - name: Build artifacts + run: just build-artifacts + + - name: Check if artifacts match repository + run: | + if ! git diff --exit-code artifacts/; then + echo "โŒ Artifacts in the repository are out of date!" + echo "Please run 'just build-artifacts' and commit the changes." + exit 1 + fi + echo "โœ… Artifacts are up to date" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..6dc622de --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,23 @@ +name: Deploy Sequencer + +on: + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Deploy to server + uses: appleboy/ssh-action@v1.2.4 + with: + host: ${{ secrets.DEPLOY_SSH_HOST }} + username: ${{ secrets.DEPLOY_SSH_USERNAME }} + key: ${{ secrets.DEPLOY_SSH_KEY }} + envs: GITHUB_ACTOR + script_path: ci_scripts/deploy.sh diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml new file mode 100644 index 00000000..7f070e31 --- /dev/null +++ b/.github/workflows/publish_image.yml @@ -0,0 +1,44 @@ +name: Publish Sequencer Runner Image + +on: + workflow_dispatch: + +jobs: + publish: + 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: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKER_REGISTRY }}/${{ github.repository }}/sequencer_runner + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./sequencer_runner/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 61e15369..6162763b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ data/ .idea/ .vscode/ rocksdb -Cargo.lock \ No newline at end of file +sequencer_runner/data/ +storage.json \ No newline at end of file diff --git a/nssa/program_methods/guest/Cargo.lock b/Cargo.lock similarity index 60% rename from nssa/program_methods/guest/Cargo.lock rename to Cargo.lock index 2c293ecb..4733c31d 100644 --- a/nssa/program_methods/guest/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,279 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.10.0", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-cors" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0346d8c1f762b41b458ed3145eea914966bb9ad20b9be0d6d463b20d45586370" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more 0.99.20", + "futures-util", + "log", + "once_cell", + "smallvec", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64 0.22.1", + "bitflags 2.10.0", + "bytes", + "bytestring", + "derive_more 2.1.0", + "encoding_rs", + "foldhash", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.111", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27e8fe9ba4ae613c21f677c2cfaf0696c3744030c6f485b34634e502d6bb379" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.7.8", + "bytes", + "bytestring", + "cfg-if", + "derive_more 0.99.20", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.4.10", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -16,9 +289,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -38,6 +311,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -62,7 +385,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-crypto-primitives-macros", "ark-ec", "ark-ff", @@ -86,7 +409,7 @@ checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -95,7 +418,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-ff", "ark-poly", "ark-serialize", @@ -137,7 +460,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -150,7 +473,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -174,7 +497,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-ff", "ark-serialize", "ark-std", @@ -233,7 +556,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -270,6 +593,28 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -282,6 +627,24 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -290,9 +653,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" [[package]] name = "bincode" @@ -303,12 +666,57 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.111", +] + +[[package]] +name = "bip39" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +dependencies = [ + "bitcoin_hashes", + "serde", + "unicode-normalization", +] + [[package]] name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -317,9 +725,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake2" @@ -353,16 +761,16 @@ checksum = "21055e2f49cbbdbfe9f8f96d597c5527b0c6ab7933341fdc2f147180e48a988e" dependencies = [ "duplicate", "maybe-async", - "reqwest", + "reqwest 0.12.26", "serde", "thiserror", ] [[package]] name = "borsh" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" dependencies = [ "borsh-derive", "cfg_aliases", @@ -370,22 +778,22 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" @@ -404,7 +812,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -415,18 +823,37 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] [[package]] -name = "camino" -version = "1.2.1" +name = "bytestring" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.13+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] @@ -456,19 +883,30 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] [[package]] -name = "cfg-if" -version = "1.0.3" +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -494,8 +932,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -509,6 +949,57 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + [[package]] name = "cobs" version = "0.3.0" @@ -518,12 +1009,64 @@ dependencies = [ "thiserror", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "borsh", + "hex", + "log", + "nssa", + "nssa_core", + "reqwest 0.11.27", + "serde", + "serde_json", + "sha2", + "thiserror", +] + +[[package]] +name = "console" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -561,15 +1104,52 @@ dependencies = [ ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.20.11" @@ -601,7 +1181,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -615,7 +1195,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -626,7 +1206,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -637,7 +1217,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -653,9 +1233,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -690,7 +1270,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -700,27 +1280,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "derive_more" -version = "2.0.1" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.111", +] + +[[package]] +name = "derive_more" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ + "convert_case 0.10.0", "proc-macro2", "quote", - "syn 2.0.106", + "rustc_version", + "syn 2.0.111", "unicode-xid", ] @@ -765,7 +1360,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -782,9 +1377,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "duplicate" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" +checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24" dependencies = [ "heck", "proc-macro2", @@ -797,6 +1392,21 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + [[package]] name = "educe" version = "0.6.0" @@ -806,7 +1416,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -821,6 +1431,27 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + [[package]] name = "embedded-io" version = "0.4.0" @@ -833,6 +1464,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -844,22 +1481,35 @@ dependencies = [ [[package]] name = "enum-ordinalize" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", ] [[package]] @@ -878,6 +1528,23 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "example_program_deployment_methods" +version = "0.1.0" +dependencies = [ + "risc0-build", +] + +[[package]] +name = "example_program_deployment_programs" +version = "0.1.0" +dependencies = [ + "bytemuck", + "hex", + "nssa_core", + "risc0-zkvm", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -885,10 +1552,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "find-msvc-tools" -version = "0.1.4" +name = "ff" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "fnv" @@ -902,6 +1579,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -909,7 +1595,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -920,9 +1606,15 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -938,6 +1630,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -954,6 +1661,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -968,7 +1686,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -989,6 +1707,7 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1002,12 +1721,13 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1019,24 +1739,70 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.12.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1055,9 +1821,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hashlink" @@ -1074,12 +1840,27 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + [[package]] name = "hex-literal" version = "0.4.1" @@ -1087,16 +1868,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] -name = "http" -version = "1.3.1" +name = "hex-literal" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", "itoa", ] +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1104,7 +1927,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1115,8 +1938,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1127,17 +1950,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] -name = "hyper" -version = "1.7.0" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "httparse", "itoa", "pin-project-lite", @@ -1153,8 +2012,8 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", - "hyper", + "http 1.4.0", + "hyper 1.8.1", "hyper-util", "rustls", "rustls-pki-types", @@ -1165,24 +2024,37 @@ dependencies = [ ] [[package]] -name = "hyper-util" -version = "0.1.17" +name = "hyper-tls" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "base64", + "bytes", + "hyper 0.14.32", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http", - "http-body", - "hyper", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -1214,9 +2086,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1227,9 +2099,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1240,11 +2112,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1255,42 +2126,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1344,16 +2211,30 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] +[[package]] +name = "indicatif" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88" +dependencies = [ + "console", + "portable-atomic", + "unicode-segmentation", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "inout" version = "0.1.4" @@ -1363,6 +2244,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "integration_tests" +version = "0.1.0" +dependencies = [ + "actix-web", + "anyhow", + "base64 0.22.1", + "borsh", + "common", + "env_logger", + "futures", + "hex", + "key_protocol", + "log", + "nssa", + "nssa_core", + "sequencer_core", + "sequencer_runner", + "tempfile", + "tokio", + "wallet", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1371,14 +2275,31 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -1404,15 +2325,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "js-sys" -version = "0.3.81" +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", + "signature", +] + [[package]] name = "keccak" version = "0.1.5" @@ -1423,10 +2369,37 @@ dependencies = [ ] [[package]] -name = "lazy-regex" -version = "3.4.1" +name = "key_protocol" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "anyhow", + "base58", + "bip39", + "common", + "hex", + "hmac-sha512", + "itertools 0.14.0", + "k256", + "nssa", + "nssa_core", + "rand 0.8.5", + "serde", + "sha2", + "thiserror", +] + +[[package]] +name = "language-tags" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy-regex" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -1435,14 +2408,14 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1456,9 +2429,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] [[package]] name = "libm" @@ -1468,14 +2451,38 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "libc", ] +[[package]] +name = "librocksdb-sys" +version = "0.17.3+10.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "libc", + "libz-sys", +] + +[[package]] +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1484,15 +2491,41 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru-slab" @@ -1517,7 +2550,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1526,6 +2559,13 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "mempool" +version = "0.1.0" +dependencies = [ + "tokio", +] + [[package]] name = "merlin" version = "3.0.0" @@ -1544,24 +2584,54 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "block", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", ] [[package]] -name = "mio" -version = "1.0.4" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1571,13 +2641,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e" [[package]] -name = "nssa-core" +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nssa" version = "0.1.0" dependencies = [ "borsh", + "bytemuck", + "env_logger", + "hex", + "hex-literal 1.1.0", + "log", + "nssa_core", + "rand 0.8.5", + "risc0-binfmt", + "risc0-build", + "risc0-zkvm", + "secp256k1", + "serde", + "sha2", + "test-case", + "test_program_methods", + "thiserror", +] + +[[package]] +name = "nssa_core" +version = "0.1.0" +dependencies = [ + "anyhow", + "base58", + "borsh", + "bytemuck", "chacha20", + "k256", "risc0-zkvm", "serde", + "serde_json", "thiserror", ] @@ -1593,11 +2701,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -1646,9 +2753,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -1656,13 +2763,13 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1680,12 +2787,102 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "optfield" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969ccca8ffc4fb105bd131a228107d5c9dd89d9d627edf3295cbe979156f9712" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "paste" version = "1.0.15" @@ -1740,6 +2937,30 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "postcard" version = "1.1.3" @@ -1754,9 +2975,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -1782,14 +3003,14 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.7", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1802,27 +3023,44 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", "version_check", - "yansi", +] + +[[package]] +name = "program_deployment" +version = "0.1.0" +dependencies = [ + "clap", + "nssa", + "nssa_core", + "tokio", + "wallet", +] + +[[package]] +name = "program_methods" +version = "0.1.0" +dependencies = [ + "risc0-build", ] [[package]] name = "programs" version = "0.1.0" dependencies = [ - "nssa-core", + "nssa_core", "risc0-zkvm", "serde", ] [[package]] name = "proptest" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -1850,7 +3088,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1866,7 +3104,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.1", "thiserror", "tokio", "tracing", @@ -1880,7 +3118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", @@ -1903,16 +3141,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.1", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -1929,6 +3167,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -1978,7 +3217,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -1990,6 +3229,15 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -2018,7 +3266,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -2044,6 +3292,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + [[package]] name = "regex-syntax" version = "0.8.8" @@ -2052,19 +3306,59 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64", + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-rustls", "hyper-util", "js-sys", @@ -2077,7 +3371,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-rustls", "tokio-util", @@ -2092,6 +3386,16 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2108,14 +3412,14 @@ dependencies = [ [[package]] name = "risc0-binfmt" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c8f97f81bcdead4101bca06469ecef481a2695cd04e7e877b49dea56a7f6f2a" +checksum = "9dca096030bb4c52f99b12abcfe3531ea93b17b95a12a5aeb06fbf8ee588a275" dependencies = [ "anyhow", "borsh", "bytemuck", - "derive_more", + "derive_more 2.1.0", "elf", "lazy_static", "postcard", @@ -2130,9 +3434,9 @@ dependencies = [ [[package]] name = "risc0-build" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bbb512d728e011d03ce0958ca7954624ee13a215bcafd859623b3c63b2a3f60" +checksum = "e744682b661f2a022fddffddfe242608f8b194e839d9e83ddbb3378974942241" dependencies = [ "anyhow", "cargo_metadata", @@ -2154,9 +3458,9 @@ dependencies = [ [[package]] name = "risc0-circuit-keccak" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f195f865ac1afdc21a172d7756fdcc21be18e13eb01d78d3d7f2b128fa881ba" +checksum = "4e1d23ef3648bb85b0bd37bc9f9f7d13f1a4388e5e779e18f7eea82b969e5dbc" dependencies = [ "anyhow", "bytemuck", @@ -2170,9 +3474,9 @@ dependencies = [ [[package]] name = "risc0-circuit-recursion" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca8f15c8abc0fd8c097aa7459879110334d191c63dd51d4c28881c4a497279e" +checksum = "028cd26e1b1f7bdd964d2f1eac8f812d1872b6b8fd24f10804f07d916b90000e" dependencies = [ "anyhow", "bytemuck", @@ -2185,14 +3489,14 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1b0689f4a270a2f247b04397ebb431b8f64fe5170e98ee4f9d71bd04825205" +checksum = "e7ecd73a71ddce62eab8a28552ee182dc2ea08cdce2a3474a616a80bf2d6e9be" dependencies = [ "anyhow", "bit-vec", "bytemuck", - "derive_more", + "derive_more 2.1.0", "paste", "risc0-binfmt", "risc0-core", @@ -2213,9 +3517,9 @@ dependencies = [ [[package]] name = "risc0-groth16" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724285dc79604abfb2d40feaefe3e335420a6b293511661f77d6af62f1f5fae9" +checksum = "73ff13f9b427254c5264e01aaa32e33f355525299b6829449295905778f3b1e8" dependencies = [ "anyhow", "ark-bn254", @@ -2234,9 +3538,9 @@ dependencies = [ [[package]] name = "risc0-zkos-v1compat" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840c2228803557a8b7dc035a8f196516b6fd68c9dc6ac092f0c86241b5b1bafb" +checksum = "faf1f35f2ef61d8d86fdd06288c11d2f3bbf08f1af66b24ca0a1976ecbf324a1" dependencies = [ "include_bytes_aligned", "no_std_strings", @@ -2245,9 +3549,9 @@ dependencies = [ [[package]] name = "risc0-zkp" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb6bf356f469bb8744f72a07a37134c5812c1d55d6271bba80e87bdb7a58c8e" +checksum = "beb493b3f007f04a11106a001c66bca77338d0fc375766189fd7ca3a1e8c3700" dependencies = [ "anyhow", "blake2", @@ -2256,7 +3560,7 @@ dependencies = [ "cfg-if", "digest", "hex", - "hex-literal", + "hex-literal 0.4.1", "metal", "paste", "rand_core 0.9.3", @@ -2270,9 +3574,9 @@ dependencies = [ [[package]] name = "risc0-zkvm" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcce11648a9ff60b8e7af2f0ce7fbf8d25275ab6d414cc91b9da69ee75bc978" +checksum = "c39d9943fe71decea1e8b6a99480cefa33799ab08b5abfccd7e2a18fb07121c1" dependencies = [ "anyhow", "bincode", @@ -2280,7 +3584,7 @@ dependencies = [ "borsh", "bytemuck", "bytes", - "derive_more", + "derive_more 2.1.0", "hex", "lazy-regex", "prost", @@ -2313,13 +3617,23 @@ dependencies = [ "bytemuck", "cfg-if", "getrandom 0.2.16", - "getrandom 0.3.3", + "getrandom 0.3.4", "libm", "num_enum", "paste", "stability", ] +[[package]] +name = "rocksdb" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb7af00d2b17dbd07d82c0063e25411959748ff03e8d4f96134c2ff41fce34f" +dependencies = [ + "libc", + "librocksdb-sys", +] + [[package]] name = "rrs-lib" version = "0.1.0" @@ -2332,9 +3646,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" dependencies = [ "const-oid", "digest", @@ -2378,13 +3692,22 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -2393,9 +3716,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.32" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "once_cell", "ring", @@ -2406,10 +3729,19 @@ dependencies = [ ] [[package]] -name = "rustls-pki-types" -version = "1.12.0" +name = "rustls-pemfile" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -2417,9 +3749,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -2457,6 +3789,15 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.9.0" @@ -2471,9 +3812,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "dyn-clone", "ref-cast", @@ -2481,6 +3822,70 @@ dependencies = [ "serde_json", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.2", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -2491,6 +3896,66 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sequencer_core" +version = "0.1.0" +dependencies = [ + "anyhow", + "base58", + "chrono", + "common", + "futures", + "log", + "mempool", + "nssa", + "nssa_core", + "serde", + "serde_json", + "storage", + "tempfile", + "tokio", +] + +[[package]] +name = "sequencer_rpc" +version = "0.1.0" +dependencies = [ + "actix-cors", + "actix-web", + "anyhow", + "base58", + "base64 0.22.1", + "borsh", + "common", + "futures", + "hex", + "itertools 0.14.0", + "log", + "mempool", + "nssa", + "sequencer_core", + "serde", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "sequencer_runner" +version = "0.1.0" +dependencies = [ + "actix", + "actix-web", + "anyhow", + "clap", + "common", + "env_logger", + "log", + "sequencer_core", + "sequencer_rpc", + "tokio", +] + [[package]] name = "serde" version = "1.0.228" @@ -2518,7 +3983,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -2557,17 +4022,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ - "base64", + "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", + "indexmap 2.12.1", "schemars 0.9.0", - "schemars 1.0.4", + "schemars 1.1.0", "serde_core", "serde_json", "serde_with_macros", @@ -2576,14 +4041,35 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", ] [[package]] @@ -2603,6 +4089,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -2625,6 +4120,26 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.1" @@ -2658,7 +4173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -2667,6 +4182,16 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "storage" +version = "0.1.0" +dependencies = [ + "borsh", + "common", + "rocksdb", + "thiserror", +] + [[package]] name = "strsim" version = "0.11.1" @@ -2691,7 +4216,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -2713,15 +4238,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -2739,7 +4270,28 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -2749,12 +4301,69 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "test-case-core", +] + +[[package]] +name = "test_program_methods" +version = "0.1.0" +dependencies = [ + "risc0-build", +] + +[[package]] +name = "test_programs" +version = "0.1.0" +dependencies = [ + "nssa_core", + "risc0-zkvm", +] + [[package]] name = "thiserror" version = "2.0.17" @@ -2772,7 +4381,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -2808,9 +4417,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -2840,11 +4449,35 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", - "socket2", + "signal-hook-registry", + "socket2 0.6.1", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -2857,9 +4490,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -2891,9 +4524,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.4+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "fe3cea6b2aa3b910092f6abd4053ea464fab5f9c170ba5e9a6aead16ec4af2b6" dependencies = [ "serde_core", ] @@ -2904,7 +4537,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.1", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -2914,21 +4547,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.7.3", + "indexmap 2.12.1", + "toml_datetime 0.7.4+spec-1.0.0", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.5+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "4c03bee5ce3696f31250db0bbaff18bc43301ce0e8db2ed1f07cbb2acf89984c" dependencies = [ "winnow", ] @@ -2948,7 +4581,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -2956,15 +4589,15 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "bytes", "futures-util", - "http", - "http-body", + "http 1.4.0", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower", @@ -2986,9 +4619,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", @@ -2998,20 +4631,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" dependencies = [ "once_cell", "valuable", @@ -3046,9 +4679,30 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -3056,6 +4710,22 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -3080,18 +4750,60 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wallet" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-stream", + "base58", + "base64 0.22.1", + "borsh", + "bytemuck", + "clap", + "common", + "env_logger", + "futures", + "hex", + "indicatif", + "itertools 0.14.0", + "key_protocol", + "log", + "nssa", + "nssa_core", + "optfield", + "rand 0.8.5", + "risc0-zkvm", + "serde", + "serde_json", + "sha2", + "tokio", +] + [[package]] name = "want" version = "0.3.1" @@ -3107,15 +4819,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -3127,9 +4830,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -3138,25 +4841,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -3167,9 +4856,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3177,22 +4866,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -3212,9 +4901,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -3232,13 +4921,44 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -3260,7 +4980,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -3271,7 +4991,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -3300,18 +5020,18 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] @@ -3334,6 +5054,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -3367,6 +5102,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3379,6 +5120,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -3391,6 +5138,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3415,6 +5168,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -3427,6 +5186,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -3439,6 +5204,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -3451,6 +5222,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3465,13 +5242,23 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen" version = "0.46.0" @@ -3480,9 +5267,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yaml-rust2" @@ -3495,19 +5282,12 @@ dependencies = [ "hashlink", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -3515,34 +5295,34 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -3562,7 +5342,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", "synstructure", ] @@ -3583,14 +5363,14 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -3599,9 +5379,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -3610,11 +5390,11 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] diff --git a/Cargo.toml b/Cargo.toml index dd24d98c..ef1b881d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,15 +12,41 @@ members = [ "common", "nssa", "nssa/core", - "integration_tests/proc_macro_test_attribute", + "program_methods", + "program_methods/guest", + "test_program_methods", + "test_program_methods/guest", "examples/program_deployment", + "examples/program_deployment/methods", + "examples/program_deployment/methods/guest", ] [workspace.dependencies] +nssa = { path = "nssa" } +nssa_core = { path = "nssa/core" } +common = { path = "common" } +mempool = { path = "mempool" } +storage = { path = "storage" } +key_protocol = { path = "key_protocol" } +sequencer_core = { path = "sequencer_core" } +sequencer_rpc = { path = "sequencer_rpc" } +sequencer_runner = { path = "sequencer_runner" } +wallet = { path = "wallet" } +test_program_methods = { path = "test_program_methods" } + +tokio = { version = "1.28.2", features = [ + "net", + "rt-multi-thread", + "sync", + "fs", +] } +risc0-zkvm = { version = "3.0.3", features = ['std'] } +risc0-build = "3.0.3" anyhow = "1.0.98" num_cpus = "1.13.1" openssl = { version = "0.10", features = ["vendored"] } openssl-probe = { version = "0.1.2" } +serde = { version = "1.0.60", default-features = false, features = ["derive"] } serde_json = "1.0.81" actix = "0.13.0" actix-cors = "0.6.1" @@ -33,9 +59,9 @@ lru = "0.7.8" thiserror = "2.0.12" sha2 = "0.10.8" hex = "0.4.3" +bytemuck = "1.24.0" aes-gcm = "0.10.3" toml = "0.7.4" -secp256k1-zkp = "0.11.0" bincode = "1.3.3" tempfile = "3.14.0" light-poseidon = "0.3.0" @@ -50,46 +76,21 @@ borsh = "1.5.7" base58 = "0.2.0" itertools = "0.14.0" -rocksdb = { version = "0.21.0", default-features = false, features = [ +rocksdb = { version = "0.24.0", default-features = false, features = [ "snappy", + "bindgen-runtime", ] } - -[workspace.dependencies.rand] -features = ["std", "std_rng", "getrandom"] -version = "0.8.5" - -[workspace.dependencies.k256] -features = ["ecdsa-core", "arithmetic", "expose-field", "serde", "pem"] -version = "0.13.3" - -[workspace.dependencies.elliptic-curve] -features = ["arithmetic"] -version = "0.13.8" - -[workspace.dependencies.serde] -features = ["derive"] -version = "1.0.60" - -[workspace.dependencies.actix-web] -default-features = false -features = ["macros"] -version = "=4.1.0" - -[workspace.dependencies.clap] -features = ["derive", "env"] -version = "4.5.42" - -[workspace.dependencies.tokio-retry] -version = "0.3.0" - -[workspace.dependencies.reqwest] -features = ["json"] -version = "0.11.16" - -[workspace.dependencies.tokio] -features = ["net", "rt-multi-thread", "sync", "fs"] -version = "1.28.2" - -[workspace.dependencies.tracing] -features = ["std"] -version = "0.1.13" +rand = { version = "0.8.5", features = ["std", "std_rng", "getrandom"] } +k256 = { version = "0.13.3", features = [ + "ecdsa-core", + "arithmetic", + "expose-field", + "serde", + "pem", +] } +elliptic-curve = { version = "0.13.8", features = ["arithmetic"] } +actix-web = { version = "=4.1.0", default-features = false, features = [ + "macros", +] } +clap = { version = "4.5.42", features = ["derive", "env"] } +reqwest = { version = "0.11.16", features = ["json"] } diff --git a/Justfile b/Justfile new file mode 100644 index 00000000..89be1097 --- /dev/null +++ b/Justfile @@ -0,0 +1,19 @@ +set shell := ["bash", "-eu", "-o", "pipefail", "-c"] + +default: + @just --list + +# ---- Configuration ---- +METHODS_PATH := "program_methods" +TEST_METHODS_PATH := "test_program_methods" +ARTIFACTS := "artifacts" + +# ---- Artifacts build ---- +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 diff --git a/README.md b/README.md index ff596b8c..f7885be0 100644 --- a/README.md +++ b/README.md @@ -69,16 +69,14 @@ Install build dependencies - On Linux Ubuntu / Debian ```sh -apt install build-essential clang libssl-dev pkg-config +apt install build-essential clang libclang-dev libssl-dev pkg-config ``` Fedora ```sh -sudo dnf install clang openssl-devel pkgconf llvm +sudo dnf install clang clang-devel openssl-devel pkgconf ``` -> **Note for Fedora 41+ users:** GCC 14+ has stricter C++ standard library headers that cause build failures with the bundled RocksDB. You must set `CXXFLAGS="-include cstdint"` when running cargo commands. See the [Run tests](#run-tests) section for examples. - - On Mac ```sh xcode-select --install @@ -110,9 +108,6 @@ The NSSA repository includes both unit and integration test suites. ```bash # RISC0_DEV_MODE=1 is used to skip proof generation and reduce test runtime overhead RISC0_DEV_MODE=1 cargo test --release - -# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build: -CXXFLAGS="-include cstdint" RISC0_DEV_MODE=1 cargo test --release ``` ### Integration tests @@ -122,9 +117,6 @@ export NSSA_WALLET_HOME_DIR=$(pwd)/integration_tests/configs/debug/wallet/ cd integration_tests # RISC0_DEV_MODE=1 skips proof generation; RUST_LOG=info enables runtime logs RUST_LOG=info RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all - -# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build: -CXXFLAGS="-include cstdint" RUST_LOG=info RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all ``` # Run the sequencer @@ -134,9 +126,6 @@ The sequencer can be run locally: ```bash cd sequencer_runner RUST_LOG=info cargo run --release configs/debug - -# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build: -CXXFLAGS="-include cstdint" RUST_LOG=info cargo run --release configs/debug ``` If everything went well you should see an output similar to this: @@ -162,13 +151,12 @@ This repository includes a CLI for interacting with the Nescience sequencer. To ```bash cargo install --path wallet --force - -# On Fedora 41+ (GCC 14+), prefix with CXXFLAGS to fix RocksDB build: -CXXFLAGS="-include cstdint" cargo install --path wallet --force ``` Run `wallet help` to check everything went well. +Some completion scripts exists, see the [completions](./completions/README.md) folder. + ## Tutorial This tutorial walks you through creating accounts and executing NSSA programs in both public and private contexts. @@ -204,6 +192,7 @@ Commands: account Account view and sync subcommand pinata Pinata program interaction subcommand token Token program interaction subcommand + amm AMM program interaction subcommand check-health Check the wallet can connect to the node and builtin local programs match the remote versions ``` @@ -618,13 +607,13 @@ wallet account new public Generated new account with account_id Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 ``` -Let's send 10 B tokens to this new account. We'll debit this from the supply account used in the creation of the token. +Let's send 1000 B tokens to this new account. We'll debit this from the supply account used in the creation of the token. ```bash wallet token send \ --from Private/HMRHZdPw4pbyPVZHNGrV6K5AA95wACFsHTRST84fr3CF \ --to Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \ - --amount 10 + --amount 1000 ``` Let's inspect the public account: @@ -634,7 +623,7 @@ wallet account get --account-id Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6 # Output: Holding account owned by token program -{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":10} +{"account_type":"Token holding","definition_id":"GQ3C8rbprTtQUCvkuVBRu3v9wvUvjafCMFqoSPvTEVii","balance":1000} ``` ### Chain information @@ -658,3 +647,107 @@ Last block id is 65537 ``` +### Automated Market Maker (AMM) + +NSSA includes an AMM program that manages liquidity pools and enables swaps between custom tokens. To test this functionality, we first need to create a liquidity pool. + +#### Creating a liquidity pool for a token pair + +We start by creating a new pool for the tokens previously created. In return for providing liquidity, we will receive liquidity provider (LP) tokens, which represent our share of the pool and are required to withdraw liquidity later. + +>[!NOTE] +> The AMM program does not currently charge swap fees or distribute rewards to liquidity providers. LP tokens therefore only represent a proportional share of the pool reserves and do not provide additional value from swap activity. Fee support for liquidity providers will be added in future versions of the AMM program. + +To hold these LP tokens, we first create a new account: + +```bash +wallet account new public + +# Output: +Generated new account with account_id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf +``` + +Next, we initialize the liquidity pool by depositing tokens A and B and specifying the account that will receive the LP tokens: + +```bash +wallet amm new \ + --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \ + --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \ + --user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \ + --balance-a 100 \ + --balance-b 200 +``` + +The newly created account is owned by the token program, meaning that LP tokens are managed by the same token infrastructure as regular tokens. + +```bash +wallet account get --account-id Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf + +# Output: +Holding account owned by token program +{"account_type":"Token holding","definition_id":"7BeDS3e28MA5Err7gBswmR1fUKdHXqmUpTefNPu3pJ9i","balance":100} +``` + +If you inspect the `user-holding-a` and `user-holding-b` accounts passed to the `wallet amm new` command, you will see that 100 and 200 tokens were deducted, respectively. These tokens now reside in the liquidity pool and are available for swaps by any user. + + +#### Swaping + +Token swaps can be performed using the wallet amm swap command: + +```bash +wallet amm swap \ + --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \ + --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \ + # The amount of tokens to swap + --amount-in 5 \ + # The minimum number of tokens expected in return + --min-amount-out 8 \ + # The definition ID of the token being provided to the swap + # In this case, we are swapping from TOKENA to TOKENB, and so this is the definition ID of TOKENA + --token-definition 4X9kAcnCZ1Ukkbm3nywW9xfCNPK8XaMWCk3zfs1sP4J7 +``` + +Once executed, 5 tokens are deducted from the Token A holding account and the corresponding amount (determined by the poolโ€™s pricing function) is credited to the Token B holding account. + + +#### Withdrawing liquidity from the pool + +Liquidity providers can withdraw assets from the pool by redeeming (burning) LP tokens. The amount of tokens received is proportional to the share of LP tokens being redeemed relative to the total LP supply. + +This operation is performed using the `wallet amm remove-liquidity` command: + +```bash +wallet amm remove-liquidity \ + --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \ + --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \ + --user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \ + --balance-lp 20 \ + --min-amount-a 1 \ + --min-amount-b 1 +``` + +This instruction burns `balance-lp` LP tokens from the userโ€™s LP holding account. In exchange, the AMM transfers tokens A and B from the poolโ€™s vault accounts to the userโ€™s holding accounts, according to the current pool reserves. + +The `min-amount-a` and `min-amount-b` parameters specify the minimum acceptable amounts of tokens A and B to be received. If the computed outputs fall below either threshold, the instruction fails, protecting the user against unfavorable pool state changes. + +#### Adding liquidity to the pool + +Additional liquidity can be added to an existing pool by depositing tokens A and B in the ratio implied by the current pool reserves. In return, new LP tokens are minted to represent the userโ€™s proportional share of the pool. + +This is done using the `wallet amm add-liquidity` command: + +```bash +wallet amm add-liquidity \ + --user-holding-a Public/9RRSMm3w99uCD2Jp2Mqqf6dfc8me2tkFRE9HeU2DFftw \ + --user-holding-b Public/88f2zeTgiv9LUthQwPJbrmufb9SiDfmpCs47B7vw6Gd6 \ + --user-holding-lp Public/FHgLW9jW4HXMV6egLWbwpTqVAGiCHw2vkg71KYSuimVf \ + --min-amount-lp 1 \ + --max-amount-a 10 \ + --max-amount-b 10 +``` + +In this instruction, `max-amount-a` and `max-amount-b` define upper bounds on the number of tokens A and B that may be withdrawn from the userโ€™s accounts. The AMM computes the actual required amounts based on the poolโ€™s reserve ratio. + +The `min-amount-lp` parameter specifies the minimum number of LP tokens that must be minted for the transaction to succeed. If the resulting LP token amount is below this threshold, the instruction fails. + diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin new file mode 100644 index 00000000..1a2fabca Binary files /dev/null and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin new file mode 100644 index 00000000..73d5fec1 Binary files /dev/null and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin new file mode 100644 index 00000000..278e88f4 Binary files /dev/null and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin new file mode 100644 index 00000000..4a4c8bb6 Binary files /dev/null and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin new file mode 100644 index 00000000..d8f5915e Binary files /dev/null and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin new file mode 100644 index 00000000..5f6d3781 Binary files /dev/null and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin new file mode 100644 index 00000000..96e339c2 Binary files /dev/null and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin new file mode 100644 index 00000000..731d2dc7 Binary files /dev/null and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin new file mode 100644 index 00000000..692d152b Binary files /dev/null and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin new file mode 100644 index 00000000..da0a9bef Binary files /dev/null and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin new file mode 100644 index 00000000..86fde894 Binary files /dev/null and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin new file mode 100644 index 00000000..1ae1cd98 Binary files /dev/null and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin new file mode 100644 index 00000000..8f80ab58 Binary files /dev/null and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin new file mode 100644 index 00000000..50199403 Binary files /dev/null and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin new file mode 100644 index 00000000..4994ae3f Binary files /dev/null and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin new file mode 100644 index 00000000..796de2d3 Binary files /dev/null and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin new file mode 100644 index 00000000..017de8b3 Binary files /dev/null and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin new file mode 100644 index 00000000..23195ef2 Binary files /dev/null and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin new file mode 100644 index 00000000..db1f87fa Binary files /dev/null and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin new file mode 100644 index 00000000..17c95475 Binary files /dev/null and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/ci_scripts/build-macos.sh b/ci_scripts/build-macos.sh deleted file mode 100644 index 3d39af01..00000000 --- a/ci_scripts/build-macos.sh +++ /dev/null @@ -1,4 +0,0 @@ -set -e -curl -L https://risczero.com/install | bash -/Users/runner/.risc0/bin/rzup install -RUSTFLAGS="-D warnings" cargo build diff --git a/ci_scripts/build-ubuntu.sh b/ci_scripts/build-ubuntu.sh deleted file mode 100644 index 0da057d2..00000000 --- a/ci_scripts/build-ubuntu.sh +++ /dev/null @@ -1,4 +0,0 @@ -set -e -curl -L https://risczero.com/install | bash -/home/runner/.risc0/bin/rzup install -RUSTFLAGS="-D warnings" cargo build diff --git a/ci_scripts/deploy.sh b/ci_scripts/deploy.sh new file mode 100644 index 00000000..7615df03 --- /dev/null +++ b/ci_scripts/deploy.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -e + +# Base directory for deployment +LSSA_DIR="/home/arjentix/test_deploy/lssa" + +# Expect GITHUB_ACTOR to be passed as first argument or environment variable +GITHUB_ACTOR="${1:-${GITHUB_ACTOR:-unknown}}" + +# Function to log messages with timestamp +log_deploy() { + echo "[$(date '+%Y-%m-%d %H:%M:%S %Z')] $1" >> "${LSSA_DIR}/deploy.log" +} + +# Error handler +handle_error() { + echo "โœ— Deployment failed by: ${GITHUB_ACTOR}" + log_deploy "Deployment failed by: ${GITHUB_ACTOR}" + exit 1 +} + +find_sequencer_runner_pids() { + pgrep -f "sequencer_runner" | grep -v $$ +} + +# Set trap to catch any errors +trap 'handle_error' ERR + +# Log deployment info +log_deploy "Deployment initiated by: ${GITHUB_ACTOR}" + +# Navigate to code directory +if [ ! -d "${LSSA_DIR}/code" ]; then + mkdir -p "${LSSA_DIR}/code" +fi +cd "${LSSA_DIR}/code" + +# Stop current sequencer if running +if find_sequencer_runner_pids > /dev/null; then + echo "Stopping current sequencer..." + find_sequencer_runner_pids | xargs -r kill -SIGINT || true + sleep 2 + # Force kill if still running + find_sequencer_runner_pids | grep -v $$ | xargs -r kill -9 || true +fi + +# Clone or update repository +if [ -d ".git" ]; then + echo "Updating existing repository..." + git fetch origin + git checkout main + git reset --hard origin/main +else + echo "Cloning repository..." + git clone https://github.com/vacp2p/nescience-testnet.git . + git checkout main +fi + +# Build sequencer_runner and wallet in release mode +echo "Building sequencer_runner" +# That could be just `cargo build --release --bin sequencer_runner --bin wallet` +# but we have `no_docker` feature bug, see issue #179 +cd sequencer_runner +cargo build --release +cd ../wallet +cargo build --release +cd .. + +# Run sequencer_runner with config +echo "Starting sequencer_runner..." +export RUST_LOG=info +nohup ./target/release/sequencer_runner "${LSSA_DIR}/configs/sequencer" > "${LSSA_DIR}/sequencer.log" 2>&1 & + +# Wait 5 seconds and check health using wallet +sleep 5 +if ./target/release/wallet check-health; then + echo "โœ“ Sequencer started successfully and is healthy" + log_deploy "Deployment completed successfully by: ${GITHUB_ACTOR}" + exit 0 +else + echo "โœ— Sequencer failed health check" + tail -n 50 "${LSSA_DIR}/sequencer.log" + handle_error +fi diff --git a/ci_scripts/lint-ubuntu.sh b/ci_scripts/lint-ubuntu.sh deleted file mode 100755 index dcfcfc66..00000000 --- a/ci_scripts/lint-ubuntu.sh +++ /dev/null @@ -1,8 +0,0 @@ -set -e - -cargo +nightly fmt -- --check - -cargo install taplo-cli --locked -taplo fmt --check - -RISC0_SKIP_BUILD=1 cargo clippy --workspace --all-targets -- -D warnings diff --git a/ci_scripts/test-ubuntu.sh b/ci_scripts/test-ubuntu.sh deleted file mode 100755 index 8114cb7c..00000000 --- a/ci_scripts/test-ubuntu.sh +++ /dev/null @@ -1,17 +0,0 @@ -set -e - -curl -L https://risczero.com/install | bash -/home/runner/.risc0/bin/rzup install - -RISC0_DEV_MODE=1 cargo test --release --features no_docker - -cd integration_tests -export NSSA_WALLET_HOME_DIR=$(pwd)/configs/debug/wallet/ -export RUST_LOG=info -echo "Try test valid proof at least once" -cargo run $(pwd)/configs/debug test_success_private_transfer_to_another_owned_account -echo "Continuing in dev mode" -RISC0_DEV_MODE=1 cargo run $(pwd)/configs/debug all -cd .. - -cd nssa/program_methods/guest && cargo test --release diff --git a/common/Cargo.toml b/common/Cargo.toml index 920ad2a8..a6e26fad 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -4,18 +4,16 @@ version = "0.1.0" edition = "2024" [dependencies] +nssa.workspace = true +nssa_core.workspace = true + anyhow.workspace = true thiserror.workspace = true serde_json.workspace = true serde.workspace = true reqwest.workspace = true - sha2.workspace = true log.workspace = true hex.workspace = true -nssa-core = { path = "../nssa/core", features = ["host"] } borsh.workspace = true base64.workspace = true - -[dependencies.nssa] -path = "../nssa" diff --git a/common/src/error.rs b/common/src/error.rs index e1634a63..5c81a106 100644 --- a/common/src/error.rs +++ b/common/src/error.rs @@ -1,3 +1,4 @@ +use nssa::AccountId; use serde::Deserialize; use crate::rpc_primitives::errors::RpcError; @@ -49,4 +50,6 @@ pub enum ExecutionFailureKind { SequencerClientError(#[from] SequencerClientError), #[error("Can not pay for operation")] InsufficientFundsError, + #[error("Account {0} data is invalid")] + AccountDataError(AccountId), } diff --git a/common/src/sequencer_client.rs b/common/src/sequencer_client.rs index 622c0c11..0cb03f6f 100644 --- a/common/src/sequencer_client.rs +++ b/common/src/sequencer_client.rs @@ -44,8 +44,10 @@ impl SequencerClient { ) -> Result { Ok(Self { client: Client::builder() - //Add more fiedls if needed + // Add more fields if needed .timeout(std::time::Duration::from_secs(60)) + // Should be kept in sync with server keep-alive settings + .pool_idle_timeout(std::time::Duration::from_secs(5)) .build()?, sequencer_addr, basic_auth, @@ -60,6 +62,10 @@ impl SequencerClient { let request = rpc_primitives::message::Request::from_payload_version_2_0(method.to_string(), payload); + log::debug!( + "Calling method {method} with payload {request:?} to sequencer at {}", + self.sequencer_addr + ); let mut call_builder = self.client.post(&self.sequencer_addr); if let Some((username, password)) = &self.basic_auth { diff --git a/completions/README.md b/completions/README.md new file mode 100644 index 00000000..4695c9e6 --- /dev/null +++ b/completions/README.md @@ -0,0 +1,135 @@ +# Wallet CLI Completion + +Completion scripts for the LSSA `wallet` command. + +## ZSH + +Works with both vanilla zsh and oh-my-zsh. + +### Features + +- Full completion for all wallet subcommands +- Contextual option completion for each command +- Dynamic account ID completion via `wallet account list` +- Descriptions for all commands and options + +Note that only accounts created by the user auto-complete. +Preconfigured accounts and accounts only with `/` (no number) are not completed. + +e.g.: + +``` +โ–ถ wallet account list +Preconfigured Public/Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw, +Preconfigured Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy, +Preconfigured Private/3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw, +Preconfigured Private/AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX, +/ Public/8DstRgMQrB2N9a7ymv98RDDbt8nctrP9ZzaNRSpKDZSu, +/0 Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH, +/ Private/Bcv15B36bs1VqvQAdY6ZGFM1KioByNQQsB92KTNAx6u2 +``` + +Only `Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH` is used for completion. + +### Supported Commands + +| Command | Description | +|------------------------|-------------------------------------------------------------| +| `wallet auth-transfer` | Authenticated transfer (init, send) | +| `wallet chain-info` | Chain info queries (current-block-id, block, transaction) | +| `wallet account` | Account management (get, list, new, sync-private) | +| `wallet pinata` | Piรฑata faucet (claim) | +| `wallet token` | Token operations (new, send) | +| `wallet amm` | AMM operations (new, swap, add-liquidity, remove-liquidity) | +| `wallet check-health` | Health check | + +### Installation + +#### Vanilla Zsh + +1. Create a completions directory: + + ```sh + mkdir -p ~/.zsh/completions + ``` + +2. Copy the completion file: + + ```sh + cp ./zsh/_wallet ~/.zsh/completions/ + ``` + +3. Add to your `~/.zshrc` (before any `compinit` call, or add these lines if you don't have one): + + ```sh + fpath=(~/.zsh/completions $fpath) + autoload -Uz compinit && compinit + ``` + +4. Reload your shell: + + ```sh + exec zsh + ``` + +#### Oh-My-Zsh + +1. Create the plugin directory and copy the file: + + ```sh + mkdir -p ~/.oh-my-zsh/custom/plugins/wallet + cp _wallet ~/.oh-my-zsh/custom/plugins/wallet/ + ``` + +2. Add `wallet` to your plugins array in `~/.zshrc`: + + ```sh + plugins=(... wallet) + ``` + +3. Reload your shell: + + ```sh + exec zsh + ``` + +### Requirements + +The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`. + +### Usage + +```sh +# Main commands +wallet + +# Account subcommands +wallet account + +# Options for auth-transfer send +wallet auth-transfer send -- + +# Account types when creating +wallet account new +# Shows: public private + +# Account IDs (fetched dynamically) +wallet account get --account-id +# Shows: Public/... Private/... +``` + +## Troubleshooting + +### Completions not appearing + +1. Check that `compinit` is called in your `.zshrc` +2. Rebuild the completion cache: + + ```sh + rm -f ~/.zcompdump* + exec zsh + ``` + +### Account IDs not completing + +Ensure `wallet account list` works from your command line. diff --git a/completions/zsh/_wallet b/completions/zsh/_wallet new file mode 100644 index 00000000..d2831ef0 --- /dev/null +++ b/completions/zsh/_wallet @@ -0,0 +1,435 @@ +#compdef wallet + +# Zsh completion script for the wallet CLI +# See instructions in ../README.md + +_wallet() { + local -a commands + local -a subcommands + local curcontext="$curcontext" state line + typeset -A opt_args + + _arguments -C \ + '(-c --continuous-run)'{-c,--continuous-run}'[Continuous run flag]' \ + '--auth[Basic authentication in the format user or user\:password]:auth:' \ + '1: :->command' \ + '*:: :->args' + + case $state in + command) + commands=( + 'auth-transfer:Authenticated transfer subcommand' + 'chain-info:Generic chain info subcommand' + 'account:Account view and sync subcommand' + 'pinata:Pinata program interaction subcommand' + 'token:Token program interaction subcommand' + 'amm:AMM program interaction subcommand' + 'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions' + 'config:Command to setup config, get and set config fields' + 'restore-keys:Restoring keys from given password at given depth' + 'deploy-program:Deploy a program' + 'help:Print help message or the help of the given subcommand(s)' + ) + _describe -t commands 'wallet commands' commands + ;; + args) + case $line[1] in + auth-transfer) + _wallet_auth_transfer + ;; + chain-info) + _wallet_chain_info + ;; + account) + _wallet_account + ;; + pinata) + _wallet_pinata + ;; + token) + _wallet_token + ;; + amm) + _wallet_amm + ;; + config) + _wallet_config + ;; + restore-keys) + _wallet_restore_keys + ;; + deploy-program) + _wallet_deploy_program + ;; + help) + _wallet_help + ;; + esac + ;; + esac +} + +# auth-transfer subcommand +_wallet_auth_transfer() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'init:Initialize account under authenticated transfer program' + 'send:Send native tokens from one account to another with variable privacy' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'auth-transfer subcommands' subcommands + ;; + args) + case $line[1] in + init) + _arguments \ + '--account-id[Account ID to initialize]:account_id:_wallet_account_ids' + ;; + send) + _arguments \ + '--from[Source account ID]:from_account:_wallet_account_ids' \ + '--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \ + '--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \ + '--to-ipk[Destination viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of native tokens to send]:amount:' + ;; + esac + ;; + esac +} + +# chain-info subcommand +_wallet_chain_info() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'current-block-id:Get current block id from sequencer' + 'block:Get block at id from sequencer' + 'transaction:Get transaction at hash from sequencer' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'chain-info subcommands' subcommands + ;; + args) + case $line[1] in + block) + _arguments \ + '--id[Block ID to retrieve]:block_id:' + ;; + transaction) + _arguments \ + '--hash[Transaction hash to retrieve]:tx_hash:' + ;; + esac + ;; + esac +} + +# account subcommand +_wallet_account() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'get:Get account data' + 'list:List all accounts' + 'ls:List all accounts (alias for list)' + 'new:Produce new public or private account' + 'sync-private:Sync private accounts' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'account subcommands' subcommands + ;; + args) + case $line[1] in + get) + _arguments \ + '(-r --raw)'{-r,--raw}'[Get raw account data]' \ + '(-k --keys)'{-k,--keys}'[Display keys (pk for public accounts, npk/ipk for private accounts)]' \ + '(-a --account-id)'{-a,--account-id}'[Account ID to query]:account_id:_wallet_account_ids' + ;; + list|ls) + _arguments \ + '(-l --long)'{-l,--long}'[Display detailed account information]' + ;; + new) + _arguments -C \ + '1: :->account_type' \ + '*:: :->new_args' + case $state in + account_type) + compadd public private + ;; + new_args) + _arguments \ + '--cci[Chain index of a parent node]:chain_index:' + ;; + esac + ;; + esac + ;; + esac +} + +# pinata subcommand +_wallet_pinata() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'claim:Claim tokens from the Piรฑata faucet' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'pinata subcommands' subcommands + ;; + args) + case $line[1] in + claim) + _arguments \ + '--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids' + ;; + esac + ;; + esac +} + +# token subcommand +_wallet_token() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'new:Produce a new token' + 'send:Send tokens from one account to another with variable privacy' + 'burn:Burn tokens on holder, modify definition' + 'mint:Mint tokens on holder, modify definition' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'token subcommands' subcommands + ;; + args) + case $line[1] in + new) + _arguments \ + '--name[Token name]:name:' \ + '--total-supply[Total supply of tokens to mint]:total_supply:' \ + '--definition-account-id[Account ID for token definition]:definition_account:_wallet_account_ids' \ + '--supply-account-id[Account ID to receive initial supply]:supply_account:_wallet_account_ids' + ;; + send) + _arguments \ + '--from[Source holding account ID]:from_account:_wallet_account_ids' \ + '--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \ + '--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \ + '--to-ipk[Destination viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of tokens to send]:amount:' + ;; + burn) + _arguments \ + '--definition[Definition account ID]:definition_account:_wallet_account_ids' \ + '--holder[Holder account ID]:holder_account:_wallet_account_ids' \ + '--amount[Amount of tokens to burn]:amount:' + ;; + mint) + _arguments \ + '--definition[Definition account ID]:definition_account:_wallet_account_ids' \ + '--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \ + '--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \ + '--holder-ipk[Holder viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of tokens to mint]:amount:' + ;; + esac + ;; + esac +} + +# amm subcommand +_wallet_amm() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'new:Create a new liquidity pool' + 'swap:Swap tokens using the AMM' + 'add-liquidity:Add liquidity to an existing pool' + 'remove-liquidity:Remove liquidity from a pool' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'amm subcommands' subcommands + ;; + args) + case $line[1] in + new) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--balance-a[Amount of token A to deposit]:balance_a:' \ + '--balance-b[Amount of token B to deposit]:balance_b:' + ;; + swap) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--amount-in[Amount of tokens to swap]:amount_in:' \ + '--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \ + '--token-definition[Definition ID of the token being provided]:token_def:' + ;; + add-liquidity) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--max-amount-a[Maximum amount of token A to deposit]:max_amount_a:' \ + '--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \ + '--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:' + ;; + remove-liquidity) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--balance-lp[Amount of LP tokens to burn]:balance_lp:' \ + '--min-amount-a[Minimum token A to receive]:min_amount_a:' \ + '--min-amount-b[Minimum token B to receive]:min_amount_b:' + ;; + esac + ;; + esac +} + +# config subcommand +_wallet_config() { + local -a subcommands + local -a config_keys + + config_keys=( + 'all' + 'override_rust_log' + 'sequencer_addr' + 'seq_poll_timeout_millis' + 'seq_tx_poll_max_blocks' + 'seq_poll_max_retries' + 'seq_block_poll_max_amount' + 'initial_accounts' + 'basic_auth' + ) + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'get:Getter of config fields' + 'set:Setter of config fields' + 'description:Prints description of corresponding field' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'config subcommands' subcommands + ;; + args) + case $line[1] in + get|description) + compadd -a config_keys + ;; + set) + _arguments \ + '1:key:compadd -a config_keys' \ + '2:value:' + ;; + esac + ;; + esac +} + +# restore-keys subcommand +_wallet_restore_keys() { + _arguments \ + '(-d --depth)'{-d,--depth}'[How deep in tree accounts may be]:depth:' +} + +# deploy-program subcommand +_wallet_deploy_program() { + _arguments \ + '1:binary filepath:_files' +} + +# help subcommand +_wallet_help() { + local -a commands + commands=( + 'auth-transfer:Authenticated transfer subcommand' + 'chain-info:Generic chain info subcommand' + 'account:Account view and sync subcommand' + 'pinata:Pinata program interaction subcommand' + 'token:Token program interaction subcommand' + 'amm:AMM program interaction subcommand' + 'check-health:Check the wallet can connect to the node' + 'config:Command to setup config, get and set config fields' + 'restore-keys:Restoring keys from given password at given depth' + 'deploy-program:Deploy a program' + ) + _describe -t commands 'wallet commands' commands +} + +# Helper function to complete account IDs +# Uses `wallet account list` to get available accounts +# Only includes accounts with /N prefix (where N is a number) +_wallet_account_ids() { + local -a accounts + local line + + # Try to get accounts from wallet account list command + # Filter to lines starting with /N (numbered accounts) and extract the account ID + if command -v wallet &>/dev/null; then + while IFS= read -r line; do + # Remove trailing comma if present and add to array + [[ -n "$line" ]] && accounts+=("${line%,}") + done < <(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}') + fi + + # Provide type prefixes as fallback if command fails or returns nothing + if (( ${#accounts} == 0 )); then + compadd -S '' -- 'Public/' 'Private/' + return + fi + + _multi_parts / accounts +} + +_wallet "$@" diff --git a/examples/program_deployment/Cargo.toml b/examples/program_deployment/Cargo.toml index 21d4fc86..6aff2d0f 100644 --- a/examples/program_deployment/Cargo.toml +++ b/examples/program_deployment/Cargo.toml @@ -4,10 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] +nssa.workspace = true +nssa_core.workspace = true +wallet.workspace = true + tokio = { workspace = true, features = ["macros"] } -wallet = { path = "../../wallet" } -nssa-core = { path = "../../nssa/core" } -nssa = { path = "../../nssa" } -key_protocol = { path = "../../key_protocol/" } -clap = "4.5.53" -serde = "1.0.228" +clap.workspace = true diff --git a/examples/program_deployment/README.md b/examples/program_deployment/README.md index 1bc7ed74..b0d59151 100644 --- a/examples/program_deployment/README.md +++ b/examples/program_deployment/README.md @@ -41,13 +41,15 @@ In a second terminal, from the `lssa` root directory, compile the example Risc0 ```bash cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml ``` -The compiled `.bin` files will appear under: +Because this repository is organized as a Cargo workspace, build artifacts are written to the +shared `target/` directory at the workspace root by default. The compiled `.bin` files will +appear under: ``` -examples/program_deployment/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/ +target/riscv32im-risc0-zkvm-elf/docker/ ``` For convenience, export this path: ```bash -export EXAMPLE_PROGRAMS_BUILD_DIR=$(pwd)/examples/program_deployment/methods/guest/target/riscv32im-risc0-zkvm-elf/docker +export EXAMPLE_PROGRAMS_BUILD_DIR=$(pwd)/target/riscv32im-risc0-zkvm-elf/docker ``` > [!IMPORTANT] @@ -340,7 +342,7 @@ Luckily all that complexity is hidden behind the `wallet_core.send_privacy_prese .send_privacy_preserving_tx( accounts, &Program::serialize_instruction(greeting).unwrap(), - &program, + &program.into(), ) .await .unwrap(); @@ -568,4 +570,94 @@ Output: ``` Hola mundo!Hello from tail call ``` +## Private tail-calls +There's support for tail calls in privacy preserving executions too. The `run_hello_world_through_tail_call_private.rs` runner walks you through the process of invoking such an execution. +The only difference is that, since the execution is local, the runner will need both programs: the `simple_tail_call` and it's dependency `hello_world`. + +Let's use our existing private account with id `8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU`. This one is already owned by the `hello_world` program. + +You can test the privacy tail calls with +```bash +cargo run --bin run_hello_world_through_tail_call_private \ + $EXAMPLE_PROGRAMS_BUILD_DIR/simple_tail_call.bin \ + $EXAMPLE_PROGRAMS_BUILD_DIR/hello_world.bin \ + 8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU +``` + +>[!NOTE] +> The above command may take longer than the previous privacy executions because needs to generate proofs of execution of both the `simple_tail_call` and the `hello_world` programs. + +Once finished run the following to see the changes +```bash +wallet account sync-private +wallet account get --account-id Private/8vzkK7vsdrS2gdPhLk72La8X4FJkgJ5kJLUBRbEVkReU +``` + +# 13. Program derived accounts: authorizing accounts through tail calls + +## Digression: account authority vs account program ownership + +In NSSA there are two distinct concepts that control who can modify an account: +**Program Ownership:** Each account has a field: `program_owner: ProgramId`. +This indicates which program is allowed to update the accountโ€™s state during execution. +- If a program is the program_owner of an account, it can freely mutate its fields. +- If the account is uninitialized (`program_owner = DEFAULT_PROGRAM_ID`), a program may claim it and become its owner. +- If a program is not the owner and the account is not claimable, any attempt to modify it will cause the transition to fail. +Program ownership is about mutation rights during program execution. + +**Account authority**: Independent from program ownership, each account also has an authority. The entity that is allowed to set: `is_authorized = true`. This flag indicates that the account has been authorized for use in a transaction. +Who can act as authority? +- User-defined accounts: The user is the authority. They can mark an account as authorized by: + - Signing the transaction (public accounts) + - Providing a valid nullifiers secret key ownership proof (private accounts) +- Program derived accounts: Programs are automatically the authority of a dedicated namespace of public accounts. + +Each program owns a non-overlapping space of 2^256 **public** account IDs. They do not overlap with: +- User accounts (public or private) +- Other programโ€™s PDAs + +> [!NOTE] +> Currently PDAs are restricted to the public state. + +A program can be the authority of an account owned by another program, which is the most common case. +During a chained call, a program can mark its PDA accounts as `is_authorized=true` without requiring any user signatures or nullifier secret keys. This enables programs to safely authorize accounts during program composition. Importantly, these flags can only be set to true for PDA accounts through an execution of the program that is their authority. No user and no other program can execute any transition that requires authorization of PDA accounts belonging to a different program. + +## Running the example +This tutorial includes an example of PDA usage in `methods/guest/src/bin/tail_call_with_pda.rs.`. That programโ€™s sole purpose is to forward one of its own PDA accounts, an account for which it is the authority, to the "Hello World with authorization" program via a chained call. The Hello World program will then claim the account and become its program owner, but the `tail_call_with_pda` program remains the authority. This means it is still the only entity capable of marking that account as `is_authorized=true`. + +Deploy the program: +```bash +wallet deploy-program $EXAMPLE_PROGRAMS_BUILD_DIR/tail_call_with_pda.bin +``` + +There is no need to create a new account for this example, because we simply use one of the PDA accounts belonging to the `tail_call_with_pda` program. + +Execute the program +```bash +cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda $EXAMPLE_PROGRAMS_BUILD_DIR/tail_call_with_pda.bin +``` + +You'll see an output like the following: + +```bash +The program derived account ID is: 3tfTPPuxj3eSE1cLVuNBEk8eSHzpnYS1oqEdeH3Nfsks +``` + +Then check the status of that account + +```bash +wallet account get --account-id Public/3tfTPPuxj3eSE1cLVuNBEk8eSHzpnYS1oqEdeH3Nfsks +``` + +Output: +```bash +{ + "balance":0, + "program_owner_b64":"HZXHYRaKf6YusVo8x00/B15uyY5sGsJb1bzH4KlCY5g=", + "data_b64": "SGVsbG8gZnJvbSB0YWlsIGNhbGwgd2l0aCBQcm9ncmFtIERlcml2ZWQgQWNjb3VudCBJRA==", + "nonce":0" +} +``` + + diff --git a/examples/program_deployment/methods/Cargo.toml b/examples/program_deployment/methods/Cargo.toml index 0317d2b6..a25aecf2 100644 --- a/examples/program_deployment/methods/Cargo.toml +++ b/examples/program_deployment/methods/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "test-program-methods" +name = "example_program_deployment_methods" version = "0.1.0" edition = "2024" [build-dependencies] -risc0-build = { version = "3.0.3" } +risc0-build.workspace = true [package.metadata.risc0] methods = ["guest"] diff --git a/examples/program_deployment/methods/guest/Cargo.toml b/examples/program_deployment/methods/guest/Cargo.toml index 8e2a1994..245bc5db 100644 --- a/examples/program_deployment/methods/guest/Cargo.toml +++ b/examples/program_deployment/methods/guest/Cargo.toml @@ -1,13 +1,11 @@ [package] -name = "programs" +name = "example_program_deployment_programs" version = "0.1.0" edition = "2024" -[workspace] - [dependencies] -risc0-zkvm = { version = "3.0.3", features = ['std'] } -nssa-core = { path = "../../../../nssa/core" } -serde = { version = "1.0.219", default-features = false } -hex = "0.4.3" -bytemuck = "1.24.0" +nssa_core.workspace = true + +hex.workspace = true +bytemuck.workspace = true +risc0-zkvm.workspace = true diff --git a/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs b/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs index d2bb58ce..e933598f 100644 --- a/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs +++ b/examples/program_deployment/methods/guest/src/bin/simple_tail_call.rs @@ -9,13 +9,12 @@ use nssa_core::program::{ // It reads a single account, emits it unchanged, and then triggers a tail call // to the Hello World program with a fixed greeting. - /// This needs to be set to the ID of the Hello world program. /// To get the ID run **from the root directoy of the repository**: /// `cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml` /// This compiles the programs and outputs the IDs in hex that can be used to copy here. const HELLO_WORLD_PROGRAM_ID_HEX: &str = - "7e99d6e2d158f4dea59597011da5d1c2eef17beed6667657f515b387035b935a"; + "e9dfc5a5d03c9afa732adae6e0edfce4bbb44c7a2afb9f148f4309917eb2de6f"; fn hello_world_program_id() -> ProgramId { let hello_world_program_id_bytes: [u8; 32] = hex::decode(HELLO_WORLD_PROGRAM_ID_HEX) diff --git a/examples/program_deployment/methods/guest/src/bin/tail_call_with_pda.rs b/examples/program_deployment/methods/guest/src/bin/tail_call_with_pda.rs new file mode 100644 index 00000000..684fa1e8 --- /dev/null +++ b/examples/program_deployment/methods/guest/src/bin/tail_call_with_pda.rs @@ -0,0 +1,76 @@ +use nssa_core::program::{ + AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, +}; + +// Tail Call with PDA example program. +// +// Demonstrates how to chain execution to another program using `ChainedCall` +// while authorizing program-derived accounts. +// +// Expects a single input account whose Account ID is derived from this +// programโ€™s ID and the fixed PDA seed below (as defined by the +// `>` implementation). +// +// Emits this account unchanged, then performs a tail call to the +// Hello-World-with-Authorization program with a fixed greeting. The same +// account is passed along but marked with `is_authorized = true`. + +const HELLO_WORLD_WITH_AUTHORIZATION_PROGRAM_ID_HEX: &str = + "1d95c761168a7fa62eb15a3cc74d3f075e6ec98e6c1ac25bd5bcc7e0a9426398"; +const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]); + +fn hello_world_program_id() -> ProgramId { + let hello_world_program_id_bytes: [u8; 32] = + hex::decode(HELLO_WORLD_WITH_AUTHORIZATION_PROGRAM_ID_HEX) + .unwrap() + .try_into() + .unwrap(); + bytemuck::cast(hello_world_program_id_bytes) +} + +fn main() { + // Read inputs + let ( + ProgramInput { + pre_states, + instruction: _, + }, + instruction_data, + ) = read_nssa_inputs::<()>(); + + // Unpack the input account pre state + let [pre_state] = pre_states + .clone() + .try_into() + .unwrap_or_else(|_| panic!("Input pre states should consist of a single account")); + + // Create the (unchanged) post state + let post_state = AccountPostState::new(pre_state.account.clone()); + + // Create the chained call + let chained_call_greeting: Vec = + b"Hello from tail call with Program Derived Account ID".to_vec(); + let chained_call_instruction_data = risc0_zkvm::serde::to_vec(&chained_call_greeting).unwrap(); + + // Flip the `is_authorized` flag to true + let pre_state_for_chained_call = { + let mut this = pre_state.clone(); + this.is_authorized = true; + this + }; + let chained_call = ChainedCall { + program_id: hello_world_program_id(), + instruction_data: chained_call_instruction_data, + pre_states: vec![pre_state_for_chained_call], + pda_seeds: vec![PDA_SEED], + }; + + // Write the outputs + write_nssa_outputs_with_chained_call( + instruction_data, + vec![pre_state], + vec![post_state], + vec![chained_call], + ); +} diff --git a/examples/program_deployment/src/bin/run_hello_world.rs b/examples/program_deployment/src/bin/run_hello_world.rs index a7dc0fca..3c0c9034 100644 --- a/examples/program_deployment/src/bin/run_hello_world.rs +++ b/examples/program_deployment/src/bin/run_hello_world.rs @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `hello_world.rs` guest program with: // @@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary 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 be4280b8..27ac2079 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -1,5 +1,5 @@ use nssa::{AccountId, program::Program}; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `hello_world.rs` guest program with: // @@ -22,11 +22,8 @@ use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary @@ -53,8 +50,8 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(greeting).unwrap(), - &program, + Program::serialize_instruction(greeting).unwrap(), + &program.into(), ) .await .unwrap(); 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 d7c91f89..56d28084 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 @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `simple_tail_call.rs` guest program with: // @@ -24,11 +24,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary 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 new file mode 100644 index 00000000..9b3619cb --- /dev/null +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; + +use nssa::{ + AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies, + program::Program, +}; +use wallet::{PrivacyPreservingAccount, WalletCore}; + +// Before running this example, compile the `simple_tail_call.rs` guest program with: +// +// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml +// +// Note: you must run the above command from the root of the `lssa` repository. +// Note: The compiled binary file is stored in +// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin +// +// +// Usage: +// cargo run --bin run_hello_world_through_tail_call_private /path/to/guest/binary +// +// Example: +// cargo run --bin run_hello_world_through_tail_call \ +// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin \ +// Ds8q5PjLcKwwV97Zi7duhRVF9uwA2PuYMoLL7FwCzsXE + +#[tokio::main] +async fn main() { + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); + + // Parse arguments + // First argument is the path to the simple_tail_call program binary + let simple_tail_call_path = std::env::args_os().nth(1).unwrap().into_string().unwrap(); + // Second argument is the path to the hello_world program binary + let hello_world_path = std::env::args_os().nth(2).unwrap().into_string().unwrap(); + // Third argument is the account_id + let account_id: AccountId = std::env::args_os() + .nth(3) + .unwrap() + .into_string() + .unwrap() + .parse() + .unwrap(); + + // 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 hello_world_bytecode: Vec = std::fs::read(hello_world_path).unwrap(); + let hello_world = Program::new(hello_world_bytecode).unwrap(); + let dependencies: HashMap = + [(hello_world.id(), hello_world)].into_iter().collect(); + let program_with_dependencies = ProgramWithDependencies::new(simple_tail_call, dependencies); + + let accounts = vec![PrivacyPreservingAccount::PrivateOwned(account_id)]; + + // Construct and submit the privacy-preserving transaction + let instruction = (); + wallet_core + .send_privacy_preserving_tx( + accounts, + Program::serialize_instruction(instruction).unwrap(), + &program_with_dependencies, + ) + .await + .unwrap(); +} 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 21740ae9..09fd621e 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 @@ -3,7 +3,7 @@ use nssa::{ program::Program, public_transaction::{Message, WitnessSet}, }; -use wallet::{WalletCore, helperfunctions::fetch_config}; +use wallet::WalletCore; // Before running this example, compile the `hello_world_with_authorization.rs` guest program with: // @@ -26,11 +26,8 @@ use wallet::{WalletCore, helperfunctions::fetch_config}; #[tokio::main] async fn main() { - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); // Parse arguments // First argument is the path to the program binary @@ -50,7 +47,7 @@ async fn main() { // Load signing keys to provide authorization let signing_key = wallet_core - .storage + .storage() .user_data .get_pub_account_signing_key(&account_id) .expect("Input account should be a self owned public account"); 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 new file mode 100644 index 00000000..43839ba9 --- /dev/null +++ b/examples/program_deployment/src/bin/run_hello_world_with_authorization_through_tail_call_with_pda.rs @@ -0,0 +1,59 @@ +use nssa::{ + AccountId, PublicTransaction, + program::Program, + public_transaction::{Message, WitnessSet}, +}; +use nssa_core::program::PdaSeed; +use wallet::WalletCore; + +// Before running this example, compile the `simple_tail_call.rs` guest program with: +// +// cargo risczero build --manifest-path examples/program_deployment/methods/guest/Cargo.toml +// +// Note: you must run the above command from the root of the `lssa` repository. +// Note: The compiled binary file is stored in +// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/simple_tail_call.bin +// +// +// Usage: +// cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda +// /path/to/guest/binary +// +// Example: +// cargo run --bin run_hello_world_with_authorization_through_tail_call_with_pda \ +// methods/guest/target/riscv32im-risc0-zkvm-elf/docker/tail_call_with_pda.bin + +const PDA_SEED: PdaSeed = PdaSeed::new([37; 32]); + +#[tokio::main] +async fn main() { + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); + + // Parse arguments + // First argument is the path to the program binary + let program_path = std::env::args_os().nth(1).unwrap().into_string().unwrap(); + + // Load the program + let bytecode: Vec = std::fs::read(program_path).unwrap(); + let program = Program::new(bytecode).unwrap(); + + // Compute the PDA to pass it as input account to the public execution + let pda = AccountId::from((&program.id(), &PDA_SEED)); + let account_ids = vec![pda]; + let instruction_data = (); + let nonces = vec![]; + let signing_keys = []; + let message = Message::try_new(program.id(), account_ids, nonces, instruction_data).unwrap(); + let witness_set = WitnessSet::for_message(&message, &signing_keys); + let tx = PublicTransaction::new(message, witness_set); + + // Submit the transaction + let _response = wallet_core + .sequencer_client + .send_tx_public(tx) + .await + .unwrap(); + + println!("The program derived account id is: {pda}"); +} 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 77c25978..fc116241 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 @@ -1,6 +1,6 @@ use clap::{Parser, Subcommand}; use nssa::{PublicTransaction, program::Program, public_transaction}; -use wallet::{PrivacyPreservingAccount, WalletCore, helperfunctions::fetch_config}; +use wallet::{PrivacyPreservingAccount, WalletCore}; // Before running this example, compile the `hello_world_with_move_function.rs` guest program with: // @@ -62,11 +62,8 @@ async fn main() { let bytecode: Vec = std::fs::read(cli.program_path).unwrap(); let program = Program::new(bytecode).unwrap(); - // Load wallet config and storage - let wallet_config = fetch_config().await.unwrap(); - let wallet_core = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); + // Initialize wallet + let wallet_core = WalletCore::from_env().unwrap(); match cli.command { Command::WritePublic { @@ -104,8 +101,8 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), - &program, + Program::serialize_instruction(instruction).unwrap(), + &program.into(), ) .await .unwrap(); @@ -145,8 +142,8 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), - &program, + Program::serialize_instruction(instruction).unwrap(), + &program.into(), ) .await .unwrap(); diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 58be5a5a..b888c177 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -4,41 +4,21 @@ version = "0.1.0" edition = "2024" [dependencies] +nssa_core = { workspace = true, features = ["host"] } +nssa.workspace = true +sequencer_core = { workspace = true, features = ["testnet"] } +sequencer_runner.workspace = true +wallet.workspace = true +common.workspace = true +key_protocol.workspace = true + anyhow.workspace = true env_logger.workspace = true log.workspace = true -actix.workspace = true actix-web.workspace = true base64.workspace = true -tokio.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } hex.workspace = true tempfile.workspace = true borsh.workspace = true - -nssa-core = { path = "../nssa/core", features = ["host"] } - -proc_macro_test_attribute = { path = "./proc_macro_test_attribute" } - -[dependencies.clap] -features = ["derive", "env"] -workspace = true - -[dependencies.sequencer_core] -path = "../sequencer_core" -features = ["testnet"] - -[dependencies.sequencer_runner] -path = "../sequencer_runner" - -[dependencies.wallet] -path = "../wallet" - -[dependencies.common] -path = "../common" - -[dependencies.key_protocol] -path = "../key_protocol" - -[dependencies.nssa] -path = "../nssa" -features = ["no_docker"] +futures.workspace = true diff --git a/integration_tests/configs/sequencer/sequencer_config.json b/integration_tests/configs/sequencer/sequencer_config.json new file mode 100644 index 00000000..1548bb5b --- /dev/null +++ b/integration_tests/configs/sequencer/sequencer_config.json @@ -0,0 +1,158 @@ +{ + "home": "", + "override_rust_log": null, + "genesis_id": 1, + "is_genesis_random": true, + "max_num_tx_in_block": 20, + "mempool_max_size": 10000, + "block_create_timeout_millis": 10000, + "port": 0, + "initial_accounts": [ + { + "account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy", + "balance": 10000 + }, + { + "account_id": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw", + "balance": 20000 + } + ], + "initial_commitments": [ + { + "npk": [ + 63, + 202, + 178, + 231, + 183, + 82, + 237, + 212, + 216, + 221, + 215, + 255, + 153, + 101, + 177, + 161, + 254, + 210, + 128, + 122, + 54, + 190, + 230, + 151, + 183, + 64, + 225, + 229, + 113, + 1, + 228, + 97 + ], + "account": { + "program_owner": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "balance": 10000, + "data": [], + "nonce": 0 + } + }, + { + "npk": [ + 192, + 251, + 166, + 243, + 167, + 236, + 84, + 249, + 35, + 136, + 130, + 172, + 219, + 225, + 161, + 139, + 229, + 89, + 243, + 125, + 194, + 213, + 209, + 30, + 23, + 174, + 100, + 244, + 124, + 74, + 140, + 47 + ], + "account": { + "program_owner": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "balance": 20000, + "data": [], + "nonce": 0 + } + } + ], + "signing_key": [ + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37, + 37 + ] +} \ No newline at end of file diff --git a/integration_tests/configs/wallet/wallet_config.json b/integration_tests/configs/wallet/wallet_config.json new file mode 100644 index 00000000..2abab83e --- /dev/null +++ b/integration_tests/configs/wallet/wallet_config.json @@ -0,0 +1,547 @@ +{ + "override_rust_log": null, + "sequencer_addr": "", + "seq_poll_timeout_millis": 12000, + "seq_tx_poll_max_blocks": 5, + "seq_poll_max_retries": 5, + "seq_block_poll_max_amount": 100, + "basic_auth": null, + "initial_accounts": [ + { + "Public": { + "account_id": "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy", + "pub_sign_key": [ + 16, + 162, + 106, + 154, + 236, + 125, + 52, + 184, + 35, + 100, + 238, + 174, + 69, + 197, + 41, + 77, + 187, + 10, + 118, + 75, + 0, + 11, + 148, + 238, + 185, + 181, + 133, + 17, + 220, + 72, + 124, + 77 + ] + } + }, + { + "Public": { + "account_id": "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw", + "pub_sign_key": [ + 113, + 121, + 64, + 177, + 204, + 85, + 229, + 214, + 178, + 6, + 109, + 191, + 29, + 154, + 63, + 38, + 242, + 18, + 244, + 219, + 8, + 208, + 35, + 136, + 23, + 127, + 207, + 237, + 216, + 169, + 190, + 27 + ] + } + }, + { + "Private": { + "account_id": "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw", + "account": { + "program_owner": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "balance": 10000, + "data": [], + "nonce": 0 + }, + "key_chain": { + "secret_spending_key": [ + 251, + 82, + 235, + 1, + 146, + 96, + 30, + 81, + 162, + 234, + 33, + 15, + 123, + 129, + 116, + 0, + 84, + 136, + 176, + 70, + 190, + 224, + 161, + 54, + 134, + 142, + 154, + 1, + 18, + 251, + 242, + 189 + ], + "private_key_holder": { + "nullifier_secret_key": [ + 29, + 250, + 10, + 187, + 35, + 123, + 180, + 250, + 246, + 97, + 216, + 153, + 44, + 156, + 16, + 93, + 241, + 26, + 174, + 219, + 72, + 84, + 34, + 247, + 112, + 101, + 217, + 243, + 189, + 173, + 75, + 20 + ], + "incoming_viewing_secret_key": [ + 251, + 201, + 22, + 154, + 100, + 165, + 218, + 108, + 163, + 190, + 135, + 91, + 145, + 84, + 69, + 241, + 46, + 117, + 217, + 110, + 197, + 248, + 91, + 193, + 14, + 104, + 88, + 103, + 67, + 153, + 182, + 158 + ], + "outgoing_viewing_secret_key": [ + 25, + 67, + 121, + 76, + 175, + 100, + 30, + 198, + 105, + 123, + 49, + 169, + 75, + 178, + 75, + 210, + 100, + 143, + 210, + 243, + 228, + 243, + 21, + 18, + 36, + 84, + 164, + 186, + 139, + 113, + 214, + 12 + ] + }, + "nullifer_public_key": [ + 63, + 202, + 178, + 231, + 183, + 82, + 237, + 212, + 216, + 221, + 215, + 255, + 153, + 101, + 177, + 161, + 254, + 210, + 128, + 122, + 54, + 190, + 230, + 151, + 183, + 64, + 225, + 229, + 113, + 1, + 228, + 97 + ], + "incoming_viewing_public_key": [ + 3, + 235, + 139, + 131, + 237, + 177, + 122, + 189, + 6, + 177, + 167, + 178, + 202, + 117, + 246, + 58, + 28, + 65, + 132, + 79, + 220, + 139, + 119, + 243, + 187, + 160, + 212, + 121, + 61, + 247, + 116, + 72, + 205 + ] + } + } + }, + { + "Private": { + "account_id": "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX", + "account": { + "program_owner": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "balance": 20000, + "data": [], + "nonce": 0 + }, + "key_chain": { + "secret_spending_key": [ + 238, + 171, + 241, + 69, + 111, + 217, + 85, + 64, + 19, + 82, + 18, + 189, + 32, + 91, + 78, + 175, + 107, + 7, + 109, + 60, + 52, + 44, + 243, + 230, + 72, + 244, + 192, + 92, + 137, + 33, + 118, + 254 + ], + "private_key_holder": { + "nullifier_secret_key": [ + 25, + 211, + 215, + 119, + 57, + 223, + 247, + 37, + 245, + 144, + 122, + 29, + 118, + 245, + 83, + 228, + 23, + 9, + 101, + 120, + 88, + 33, + 238, + 207, + 128, + 61, + 110, + 2, + 89, + 62, + 164, + 13 + ], + "incoming_viewing_secret_key": [ + 193, + 181, + 14, + 196, + 142, + 84, + 15, + 65, + 128, + 101, + 70, + 196, + 241, + 47, + 130, + 221, + 23, + 146, + 161, + 237, + 221, + 40, + 19, + 126, + 59, + 15, + 169, + 236, + 25, + 105, + 104, + 231 + ], + "outgoing_viewing_secret_key": [ + 20, + 170, + 220, + 108, + 41, + 23, + 155, + 217, + 247, + 190, + 175, + 168, + 247, + 34, + 105, + 134, + 114, + 74, + 104, + 91, + 211, + 62, + 126, + 13, + 130, + 100, + 241, + 214, + 250, + 236, + 38, + 150 + ] + }, + "nullifer_public_key": [ + 192, + 251, + 166, + 243, + 167, + 236, + 84, + 249, + 35, + 136, + 130, + 172, + 219, + 225, + 161, + 139, + 229, + 89, + 243, + 125, + 194, + 213, + 209, + 30, + 23, + 174, + 100, + 244, + 124, + 74, + 140, + 47 + ], + "incoming_viewing_public_key": [ + 2, + 181, + 98, + 93, + 216, + 241, + 241, + 110, + 58, + 198, + 119, + 174, + 250, + 184, + 1, + 204, + 200, + 173, + 44, + 238, + 37, + 247, + 170, + 156, + 100, + 254, + 116, + 242, + 28, + 183, + 187, + 77, + 255 + ] + } + } + } + ] +} \ No newline at end of file diff --git a/integration_tests/data_changer.bin b/integration_tests/data_changer.bin deleted file mode 100644 index eb28a627..00000000 Binary files a/integration_tests/data_changer.bin and /dev/null differ diff --git a/integration_tests/proc_macro_test_attribute/Cargo.toml b/integration_tests/proc_macro_test_attribute/Cargo.toml deleted file mode 100644 index a50c3f37..00000000 --- a/integration_tests/proc_macro_test_attribute/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "proc_macro_test_attribute" -version = "0.1.0" -edition = "2024" - -[dependencies] - -[lib] -proc-macro = true diff --git a/integration_tests/proc_macro_test_attribute/src/lib.rs b/integration_tests/proc_macro_test_attribute/src/lib.rs deleted file mode 100644 index 29852a0b..00000000 --- a/integration_tests/proc_macro_test_attribute/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -extern crate proc_macro; - -use proc_macro::*; - -#[proc_macro_attribute] -pub fn nssa_integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream { - let input = item.to_string(); - - let fn_keyword = "fn "; - let fn_keyword_alternative = "fn\n"; - - let mut start_opt = None; - let mut fn_name = String::new(); - - if let Some(start) = input.find(fn_keyword) { - start_opt = Some(start); - } else if let Some(start) = input.find(fn_keyword_alternative) { - start_opt = Some(start); - } - - if let Some(start) = start_opt { - let rest = &input[start + fn_keyword.len()..]; - if let Some(end) = rest.find(|c: char| c == '(' || c.is_whitespace()) { - let name = &rest[..end]; - fn_name = name.to_string(); - } - } else { - println!("ERROR: keyword fn not found"); - } - - let extension = format!( - r#" - {input} - - function_map.insert("{fn_name}".to_string(), |home_dir: PathBuf| Box::pin(async {{ - let res = pre_test(home_dir).await.unwrap(); - - info!("Waiting for first block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - {fn_name}().await; - - post_test(res).await; - }})); - "# - ); - - extension.parse().unwrap() -} diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 401238fa..12d718ec 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -1,38 +1,25 @@ -use std::path::PathBuf; +//! This library contains common code for integration tests. + +use std::{net::SocketAddr, path::PathBuf, sync::LazyLock}; use actix_web::dev::ServerHandle; -use anyhow::Result; +use anyhow::{Context as _, Result}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; -use clap::Parser; use common::{ sequencer_client::SequencerClient, transaction::{EncodedTransaction, NSSATransaction}, }; -use log::{info, warn}; +use futures::FutureExt as _; +use log::debug; use nssa::PrivacyPreservingTransaction; use nssa_core::Commitment; use sequencer_core::config::SequencerConfig; -use sequencer_runner::startup_sequencer; use tempfile::TempDir; use tokio::task::JoinHandle; +use wallet::{WalletCore, config::WalletConfigOverrides}; -use crate::test_suite_map::{prepare_function_map, tps_test}; - -#[macro_use] -extern crate proc_macro_test_attribute; - -pub mod test_suite_map; - -mod tps_test_utils; - -#[derive(Parser, Debug)] -#[clap(version)] -struct Args { - /// Path to configs - home_dir: PathBuf, - /// Test name - test_name: String, -} +// TODO: Remove this and control time from tests +pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; pub const ACC_SENDER: &str = "BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy"; pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw"; @@ -40,104 +27,181 @@ pub const ACC_RECEIVER: &str = "Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw"; pub const ACC_SENDER_PRIVATE: &str = "3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw"; pub const ACC_RECEIVER_PRIVATE: &str = "AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX"; -pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; - pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin"; -fn make_public_account_input_from_str(account_id: &str) -> String { +static LOGGER: LazyLock<()> = LazyLock::new(env_logger::init); + +/// Test context which sets up a sequencer and a wallet for integration tests. +/// +/// It's memory and logically safe to create multiple instances of this struct in parallel tests, +/// as each instance uses its own temporary directories for sequencer and wallet data. +pub struct TestContext { + sequencer_server_handle: ServerHandle, + sequencer_loop_handle: JoinHandle>, + sequencer_client: SequencerClient, + wallet: WalletCore, + _temp_sequencer_dir: TempDir, + _temp_wallet_dir: TempDir, +} + +impl TestContext { + /// Create new test context. + pub async fn new() -> Result { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + + let sequencer_config_path = + PathBuf::from(manifest_dir).join("configs/sequencer/sequencer_config.json"); + + let sequencer_config = SequencerConfig::from_path(&sequencer_config_path) + .context("Failed to create sequencer config from file")?; + + Self::new_with_sequencer_config(sequencer_config).await + } + + /// Create new test context with custom sequencer config. + /// + /// `home` and `port` fields of the provided config will be overridden to meet tests parallelism + /// requirements. + pub async fn new_with_sequencer_config(sequencer_config: SequencerConfig) -> Result { + // Ensure logger is initialized only once + *LOGGER; + + debug!("Test context setup"); + + let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle, temp_sequencer_dir) = + Self::setup_sequencer(sequencer_config) + .await + .context("Failed to setup sequencer")?; + + // 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 sequencer_addr = if sequencer_addr.ip().is_unspecified() { + format!("http://127.0.0.1:{}", sequencer_addr.port()) + } else { + format!("http://{sequencer_addr}") + }; + + let (wallet, temp_wallet_dir) = Self::setup_wallet(sequencer_addr.clone()) + .await + .context("Failed to setup wallet")?; + + let sequencer_client = + SequencerClient::new(sequencer_addr).context("Failed to create sequencer client")?; + + Ok(Self { + sequencer_server_handle, + sequencer_loop_handle, + sequencer_client, + wallet, + _temp_sequencer_dir: temp_sequencer_dir, + _temp_wallet_dir: temp_wallet_dir, + }) + } + + async fn setup_sequencer( + mut config: SequencerConfig, + ) -> Result<(ServerHandle, SocketAddr, JoinHandle>, TempDir)> { + let temp_sequencer_dir = + tempfile::tempdir().context("Failed to create temp dir for sequencer home")?; + + debug!( + "Using temp sequencer home at {:?}", + temp_sequencer_dir.path() + ); + config.home = temp_sequencer_dir.path().to_owned(); + // Setting port to 0 lets the OS choose a free port for us + config.port = 0; + + let (sequencer_server_handle, sequencer_addr, sequencer_loop_handle) = + sequencer_runner::startup_sequencer(config).await?; + + Ok(( + sequencer_server_handle, + sequencer_addr, + sequencer_loop_handle, + temp_sequencer_dir, + )) + } + + async fn setup_wallet(sequencer_addr: String) -> Result<(WalletCore, TempDir)> { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let wallet_config_source_path = + PathBuf::from(manifest_dir).join("configs/wallet/wallet_config.json"); + + let temp_wallet_dir = + tempfile::tempdir().context("Failed to create temp dir for wallet home")?; + + let config_path = temp_wallet_dir.path().join("wallet_config.json"); + std::fs::copy(&wallet_config_source_path, &config_path) + .context("Failed to copy wallet config to temp dir")?; + + let storage_path = temp_wallet_dir.path().join("storage.json"); + let config_overrides = WalletConfigOverrides { + sequencer_addr: Some(sequencer_addr), + ..Default::default() + }; + + let wallet = WalletCore::new_init_storage( + config_path, + storage_path, + Some(config_overrides), + "test_pass".to_owned(), + ) + .context("Failed to init wallet")?; + wallet + .store_persistent_data() + .await + .context("Failed to store wallet persistent data")?; + + Ok((wallet, temp_wallet_dir)) + } + + /// Get reference to the wallet. + pub fn wallet(&self) -> &WalletCore { + &self.wallet + } + + /// Get mutable reference to the wallet. + pub fn wallet_mut(&mut self) -> &mut WalletCore { + &mut self.wallet + } + + /// Get reference to the sequencer client. + pub fn sequencer_client(&self) -> &SequencerClient { + &self.sequencer_client + } +} + +impl Drop for TestContext { + fn drop(&mut self) { + debug!("Test context cleanup"); + + let Self { + sequencer_server_handle, + sequencer_loop_handle, + sequencer_client: _, + wallet: _, + _temp_sequencer_dir, + _temp_wallet_dir, + } = self; + + sequencer_loop_handle.abort(); + + // Can't wait here as Drop can't be async, but anyway stop signal should be sent + sequencer_server_handle.stop(true).now_or_never(); + } +} + +pub fn format_public_account_id(account_id: &str) -> String { format!("Public/{account_id}") } -fn make_private_account_input_from_str(account_id: &str) -> String { +pub fn format_private_account_id(account_id: &str) -> String { format!("Private/{account_id}") } -#[allow(clippy::type_complexity)] -pub async fn pre_test( - home_dir: PathBuf, -) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - wallet::cli::execute_setup("test_pass".to_owned()).await?; - - let home_dir_sequencer = home_dir.join("sequencer"); - - let mut sequencer_config = - sequencer_runner::config::from_file(home_dir_sequencer.join("sequencer_config.json")) - .unwrap(); - - let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config); - - let (seq_http_server_handle, sequencer_loop_handle) = - startup_sequencer(sequencer_config).await?; - - Ok(( - seq_http_server_handle, - sequencer_loop_handle, - temp_dir_sequencer, - )) -} - -pub fn replace_home_dir_with_temp_dir_in_configs( - sequencer_config: &mut SequencerConfig, -) -> TempDir { - let temp_dir_sequencer = tempfile::tempdir().unwrap(); - - sequencer_config.home = temp_dir_sequencer.path().to_path_buf(); - - temp_dir_sequencer -} - -#[allow(clippy::type_complexity)] -pub async fn post_test(residual: (ServerHandle, JoinHandle>, TempDir)) { - let (seq_http_server_handle, sequencer_loop_handle, _) = residual; - - info!("Cleanup"); - - sequencer_loop_handle.abort(); - seq_http_server_handle.stop(true).await; - - let wallet_home = wallet::helperfunctions::get_home().unwrap(); - let persistent_data_home = wallet_home.join("storage.json"); - - // Removing persistent accounts after run to not affect other executions - // Not necessary an error, if fails as there is tests for failure scenario - let _ = std::fs::remove_file(persistent_data_home) - .inspect_err(|err| warn!("Failed to remove persistent data with err {err:#?}")); - - // At this point all of the references to sequencer_core must be lost. - // So they are dropped and tempdirs will be dropped too, -} - -pub async fn main_tests_runner() -> Result<()> { - env_logger::init(); - - let args = Args::parse(); - let Args { - home_dir, - test_name, - } = args; - - let function_map = prepare_function_map(); - - match test_name.as_str() { - "all" => { - // Tests that use default config - for (_, fn_pointer) in function_map { - fn_pointer(home_dir.clone()).await; - } - // Run TPS test with its own specific config - tps_test().await; - } - _ => { - let fn_pointer = function_map.get(&test_name).expect("Unknown test name"); - - fn_pointer(home_dir.clone()).await; - } - } - - Ok(()) -} - -async fn fetch_privacy_preserving_tx( +pub async fn fetch_privacy_preserving_tx( seq_client: &SequencerClient, tx_hash: String, ) -> PrivacyPreservingTransaction { @@ -161,7 +225,7 @@ async fn fetch_privacy_preserving_tx( } } -async fn verify_commitment_is_in_state( +pub async fn verify_commitment_is_in_state( commitment: Commitment, seq_client: &SequencerClient, ) -> bool { @@ -173,15 +237,15 @@ async fn verify_commitment_is_in_state( #[cfg(test)] mod tests { - use crate::{make_private_account_input_from_str, make_public_account_input_from_str}; + use super::{format_private_account_id, format_public_account_id}; #[test] fn correct_account_id_from_prefix() { let account_id1 = "cafecafe"; let account_id2 = "deadbeaf"; - let account_id1_pub = make_public_account_input_from_str(account_id1); - let account_id2_priv = make_private_account_input_from_str(account_id2); + let account_id1_pub = format_public_account_id(account_id1); + let account_id2_priv = format_private_account_id(account_id2); assert_eq!(account_id1_pub, "Public/cafecafe".to_string()); assert_eq!(account_id2_priv, "Private/deadbeaf".to_string()); diff --git a/integration_tests/src/main.rs b/integration_tests/src/main.rs deleted file mode 100644 index 583df2aa..00000000 --- a/integration_tests/src/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anyhow::Result; -use integration_tests::main_tests_runner; - -pub const NUM_THREADS: usize = 8; - -fn main() -> Result<()> { - actix::System::with_tokio_rt(|| { - tokio::runtime::Builder::new_multi_thread() - .worker_threads(NUM_THREADS) - .enable_all() - .build() - .unwrap() - }) - .block_on(main_tests_runner()) -} diff --git a/integration_tests/src/test_suite_map.rs b/integration_tests/src/test_suite_map.rs deleted file mode 100644 index 8012e17e..00000000 --- a/integration_tests/src/test_suite_map.rs +++ /dev/null @@ -1,2121 +0,0 @@ -use std::{ - collections::HashMap, - path::PathBuf, - pin::Pin, - str::FromStr, - time::{Duration, Instant}, -}; - -use actix_web::dev::ServerHandle; -use anyhow::Result; -use common::{PINATA_BASE58, sequencer_client::SequencerClient}; -use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use log::info; -use nssa::{AccountId, program::Program}; -use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; -use sequencer_runner::startup_sequencer; -use tempfile::TempDir; -use tokio::task::JoinHandle; -use wallet::{ - WalletCore, - cli::{ - Command, SubcommandReturnValue, - account::{AccountSubcommand, NewSubcommand}, - config::ConfigSubcommand, - programs::{ - native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, - token::TokenProgramAgnosticSubcommand, - }, - }, - config::PersistentStorage, - helperfunctions::{fetch_config, fetch_persistent_storage}, -}; - -use crate::{ - ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE, - NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, - fetch_privacy_preserving_tx, make_private_account_input_from_str, - make_public_account_input_from_str, post_test, pre_test, - replace_home_dir_with_temp_dir_in_configs, tps_test_utils::TpsTestManager, - verify_commitment_is_in_state, -}; - -type TestFunction = fn(PathBuf) -> Pin>>; - -pub fn prepare_function_map() -> HashMap { - let mut function_map: HashMap = HashMap::new(); - - #[nssa_integration_test] - pub async fn test_success() { - info!("########## test_success ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 20100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_move_to_another_account() { - info!("########## test_success_move_to_another_account ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let PersistentStorage { - accounts: persistent_accounts, - last_synced_block: _, - } = fetch_persistent_storage().await.unwrap(); - - let mut new_persistent_account_id = String::new(); - - for per_acc in persistent_accounts { - if (per_acc.account_id().to_string() != ACC_RECEIVER) - && (per_acc.account_id().to_string() != ACC_SENDER) - { - new_persistent_account_id = per_acc.account_id().to_string(); - } - } - - if new_persistent_account_id == String::new() { - panic!("Failed to produce new account, not present in persistent accounts"); - } - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str( - &new_persistent_account_id, - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(new_persistent_account_id) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_failure() { - info!("########## test_failure ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 1000000, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let failed_send = wallet::cli::execute_subcommand(command).await; - - assert!(failed_send.is_err()); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 10000); - assert_eq!(acc_2_balance.balance, 20000); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_two_transactions() { - info!("########## test_success_two_transactions ##########"); - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9900); - assert_eq!(acc_2_balance.balance, 20100); - - info!("First TX Success!"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(ACC_SENDER), - to: Some(make_public_account_input_from_str(ACC_RECEIVER)), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let acc_1_balance = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap(); - let acc_2_balance = seq_client - .get_account_balance(ACC_RECEIVER.to_string()) - .await - .unwrap(); - - info!("Balance of sender : {acc_1_balance:#?}"); - info!("Balance of receiver : {acc_2_balance:#?}"); - - assert_eq!(acc_1_balance.balance, 9800); - assert_eq!(acc_2_balance.balance, 20200); - - info!("Second TX Success!"); - } - - #[nssa_integration_test] - pub async fn test_get_account() { - info!("########## test_get_account ##########"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let account = seq_client - .get_account(ACC_SENDER.to_string()) - .await - .unwrap() - .account; - - assert_eq!( - account.program_owner, - Program::authenticated_transfer_program().id() - ); - assert_eq!(account.balance, 10000); - assert!(account.data.is_empty()); - assert_eq!(account.nonce, 0); - } - - /// This test creates a new token using the token program. After creating the token, the test - /// executes a token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program() { - info!("########## test_success_token_program ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // Check the status of the token holding account with the total supply is the expected after - // the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 - // bytes) ] First byte of the data equal to 1 means it's a token holding account - assert_eq!(supply_acc.data.as_ref()[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - // In this example, this is a token account of the newly created token, so it is expected - // to be equal to the account_id of the token definition account. - assert_eq!( - &supply_acc.data.as_ref()[1..33], - definition_account_id.to_bytes() - ); - assert_eq!( - u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), - 37 - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Check the status of the account at `supply_account_id` is the expected after the - // execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - // The account must be owned by the token program - assert_eq!(supply_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(supply_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(supply_acc.data[33..].try_into().unwrap()), - 30 - ); - - // Check the status of the account at `recipient_account_id` is the expected after the - // execution - let recipient_acc = seq_client - .get_account(recipient_account_id.to_string()) - .await - .unwrap() - .account; - - // The account must be owned by the token program - assert_eq!(recipient_acc.program_owner, Program::token().id()); - // First byte equal to 1 means it's a token holding account - assert_eq!(recipient_acc.data[0], 1); - // Bytes from 1 to 33 represent the id of the token this account is associated with. - assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes()); - assert_eq!( - u128::from_le_bytes(recipient_acc.data[33..].try_into().unwrap()), - 7 - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. All accounts are private owned - /// except definition which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_supply() { - info!("########## test_success_token_program_private_owned_supply ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned except supply which is public. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition() { - info!("########## test_success_token_program_private_owned_definition ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Check the status of the token definition account is the expected after the execution - let supply_acc = seq_client - .get_account(supply_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - /// This test creates a new private token using the token program. All accounts are private - /// owned. - #[nssa_integration_test] - pub async fn test_success_token_program_private_owned_definition_and_supply() { - info!( - "########## test_success_token_program_private_owned_definition_and_supply ##########" - ); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (private) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { - cci: Some(ChainIndex::root()), - }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_private_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&definition_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - let definition_acc = wallet_storage - .get_account_private(&definition_account_id) - .unwrap(); - let supply_acc = wallet_storage - .get_account_private(&supply_account_id) - .unwrap(); - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - assert_eq!(supply_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - supply_acc.data.as_ref(), - &[ - 1, 128, 101, 5, 31, 43, 36, 97, 108, 164, 92, 25, 157, 173, 5, 14, 194, 121, 239, - 84, 19, 160, 243, 47, 193, 2, 250, 247, 232, 253, 191, 232, 173, 37, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a private token transfer to a new account. - #[nssa_integration_test] - pub async fn test_success_token_program_private_claiming_path() { - info!("########## test_success_token_program_private_claiming_path ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let (recipient_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&recipient_account_id) - .unwrap(); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: None, - to_npk: Some(hex::encode(recipient_keys.nullifer_public_key.0)), - to_ipk: Some(hex::encode( - recipient_keys.incoming_viewing_public_key.0.clone(), - )), - amount: 7, - }; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new public token using the token program. After creating the token, the - /// test executes a shielded token transfer to a new account. All accounts are owned except - /// definition. - #[nssa_integration_test] - pub async fn test_success_token_program_shielded_owned() { - info!("########## test_success_token_program_shielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (public) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_public_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_public_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_private_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment2 = wallet_storage - .get_private_account_commitment(&recipient_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - } - - /// This test creates a new private token using the token program. After creating the token, the - /// test executes a deshielded token transfer to a new account. All accounts are owned - /// except definition. - #[nssa_integration_test] - pub async fn test_success_token_program_deshielded_owned() { - info!("########## test_success_token_program_deshielded_owned ##########"); - let wallet_config = fetch_config().await.unwrap(); - - // Create new account for the token definition (public) - let SubcommandReturnValue::RegisterAccount { - account_id: definition_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: supply_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - // Create new account for receiving a token transaction - let SubcommandReturnValue::RegisterAccount { - account_id: recipient_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Public { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - // Create new token - let subcommand = TokenProgramAgnosticSubcommand::New { - definition_account_id: make_public_account_input_from_str( - &definition_account_id.to_string(), - ), - supply_account_id: make_private_account_input_from_str(&supply_account_id.to_string()), - name: "A NAME".to_string(), - total_supply: 37, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - // Check the status of the token definition account is the expected after the execution - let definition_acc = seq_client - .get_account(definition_account_id.to_string()) - .await - .unwrap() - .account; - - assert_eq!(definition_acc.program_owner, Program::token().id()); - // The data of a token definition account has the following layout: - // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) ] - assert_eq!( - definition_acc.data.as_ref(), - &[ - 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - // Transfer additional 7 tokens from `supply_acc` to the account at account_id - // `recipient_account_id` - let subcommand = TokenProgramAgnosticSubcommand::Send { - from: make_private_account_input_from_str(&supply_account_id.to_string()), - to: Some(make_public_account_input_from_str( - &recipient_account_id.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 7, - }; - - wallet::cli::execute_subcommand(Command::Token(subcommand)) - .await - .unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&supply_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account() { - info!("########## test_success_private_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - let new_commitment2 = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment2, &seq_client).await); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_foreign_account() { - info!("########## test_success_private_transfer_to_another_foreign_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to_npk = NullifierPublicKey([42; 32]); - let to_npk_string = hex::encode(to_npk.0); - let to_ipk = Secp256k1Point::from_scalar(to_npk.0); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - assert_eq!(tx.message.new_commitments[0], new_commitment1); - - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments.into_iter() { - assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - } - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_private_transfer_to_another_owned_account_claiming_path() { - info!( - "########## test_success_private_transfer_to_another_owned_account_claiming_path ##########" - ); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = - Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let (to_keys, _) = wallet_storage - .storage - .user_data - .get_private_account(&to_account_id) - .cloned() - .unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), - to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), - amount: 100, - }); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - panic!("FAILED TO SEND TX"); - }; - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert_eq!(tx.message.new_commitments[0], new_commitment1); - - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments.into_iter() { - assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - } - - let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - assert_eq!(to_res_acc.balance, 100); - - info!("Success!"); - } - - // #[nssa_integration_test] - // pub async fn test_success_private_transfer_to_another_owned_account_cont_run_path() { - // info!( - // "########## test_success_private_transfer_to_another_owned_account_cont_run_path - // ##########" ); - // let continious_run_handle = tokio::spawn(wallet::cli::execute_continious_run()); - - // let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - // let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private {})); - - // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - // let SubcommandReturnValue::RegisterAccount { - // account_id: to_account_id, - // } = sub_ret - // else { - // panic!("FAILED TO REGISTER ACCOUNT"); - // }; - - // let wallet_config = fetch_config().await.unwrap(); - // let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - // .await - // .unwrap(); - - // let (to_keys, _) = wallet_storage - // .storage - // .user_data - // .user_private_accounts - // .get(&to_account_id) - // .cloned() - // .unwrap(); - - // let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - // from: make_private_account_input_from_str(&from.to_string()), - // to: None, - // to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), - // to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), - // amount: 100, - // }); - - // let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - // let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { - // panic!("FAILED TO SEND TX"); - // }; - - // let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash.clone()).await; - // println!("Waiting for next blocks to check if continoius run fetch account"); - // tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - // tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - // .await - // .unwrap(); - - // assert_eq!(tx.message.new_commitments.len(), 2); - // for commitment in tx.message.new_commitments.into_iter() { - // assert!(verify_commitment_is_in_state(commitment, &seq_client).await); - // } - - // let to_res_acc = wallet_storage.get_account_private(&to_account_id).unwrap(); - - // assert_eq!(to_res_acc.balance, 100); - - // continious_run_handle.abort(); - - // info!("Success!"); - // } - - #[nssa_integration_test] - pub async fn test_success_deshielded_transfer_to_another_account() { - info!("########## test_success_deshielded_transfer_to_another_account ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - let to: AccountId = ACC_RECEIVER.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - assert_eq!(from_acc.balance, 10000); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let from_acc = wallet_storage.get_account_private(&from).unwrap(); - let new_commitment = wallet_storage - .get_private_account_commitment(&from) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_2_balance = seq_client - .get_account_balance(to.to_string()) - .await - .unwrap(); - - assert_eq!(from_acc.balance, 10000 - 100); - assert_eq!(acc_2_balance.balance, 20100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_owned_account() { - info!("########## test_success_shielded_transfer_to_another_owned_account ##########"); - let from: AccountId = ACC_SENDER.parse().unwrap(); - let to: AccountId = ACC_RECEIVER_PRIVATE.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str(&to.to_string())), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let acc_to = wallet_storage.get_account_private(&to).unwrap(); - let new_commitment = wallet_storage.get_private_account_commitment(&to).unwrap(); - assert!(verify_commitment_is_in_state(new_commitment, &seq_client).await); - - let acc_from_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert_eq!(acc_from_balance.balance, 9900); - assert_eq!(acc_to.balance, 20000 + 100); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_success_shielded_transfer_to_another_foreign_account() { - info!("########## test_success_shielded_transfer_to_another_foreign_account ##########"); - let to_npk = NullifierPublicKey([42; 32]); - let to_npk_string = hex::encode(to_npk.0); - let to_ipk = Secp256k1Point::from_scalar(to_npk.0); - let from: AccountId = ACC_SENDER.parse().unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: None, - to_npk: Some(to_npk_string), - to_ipk: Some(hex::encode(to_ipk.0)), - amount: 100, - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let tx = fetch_privacy_preserving_tx(&seq_client, tx_hash).await; - - let acc_1_balance = seq_client - .get_account_balance(from.to_string()) - .await - .unwrap(); - - assert!( - verify_commitment_is_in_state(tx.message.new_commitments[0].clone(), &seq_client).await - ); - - assert_eq!(acc_1_balance.balance, 9900); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata() { - info!("########## test_pinata ##########"); - let pinata_account_id = PINATA_BASE58; - - let pinata_prize = 150; - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_public_account_input_from_str(ACC_SENDER), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let winner_balance_post = seq_client - .get_account_balance(ACC_SENDER.to_string()) - .await - .unwrap() - .balance; - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - assert_eq!(winner_balance_post, 10000 + pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_program_deployment() { - info!("########## test program deployment ##########"); - - let binary_filepath: PathBuf = NSSA_PROGRAM_FOR_TEST_DATA_CHANGER.parse().unwrap(); - - let command = Command::DeployProgram { - binary_filepath: binary_filepath.clone(), - }; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - 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 (NSSA account claiming mechanism) with data equal to [0] (due to program - // logic) - // - let bytecode = std::fs::read(binary_filepath).unwrap(); - let data_changer = Program::new(bytecode).unwrap(); - let account_id: AccountId = "11".repeat(16).parse().unwrap(); - let message = nssa::public_transaction::Message::try_new( - data_changer.id(), - vec![account_id], - vec![], - vec![0], - ) - .unwrap(); - let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); - let transaction = nssa::PublicTransaction::new(message, witness_set); - let _response = seq_client.send_tx_public(transaction).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - let post_state_account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .account; - assert_eq!(post_state_account.program_owner, data_changer.id()); - assert_eq!(post_state_account.balance, 0); - assert_eq!(post_state_account.data.as_ref(), &[0]); - assert_eq!(post_state_account.nonce, 0); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_authenticated_transfer_initialize_function() { - info!("########## test initialize account for authenticated transfer ##########"); - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); - let SubcommandReturnValue::RegisterAccount { account_id } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("Error creating account"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: make_public_account_input_from_str(&account_id.to_string()), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Checking correct execution"); - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let account = seq_client - .get_account(account_id.to_string()) - .await - .unwrap() - .account; - - let expected_program_owner = Program::authenticated_transfer_program().id(); - let expected_nonce = 1; - let expected_balance = 0; - - assert_eq!(account.program_owner, expected_program_owner); - assert_eq!(account.balance, expected_balance); - assert_eq!(account.nonce, expected_nonce); - assert!(account.data.is_empty()); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver() { - info!("########## test_pinata_private_receiver ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(ACC_SENDER_PRIVATE), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = - wallet::cli::execute_subcommand(command).await.unwrap() - else { - panic!("invalid subcommand return value"); - }; - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let command = Command::Account(AccountSubcommand::SyncPrivate {}); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse().unwrap()) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_pinata_private_receiver_new_account() { - info!("########## test_pinata_private_receiver_new_account ##########"); - let pinata_account_id = PINATA_BASE58; - let pinata_prize = 150; - - // Create new account for the token supply holder (private) - let SubcommandReturnValue::RegisterAccount { - account_id: winner_account_id, - } = wallet::cli::execute_subcommand(Command::Account(AccountSubcommand::New( - NewSubcommand::Private { cci: None }, - ))) - .await - .unwrap() - else { - panic!("invalid subcommand return value"); - }; - - let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: make_private_account_input_from_str(&winner_account_id.to_string()), - }); - - let wallet_config = fetch_config().await.unwrap(); - - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - let pinata_balance_pre = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Waiting for next block creation"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - info!("Checking correct balance move"); - let pinata_balance_post = seq_client - .get_account_balance(pinata_account_id.to_string()) - .await - .unwrap() - .balance; - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config) - .await - .unwrap(); - - let new_commitment1 = wallet_storage - .get_private_account_commitment(&winner_account_id) - .unwrap(); - assert!(verify_commitment_is_in_state(new_commitment1, &seq_client).await); - - assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_modify_config_fields() { - info!("########## test_modify_config_fields ##########"); - - let wallet_config = fetch_config().await.unwrap(); - let old_seq_poll_timeout_millis = wallet_config.seq_poll_timeout_millis; - - // Change config field - let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout_millis".to_string(), - value: "1000".to_string(), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - - assert_eq!(wallet_config.seq_poll_timeout_millis, 1000); - - // Return how it was at the beginning - let command = Command::Config(ConfigSubcommand::Set { - key: "seq_poll_timeout_millis".to_string(), - value: old_seq_poll_timeout_millis.to_string(), - }); - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("Success!"); - } - - #[nssa_integration_test] - pub async fn test_keys_restoration() { - info!("########## test_keys_restoration ##########"); - let from: AccountId = ACC_SENDER_PRIVATE.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id1, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id2, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id1.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 100, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&from.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 101, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let from: AccountId = ACC_SENDER.parse().unwrap(); - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::root()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id3, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { - cci: Some(ChainIndex::from_str("/0").unwrap()), - })); - - let sub_ret = wallet::cli::execute_subcommand(command).await.unwrap(); - let SubcommandReturnValue::RegisterAccount { - account_id: to_account_id4, - } = sub_ret - else { - panic!("FAILED TO REGISTER ACCOUNT"); - }; - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id3.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 102, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&from.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 103, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - info!("########## PREPARATION END ##########"); - - wallet::cli::execute_keys_restoration("test_pass".to_string(), 10) - .await - .unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let acc1 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id1) - .expect("Acc 1 should be restored"); - - let acc2 = wallet_storage - .storage - .user_data - .private_key_tree - .get_node(to_account_id2) - .expect("Acc 2 should be restored"); - - let _ = wallet_storage - .storage - .user_data - .public_key_tree - .get_node(to_account_id3) - .expect("Acc 3 should be restored"); - - let _ = wallet_storage - .storage - .user_data - .public_key_tree - .get_node(to_account_id4) - .expect("Acc 4 should be restored"); - - assert_eq!( - acc1.value.1.program_owner, - Program::authenticated_transfer_program().id() - ); - assert_eq!( - acc2.value.1.program_owner, - Program::authenticated_transfer_program().id() - ); - - assert_eq!(acc1.value.1.balance, 100); - assert_eq!(acc2.value.1.balance, 101); - - info!("########## TREE CHECKS END ##########"); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_private_account_input_from_str(&to_account_id1.to_string()), - to: Some(make_private_account_input_from_str( - &to_account_id2.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 10, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: make_public_account_input_from_str(&to_account_id3.to_string()), - to: Some(make_public_account_input_from_str( - &to_account_id4.to_string(), - )), - to_npk: None, - to_ipk: None, - amount: 11, - }); - - wallet::cli::execute_subcommand(command).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - let wallet_storage = WalletCore::start_from_config_update_chain(wallet_config.clone()) - .await - .unwrap(); - - let comm1 = wallet_storage - .get_private_account_commitment(&to_account_id1) - .expect("Acc 1 commitment should exist"); - let comm2 = wallet_storage - .get_private_account_commitment(&to_account_id2) - .expect("Acc 2 commitment should exist"); - - assert!(verify_commitment_is_in_state(comm1, &seq_client).await); - assert!(verify_commitment_is_in_state(comm2, &seq_client).await); - - let acc3 = seq_client - .get_account_balance(to_account_id3.to_string()) - .await - .expect("Acc 3 must be present in public state"); - let acc4 = seq_client - .get_account_balance(to_account_id4.to_string()) - .await - .expect("Acc 4 must be present in public state"); - - assert_eq!(acc3.balance, 91); - assert_eq!(acc4.balance, 114); - - info!("Success!"); - } - - println!("{function_map:#?}"); - - function_map -} - -#[allow(clippy::type_complexity)] -async fn pre_tps_test( - test: &TpsTestManager, -) -> Result<(ServerHandle, JoinHandle>, TempDir)> { - info!("Generating tps test config"); - let mut sequencer_config = test.generate_tps_test_config(); - info!("Done"); - - let temp_dir_sequencer = replace_home_dir_with_temp_dir_in_configs(&mut sequencer_config); - - let (seq_http_server_handle, sequencer_loop_handle) = - startup_sequencer(sequencer_config).await?; - - Ok(( - seq_http_server_handle, - sequencer_loop_handle, - temp_dir_sequencer, - )) -} - -pub async fn tps_test() { - let num_transactions = 300 * 5; - let target_tps = 12; - let tps_test = TpsTestManager::new(target_tps, num_transactions); - - let target_time = tps_test.target_time(); - info!("Target time: {:?} seconds", target_time.as_secs()); - let res = pre_tps_test(&tps_test).await.unwrap(); - - let wallet_config = fetch_config().await.unwrap(); - let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap(); - - info!("TPS test begin"); - let txs = tps_test.build_public_txs(); - let now = Instant::now(); - - let mut tx_hashes = vec![]; - for (i, tx) in txs.into_iter().enumerate() { - let tx_hash = seq_client.send_tx_public(tx).await.unwrap().tx_hash; - info!("Sent tx {i}"); - tx_hashes.push(tx_hash); - } - - for (i, tx_hash) in tx_hashes.iter().enumerate() { - loop { - if now.elapsed().as_millis() > target_time.as_millis() { - panic!("TPS test failed by timeout"); - } - - let tx_obj = seq_client - .get_transaction_by_hash(tx_hash.clone()) - .await - .inspect_err(|err| { - log::warn!( - "Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}" - ) - }); - - if let Ok(tx_obj) = tx_obj - && tx_obj.transaction.is_some() - { - info!("Found tx {i} with hash {tx_hash}"); - break; - } - } - } - let time_elapsed = now.elapsed().as_secs(); - - info!("TPS test finished successfully"); - info!("Target TPS: {}", target_tps); - info!( - "Processed {} transactions in {}s", - tx_hashes.len(), - time_elapsed - ); - info!("Target time: {:?}s", target_time.as_secs()); - - post_test(res).await; -} diff --git a/integration_tests/tests/account.rs b/integration_tests/tests/account.rs new file mode 100644 index 00000000..e5e700dd --- /dev/null +++ b/integration_tests/tests/account.rs @@ -0,0 +1,28 @@ +use anyhow::Result; +use integration_tests::{ACC_SENDER, TestContext}; +use log::info; +use nssa::program::Program; +use tokio::test; + +#[test] +async fn get_existing_account() -> Result<()> { + let ctx = TestContext::new().await?; + + let account = ctx + .sequencer_client() + .get_account(ACC_SENDER.to_string()) + .await? + .account; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 10000); + assert!(account.data.is_empty()); + assert_eq!(account.nonce, 0); + + info!("Successfully retrieved account with correct details"); + + Ok(()) +} diff --git a/integration_tests/tests/amm.rs b/integration_tests/tests/amm.rs new file mode 100644 index 00000000..073cd03d --- /dev/null +++ b/integration_tests/tests/amm.rs @@ -0,0 +1,405 @@ +use std::time::Duration; + +use anyhow::Result; +use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id}; +use log::info; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::{amm::AmmProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand}, +}; + +#[test] +async fn amm_public() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_1, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token definition + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_2, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id_1.to_string()), + supply_account_id: format_public_account_id(&supply_account_id_1.to_string()), + name: "A NAM1".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_1` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id_1.to_string()), + to: Some(format_public_account_id( + &recipient_account_id_1.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id_2.to_string()), + supply_account_id: format_public_account_id(&supply_account_id_2.to_string()), + name: "A NAM2".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Transfer 7 tokens from `supply_acc` to the account at account_id `recipient_account_id_2` + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id_2.to_string()), + to: Some(format_public_account_id( + &recipient_account_id_2.to_string(), + )), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("=================== SETUP FINISHED ==============="); + + // Create new AMM + + // Setup accounts + // Create new account for the user holding lp + let SubcommandReturnValue::RegisterAccount { + account_id: user_holding_lp, + } = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await? + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send creation tx + let subcommand = AmmProgramAgnosticSubcommand::New { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + balance_a: 3, + balance_b: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== AMM DEFINITION FINISHED ==============="); + + // Make swap + + let subcommand = AmmProgramAgnosticSubcommand::Swap { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + amount_in: 2, + min_amount_out: 1, + token_definition: definition_account_id_1.to_string(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 2 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 5 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== FIRST SWAP FINISHED ==============="); + + // Make swap + + let subcommand = AmmProgramAgnosticSubcommand::Swap { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + amount_in: 2, + min_amount_out: 1, + token_definition: definition_account_id_2.to_string(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 3 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 3 + ); + + info!("=================== SECOND SWAP FINISHED ==============="); + + // Add liquidity + + let subcommand = AmmProgramAgnosticSubcommand::AddLiquidity { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + min_amount_lp: 1, + max_amount_a: 2, + max_amount_b: 2, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 3 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 1 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 4 + ); + + info!("=================== ADD LIQ FINISHED ==============="); + + // Remove liquidity + + let subcommand = AmmProgramAgnosticSubcommand::RemoveLiquidity { + user_holding_a: format_public_account_id(&recipient_account_id_1.to_string()), + user_holding_b: format_public_account_id(&recipient_account_id_2.to_string()), + user_holding_lp: format_public_account_id(&user_holding_lp.to_string()), + balance_lp: 2, + min_amount_a: 1, + min_amount_b: 1, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::AMM(subcommand)).await?; + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let user_holding_a_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_1.to_string()) + .await? + .account; + + let user_holding_b_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_2.to_string()) + .await? + .account; + + let user_holding_lp_acc = ctx + .sequencer_client() + .get_account(user_holding_lp.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(user_holding_a_acc.data[33..].try_into().unwrap()), + 5 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_b_acc.data[33..].try_into().unwrap()), + 4 + ); + + assert_eq!( + u128::from_le_bytes(user_holding_lp_acc.data[33..].try_into().unwrap()), + 2 + ); + + info!("Success!"); + + Ok(()) +} diff --git a/integration_tests/tests/auth_transfer/main.rs b/integration_tests/tests/auth_transfer/main.rs new file mode 100644 index 00000000..c97008bd --- /dev/null +++ b/integration_tests/tests/auth_transfer/main.rs @@ -0,0 +1,2 @@ +mod private; +mod public; diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs new file mode 100644 index 00000000..4674ed0b --- /dev/null +++ b/integration_tests/tests/auth_transfer/private.rs @@ -0,0 +1,417 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use integration_tests::{ + ACC_RECEIVER, ACC_RECEIVER_PRIVATE, ACC_SENDER, ACC_SENDER_PRIVATE, + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, fetch_privacy_preserving_tx, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use log::info; +use nssa::{AccountId, program::Program}; +use nssa_core::{NullifierPublicKey, encryption::shared_key_derivation::Secp256k1Point}; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn private_transfer_to_owned_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&to) + .context("Failed to get private account commitment for receiver")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + info!("Successfully transferred privately to owned account"); + + Ok(()) +} + +#[test] +async fn private_transfer_to_foreign_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to_npk = NullifierPublicKey([42; 32]); + let to_npk_string = hex::encode(to_npk.0); + let to_ipk = Secp256k1Point::from_scalar(to_npk.0); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(to_npk_string), + to_ipk: Some(hex::encode(to_ipk.0)), + amount: 100, + }); + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + assert_eq!(tx.message.new_commitments[0], new_commitment1); + + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + info!("Successfully transferred privately to foreign account"); + + Ok(()) +} + +#[test] +async fn deshielded_transfer_to_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + let to: AccountId = ACC_RECEIVER.parse()?; + + // Check initial balance of the private sender + let from_acc = ctx + .wallet() + .get_account_private(&from) + .context("Failed to get sender's private account")?; + assert_eq!(from_acc.balance, 10000); + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_public_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let from_acc = ctx + .wallet() + .get_account_private(&from) + .context("Failed to get sender's private account")?; + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(to.to_string()) + .await?; + + assert_eq!(from_acc.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + info!("Successfully deshielded transfer to public account"); + + Ok(()) +} + +#[test] +async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create a new private account + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id, + } = sub_ret + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Get the keys for the newly created account + let (to_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&to_account_id) + .cloned() + .context("Failed to get private account")?; + + // Send to this account using claiming path (using npk and ipk instead of account ID) + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), + to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), + amount: 100, + }); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + + // Sync the wallet to claim the new account + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&from) + .context("Failed to get private account commitment for sender")?; + assert_eq!(tx.message.new_commitments[0], new_commitment1); + + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + let to_res_acc = ctx + .wallet() + .get_account_private(&to_account_id) + .context("Failed to get recipient's private account")?; + assert_eq!(to_res_acc.balance, 100); + + info!("Successfully transferred using claiming path"); + + Ok(()) +} + +#[test] +async fn shielded_transfer_to_owned_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER.parse()?; + let to: AccountId = ACC_RECEIVER_PRIVATE.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_private_account_id(&to.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let acc_to = ctx + .wallet() + .get_account_private(&to) + .context("Failed to get receiver's private account")?; + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&to) + .context("Failed to get receiver's commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let acc_from_balance = ctx + .sequencer_client() + .get_account_balance(from.to_string()) + .await?; + + assert_eq!(acc_from_balance.balance, 9900); + assert_eq!(acc_to.balance, 20100); + + info!("Successfully shielded transfer to owned private account"); + + Ok(()) +} + +#[test] +async fn shielded_transfer_to_foreign_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let to_npk = NullifierPublicKey([42; 32]); + let to_npk_string = hex::encode(to_npk.0); + let to_ipk = Secp256k1Point::from_scalar(to_npk.0); + let from: AccountId = ACC_SENDER.parse()?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: None, + to_npk: Some(to_npk_string), + to_ipk: Some(hex::encode(to_ipk.0)), + amount: 100, + }); + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await; + + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(from.to_string()) + .await?; + + assert!( + verify_commitment_is_in_state( + tx.message.new_commitments[0].clone(), + ctx.sequencer_client() + ) + .await + ); + + assert_eq!(acc_1_balance.balance, 9900); + + info!("Successfully shielded transfer to foreign account"); + + Ok(()) +} + +#[test] +#[ignore = "Flaky, TODO: #197"] +async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // NOTE: This test needs refactoring - continuous run mode doesn't work well with TestContext + // The original implementation spawned wallet::cli::execute_continuous_run() in background + // but this conflicts with TestContext's wallet management + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create a new private account + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id, + } = sub_ret + else { + anyhow::bail!("Failed to register account"); + }; + + // Get the newly created account's keys + let (to_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&to_account_id) + .cloned() + .context("Failed to get private account")?; + + // Send transfer using nullifier and incoming viewing public keys + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: None, + to_npk: Some(hex::encode(to_keys.nullifer_public_key.0)), + to_ipk: Some(hex::encode(to_keys.incoming_viewing_public_key.0)), + amount: 100, + }); + + let sub_ret = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash } = sub_ret else { + anyhow::bail!("Failed to send transaction"); + }; + + let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash.clone()).await; + + info!("Waiting for next blocks to check if continuous run fetches account"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify commitments are in state + assert_eq!(tx.message.new_commitments.len(), 2); + for commitment in tx.message.new_commitments.into_iter() { + assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); + } + + // Verify receiver account balance + let to_res_acc = ctx + .wallet() + .get_account_private(&to_account_id) + .context("Failed to get receiver account")?; + + assert_eq!(to_res_acc.balance, 100); + + Ok(()) +} + +#[test] +async fn initialize_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { account_id } = result else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: format_private_account_id(&account_id.to_string()), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Syncing private accounts"); + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let account = ctx + .wallet() + .get_account_private(&account_id) + .context("Failed to get private account")?; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 0); + assert!(account.data.is_empty()); + + info!("Successfully initialized private account"); + + Ok(()) +} diff --git a/integration_tests/tests/auth_transfer/public.rs b/integration_tests/tests/auth_transfer/public.rs new file mode 100644 index 00000000..467e3ebc --- /dev/null +++ b/integration_tests/tests/auth_transfer/public.rs @@ -0,0 +1,248 @@ +use std::time::Duration; + +use anyhow::Result; +use integration_tests::{ + ACC_RECEIVER, ACC_SENDER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id, +}; +use log::info; +use nssa::program::Program; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn successful_transfer_to_existing_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + Ok(()) +} + +#[test] +pub async fn successful_transfer_to_new_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command) + .await + .unwrap(); + + let new_persistent_account_id = ctx + .wallet() + .storage() + .user_data + .account_ids() + .map(ToString::to_string) + .find(|acc_id| acc_id != ACC_SENDER && acc_id != ACC_RECEIVER) + .expect("Failed to find newly created account in the wallet storage"); + + if new_persistent_account_id == String::new() { + panic!("Failed to produce new account, not present in persistent accounts"); + } + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(&new_persistent_account_id)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(new_persistent_account_id) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 100); + + Ok(()) +} + +#[test] +async fn failed_transfer_with_insufficient_balance() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 1000000, + }); + + let failed_send = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await; + assert!(failed_send.is_err()); + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking balances unchanged"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 10000); + assert_eq!(acc_2_balance.balance, 20000); + + Ok(()) +} + +#[test] +async fn two_consecutive_successful_transfers() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // First transfer + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move after first transfer"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9900); + assert_eq!(acc_2_balance.balance, 20100); + + info!("First TX Success!"); + + // Second transfer + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(ACC_SENDER), + to: Some(format_public_account_id(ACC_RECEIVER)), + to_npk: None, + to_ipk: None, + amount: 100, + }); + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move after second transfer"); + let acc_1_balance = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await?; + let acc_2_balance = ctx + .sequencer_client() + .get_account_balance(ACC_RECEIVER.to_string()) + .await?; + + info!("Balance of sender: {acc_1_balance:#?}"); + info!("Balance of receiver: {acc_2_balance:#?}"); + + assert_eq!(acc_1_balance.balance, 9800); + assert_eq!(acc_2_balance.balance, 20200); + + info!("Second TX Success!"); + + Ok(()) +} + +#[test] +async fn initialize_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { account_id } = result else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: format_public_account_id(&account_id.to_string()), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Checking correct execution"); + let account = ctx + .sequencer_client() + .get_account(account_id.to_string()) + .await? + .account; + + assert_eq!( + account.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!(account.balance, 0); + assert_eq!(account.nonce, 1); + assert!(account.data.is_empty()); + + info!("Successfully initialized public account"); + + Ok(()) +} diff --git a/integration_tests/tests/config.rs b/integration_tests/tests/config.rs new file mode 100644 index 00000000..ca800d0f --- /dev/null +++ b/integration_tests/tests/config.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use integration_tests::TestContext; +use log::info; +use tokio::test; +use wallet::cli::{Command, config::ConfigSubcommand}; + +#[test] +async fn modify_config_field() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let old_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis; + + // Change config field + let command = Command::Config(ConfigSubcommand::Set { + key: "seq_poll_timeout_millis".to_string(), + value: "1000".to_string(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_seq_poll_timeout_millis = ctx.wallet().config().seq_poll_timeout_millis; + assert_eq!(new_seq_poll_timeout_millis, 1000); + + // Return how it was at the beginning + let command = Command::Config(ConfigSubcommand::Set { + key: "seq_poll_timeout_millis".to_string(), + value: old_seq_poll_timeout_millis.to_string(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Successfully modified and restored config field"); + + Ok(()) +} diff --git a/integration_tests/tests/keys_restoration.rs b/integration_tests/tests/keys_restoration.rs new file mode 100644 index 00000000..9076c87f --- /dev/null +++ b/integration_tests/tests/keys_restoration.rs @@ -0,0 +1,217 @@ +use std::{str::FromStr, time::Duration}; + +use anyhow::Result; +use integration_tests::{ + ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; +use log::info; +use nssa::{AccountId, program::Program}; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::native_token_transfer::AuthTransferSubcommand, +}; + +#[test] +async fn restore_keys_from_seed() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let from: AccountId = ACC_SENDER_PRIVATE.parse()?; + + // Create first private account at root + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::root()), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id1, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create second private account at /0 + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::from_str("/0")?), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id2, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send to first private account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to_account_id1.to_string())), + to_npk: None, + to_ipk: None, + amount: 100, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Send to second private account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&from.to_string()), + to: Some(format_private_account_id(&to_account_id2.to_string())), + to_npk: None, + to_ipk: None, + amount: 101, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let from: AccountId = ACC_SENDER.parse()?; + + // Create first public account at root + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::root()), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id3, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create second public account at /0 + let command = Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::from_str("/0")?), + })); + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::RegisterAccount { + account_id: to_account_id4, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Send to first public account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_public_account_id(&to_account_id3.to_string())), + to_npk: None, + to_ipk: None, + amount: 102, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Send to second public account + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&from.to_string()), + to: Some(format_public_account_id(&to_account_id4.to_string())), + to_npk: None, + to_ipk: None, + amount: 103, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Preparation complete, performing keys restoration"); + + // Restore keys from seed + wallet::cli::execute_keys_restoration(ctx.wallet_mut(), 10).await?; + + // Verify restored private accounts + let acc1 = ctx + .wallet() + .storage() + .user_data + .private_key_tree + .get_node(to_account_id1) + .expect("Acc 1 should be restored"); + + let acc2 = ctx + .wallet() + .storage() + .user_data + .private_key_tree + .get_node(to_account_id2) + .expect("Acc 2 should be restored"); + + // Verify restored public accounts + let _acc3 = ctx + .wallet() + .storage() + .user_data + .public_key_tree + .get_node(to_account_id3) + .expect("Acc 3 should be restored"); + + let _acc4 = ctx + .wallet() + .storage() + .user_data + .public_key_tree + .get_node(to_account_id4) + .expect("Acc 4 should be restored"); + + assert_eq!( + acc1.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + assert_eq!( + acc2.value.1.program_owner, + Program::authenticated_transfer_program().id() + ); + + assert_eq!(acc1.value.1.balance, 100); + assert_eq!(acc2.value.1.balance, 101); + + info!("Tree checks passed, testing restored accounts can transact"); + + // Test that restored accounts can send transactions + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_private_account_id(&to_account_id1.to_string()), + to: Some(format_private_account_id(&to_account_id2.to_string())), + to_npk: None, + to_ipk: None, + amount: 10, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let command = Command::AuthTransfer(AuthTransferSubcommand::Send { + from: format_public_account_id(&to_account_id3.to_string()), + to: Some(format_public_account_id(&to_account_id4.to_string())), + to_npk: None, + to_ipk: None, + amount: 11, + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify commitments exist for private accounts + let comm1 = ctx + .wallet() + .get_private_account_commitment(&to_account_id1) + .expect("Acc 1 commitment should exist"); + let comm2 = ctx + .wallet() + .get_private_account_commitment(&to_account_id2) + .expect("Acc 2 commitment should exist"); + + assert!(verify_commitment_is_in_state(comm1, ctx.sequencer_client()).await); + assert!(verify_commitment_is_in_state(comm2, ctx.sequencer_client()).await); + + // Verify public account balances + let acc3 = ctx + .sequencer_client() + .get_account_balance(to_account_id3.to_string()) + .await?; + let acc4 = ctx + .sequencer_client() + .get_account_balance(to_account_id4.to_string()) + .await?; + + assert_eq!(acc3.balance, 91); // 102 - 11 + assert_eq!(acc4.balance, 114); // 103 + 11 + + info!("Successfully restored keys and verified transactions"); + + Ok(()) +} diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs new file mode 100644 index 00000000..a3f2b4b7 --- /dev/null +++ b/integration_tests/tests/pinata.rs @@ -0,0 +1,175 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use common::PINATA_BASE58; +use integration_tests::{ + ACC_SENDER, ACC_SENDER_PRIVATE, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, + format_private_account_id, format_public_account_id, verify_commitment_is_in_state, +}; +use log::info; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::{ + native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, + }, +}; + +#[test] +async fn claim_pinata_to_existing_public_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: format_public_account_id(ACC_SENDER), + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Checking correct balance move"); + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + let winner_balance_post = ctx + .sequencer_client() + .get_account_balance(ACC_SENDER.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + assert_eq!(winner_balance_post, 10000 + pinata_prize); + + info!("Successfully claimed pinata to public account"); + + Ok(()) +} + +#[test] +async fn claim_pinata_to_existing_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: format_private_account_id(ACC_SENDER_PRIVATE), + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + let SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash: _ } = result else { + anyhow::bail!("Expected PrivacyPreservingTransfer return value"); + }; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + info!("Syncing private accounts"); + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&ACC_SENDER_PRIVATE.parse()?) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + + info!("Successfully claimed pinata to existing private account"); + + Ok(()) +} + +#[test] +async fn claim_pinata_to_new_private_account() -> Result<()> { + let mut ctx = TestContext::new().await?; + + let pinata_prize = 150; + + // Create new private account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: winner_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + let winner_account_id_formatted = format_private_account_id(&winner_account_id.to_string()); + + // Initialize account under auth transfer program + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: winner_account_id_formatted.clone(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&winner_account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Claim pinata to the new private account + let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { + to: winner_account_id_formatted, + }); + + let pinata_balance_pre = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&winner_account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + let pinata_balance_post = ctx + .sequencer_client() + .get_account_balance(PINATA_BASE58.to_string()) + .await? + .balance; + + assert_eq!(pinata_balance_post, pinata_balance_pre - pinata_prize); + + info!("Successfully claimed pinata to new private account"); + + Ok(()) +} diff --git a/integration_tests/tests/program_deployment.rs b/integration_tests/tests/program_deployment.rs new file mode 100644 index 00000000..25771ec2 --- /dev/null +++ b/integration_tests/tests/program_deployment.rs @@ -0,0 +1,64 @@ +use std::{path::PathBuf, time::Duration}; + +use anyhow::Result; +use integration_tests::{ + NSSA_PROGRAM_FOR_TEST_DATA_CHANGER, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, +}; +use log::info; +use nssa::{AccountId, program::Program}; +use tokio::test; +use wallet::cli::Command; + +#[test] +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(NSSA_PROGRAM_FOR_TEST_DATA_CHANGER); + + let command = Command::DeployProgram { + binary_filepath: binary_filepath.clone(), + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + 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 (NSSA 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 account_id: AccountId = "11".repeat(16).parse()?; + let message = nssa::public_transaction::Message::try_new( + data_changer.id(), + vec![account_id], + vec![], + vec![0], + )?; + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let transaction = nssa::PublicTransaction::new(message, witness_set); + let _response = ctx.sequencer_client().send_tx_public(transaction).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let post_state_account = ctx + .sequencer_client() + .get_account(account_id.to_string()) + .await? + .account; + + assert_eq!(post_state_account.program_owner, data_changer.id()); + assert_eq!(post_state_account.balance, 0); + assert_eq!(post_state_account.data.as_ref(), &[0]); + assert_eq!(post_state_account.nonce, 0); + + info!("Successfully deployed and executed program"); + + Ok(()) +} diff --git a/integration_tests/tests/token.rs b/integration_tests/tests/token.rs new file mode 100644 index 00000000..9a8b714a --- /dev/null +++ b/integration_tests/tests/token.rs @@ -0,0 +1,968 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use integration_tests::{ + TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_private_account_id, + format_public_account_id, verify_commitment_is_in_state, +}; +use key_protocol::key_management::key_tree::chain_index::ChainIndex; +use log::info; +use nssa::program::Program; +use tokio::test; +use wallet::cli::{ + Command, SubcommandReturnValue, + account::{AccountSubcommand, NewSubcommand}, + programs::token::TokenProgramAgnosticSubcommand, +}; + +#[test] +async fn create_and_transfer_public_token() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + // The data of a token definition account has the following layout: + // [ 0x00 || name (6 bytes) || total supply (little endian 16 bytes) || metadata id (32 bytes)] + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the token holding account with the total supply + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + + // The account must be owned by the token program + assert_eq!(supply_acc.program_owner, Program::token().id()); + // The data of a token holding account has the following layout: + // [ 0x01 || corresponding_token_definition_id (32 bytes) || balance (little endian 16 bytes) ] + // First byte of the data equal to 1 means it's a token holding account + assert_eq!(supply_acc.data.as_ref()[0], 1); + // Bytes from 1 to 33 represent the id of the token this account is associated with + assert_eq!( + &supply_acc.data.as_ref()[1..33], + definition_account_id.to_bytes() + ); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Transfer 7 tokens from supply_acc to recipient_account_id + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id.to_string()), + to: Some(format_public_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the supply account after transfer + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.data[0], 1); + assert_eq!(&supply_acc.data[1..33], definition_account_id.to_bytes()); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Check the status of the recipient account after transfer + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + assert_eq!(recipient_acc.program_owner, Program::token().id()); + assert_eq!(recipient_acc.data[0], 1); + assert_eq!(&recipient_acc.data[1..33], definition_account_id.to_bytes()); + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + // Burn 3 tokens from recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Burn { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: format_public_account_id(&recipient_account_id.to_string()), + amount: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account after burn + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the recipient account after burn + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 4); + + // Mint 10 tokens at recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: Some(format_public_account_id(&recipient_account_id.to_string())), + holder_npk: None, + holder_ipk: None, + amount: 10, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account after mint + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + // Check the status of the recipient account after mint + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 14 + ); + + info!("Successfully created and transferred public token"); + + Ok(()) +} + +#[test] +async fn create_and_transfer_token_with_private_supply() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create new account for the token definition (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for the token supply holder (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new account for receiving a token transaction (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create new token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the status of the token definition account + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!(definition_acc.program_owner, Program::token().id()); + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + // Transfer 7 tokens from supply_acc to recipient_account_id + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment1 = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment1, ctx.sequencer_client()).await); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + // Burn 3 tokens from recipient_acc + let subcommand = TokenProgramAgnosticSubcommand::Burn { + definition: format_public_account_id(&definition_account_id.to_string()), + holder: format_private_account_id(&recipient_account_id.to_string()), + amount: 3, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Check the token definition account after burn + let definition_acc = ctx + .sequencer_client() + .get_account(definition_account_id.to_string()) + .await? + .account; + + assert_eq!( + definition_acc.data.as_ref(), + &[ + 0, 65, 32, 78, 65, 77, 69, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + ); + + let new_commitment2 = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment2, ctx.sequencer_client()).await); + + // Check the recipient account balance after burn + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 4 // 7 - 3 + ); + + info!("Successfully created and transferred token with private supply"); + + Ok(()) +} + +#[test] +async fn create_token_with_private_definition() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { + cci: Some(ChainIndex::root()), + })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { + cci: Some(ChainIndex::root()), + })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with private definition + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify private definition commitment + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&definition_account_id) + .context("Failed to get definition commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify supply account + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + + assert_eq!(supply_acc.program_owner, Program::token().id()); + assert_eq!(supply_acc.data.as_ref()[0], 1); + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Create private recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_private, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create public recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id_public, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Mint to public account + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: Some(format_public_account_id( + &recipient_account_id_public.to_string(), + )), + holder_npk: None, + holder_ipk: None, + amount: 10, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify definition account has updated supply + let definition_acc = ctx + .wallet() + .get_account_private(&definition_account_id) + .context("Failed to get definition account")?; + + assert_eq!( + u128::from_le_bytes(definition_acc.data[7..23].try_into()?), + 47 // 37 + 10 + ); + + // Verify public recipient received tokens + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id_public.to_string()) + .await? + .account; + + assert_eq!( + u128::from_le_bytes(recipient_acc.data[33..].try_into()?), + 10 + ); + + // Mint to private account + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: Some(format_private_account_id( + &recipient_account_id_private.to_string(), + )), + holder_npk: None, + holder_ipk: None, + amount: 5, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify private recipient commitment + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id_private) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify private recipient balance + let recipient_acc_private = ctx + .wallet() + .get_account_private(&recipient_account_id_private) + .context("Failed to get private recipient account")?; + + assert_eq!( + u128::from_le_bytes(recipient_acc_private.data[33..].try_into()?), + 5 + ); + + info!("Successfully created token with private definition and minted to both account types"); + + Ok(()) +} + +#[test] +async fn create_token_with_private_definition_and_supply() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with both private definition and supply + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify definition commitment + let definition_commitment = ctx + .wallet() + .get_private_account_commitment(&definition_account_id) + .context("Failed to get definition commitment")?; + assert!(verify_commitment_is_in_state(definition_commitment, ctx.sequencer_client()).await); + + // Verify supply commitment + let supply_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await); + + // Verify supply balance + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 37); + + // Create recipient account + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Transfer tokens + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify both commitments updated + let supply_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(supply_commitment, ctx.sequencer_client()).await); + + let recipient_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await); + + // Verify balances + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully created and transferred token with both private definition and supply"); + + Ok(()) +} + +#[test] +async fn shielded_token_transfer() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create recipient account (private) for shielded transfer + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_public_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Perform shielded transfer: public supply -> private recipient + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_public_account_id(&supply_account_id.to_string()), + to: Some(format_private_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify supply account balance + let supply_acc = ctx + .sequencer_client() + .get_account(supply_account_id.to_string()) + .await? + .account; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Verify recipient commitment exists + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify recipient balance + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully performed shielded token transfer"); + + Ok(()) +} + +#[test] +async fn deshielded_token_transfer() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (public) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create recipient account (public) for deshielded transfer + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Public { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token with private supply + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_public_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Perform deshielded transfer: private supply -> public recipient + let subcommand = TokenProgramAgnosticSubcommand::Send { + from: format_private_account_id(&supply_account_id.to_string()), + to: Some(format_public_account_id(&recipient_account_id.to_string())), + to_npk: None, + to_ipk: None, + amount: 7, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Verify supply account commitment exists + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&supply_account_id) + .context("Failed to get supply commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Verify supply balance + let supply_acc = ctx + .wallet() + .get_account_private(&supply_account_id) + .context("Failed to get supply account")?; + assert_eq!(u128::from_le_bytes(supply_acc.data[33..].try_into()?), 30); + + // Verify recipient balance + let recipient_acc = ctx + .sequencer_client() + .get_account(recipient_account_id.to_string()) + .await? + .account; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 7); + + info!("Successfully performed deshielded token transfer"); + + Ok(()) +} + +#[test] +async fn token_claiming_path_with_private_accounts() -> Result<()> { + let mut ctx = TestContext::new().await?; + + // Create token definition account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: definition_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create supply account (private) + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: supply_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Create token + let subcommand = TokenProgramAgnosticSubcommand::New { + definition_account_id: format_private_account_id(&definition_account_id.to_string()), + supply_account_id: format_private_account_id(&supply_account_id.to_string()), + name: "A NAME".to_string(), + total_supply: 37, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Create new private account for claiming path + let result = wallet::cli::execute_subcommand( + ctx.wallet_mut(), + Command::Account(AccountSubcommand::New(NewSubcommand::Private { cci: None })), + ) + .await?; + let SubcommandReturnValue::RegisterAccount { + account_id: recipient_account_id, + } = result + else { + anyhow::bail!("Expected RegisterAccount return value"); + }; + + // Get keys for foreign mint (claiming path) + let (holder_keys, _) = ctx + .wallet() + .storage() + .user_data + .get_private_account(&recipient_account_id) + .cloned() + .context("Failed to get private account keys")?; + + // Mint using claiming path (foreign account) + let subcommand = TokenProgramAgnosticSubcommand::Mint { + definition: format_private_account_id(&definition_account_id.to_string()), + holder: None, + holder_npk: Some(hex::encode(holder_keys.nullifer_public_key.0)), + holder_ipk: Some(hex::encode(holder_keys.incoming_viewing_public_key.0)), + amount: 9, + }; + + wallet::cli::execute_subcommand(ctx.wallet_mut(), Command::Token(subcommand)).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + // Sync to claim the account + let command = Command::Account(AccountSubcommand::SyncPrivate {}); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + // Verify commitment exists + let recipient_commitment = ctx + .wallet() + .get_private_account_commitment(&recipient_account_id) + .context("Failed to get recipient commitment")?; + assert!(verify_commitment_is_in_state(recipient_commitment, ctx.sequencer_client()).await); + + // Verify balance + let recipient_acc = ctx + .wallet() + .get_account_private(&recipient_account_id) + .context("Failed to get recipient account")?; + assert_eq!(u128::from_le_bytes(recipient_acc.data[33..].try_into()?), 9); + + info!("Successfully minted tokens using claiming path"); + + Ok(()) +} diff --git a/integration_tests/src/tps_test_utils.rs b/integration_tests/tests/tps.rs similarity index 71% rename from integration_tests/src/tps_test_utils.rs rename to integration_tests/tests/tps.rs index 6f597e21..10cf53e7 100644 --- a/integration_tests/src/tps_test_utils.rs +++ b/integration_tests/tests/tps.rs @@ -1,6 +1,9 @@ -use std::time::Duration; +use std::time::{Duration, Instant}; +use anyhow::Result; +use integration_tests::TestContext; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use log::info; use nssa::{ Account, AccountId, PrivacyPreservingTransaction, PrivateKey, PublicKey, PublicTransaction, privacy_preserving_transaction::{self as pptx, circuit}, @@ -13,6 +16,78 @@ use nssa_core::{ encryption::IncomingViewingPublicKey, }; use sequencer_core::config::{AccountInitialData, CommitmentsInitialData, SequencerConfig}; +use tokio::test; + +// TODO: Make a proper benchmark instead of an ad-hoc test +#[test] +pub async fn tps_test() -> Result<()> { + let num_transactions = 300 * 5; + let target_tps = 12; + + let tps_test = TpsTestManager::new(target_tps, num_transactions); + let ctx = TestContext::new_with_sequencer_config(tps_test.generate_sequencer_config()).await?; + + let target_time = tps_test.target_time(); + info!( + "TPS test begin. Target time is {target_time:?} for {num_transactions} transactions ({target_tps} TPS)" + ); + + let txs = tps_test.build_public_txs(); + let now = Instant::now(); + + let mut tx_hashes = vec![]; + for (i, tx) in txs.into_iter().enumerate() { + let tx_hash = ctx + .sequencer_client() + .send_tx_public(tx) + .await + .unwrap() + .tx_hash; + info!("Sent tx {i}"); + tx_hashes.push(tx_hash); + } + + for (i, tx_hash) in tx_hashes.iter().enumerate() { + loop { + if now.elapsed().as_millis() > target_time.as_millis() { + panic!("TPS test failed by timeout"); + } + + let tx_obj = ctx + .sequencer_client() + .get_transaction_by_hash(tx_hash.clone()) + .await + .inspect_err(|err| { + log::warn!( + "Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}" + ) + }); + + if let Ok(tx_obj) = tx_obj + && tx_obj.transaction.is_some() + { + info!("Found tx {i} with hash {tx_hash}"); + break; + } + } + } + let time_elapsed = now.elapsed().as_secs(); + + let tx_processed = tx_hashes.len(); + let actual_tps = tx_processed as u64 / time_elapsed; + info!("Processed {tx_processed} transactions in {time_elapsed:?} ({actual_tps} TPS)",); + + assert_eq!(tx_processed, num_transactions); + + assert!( + time_elapsed <= target_time.as_secs(), + "Elapsed time {time_elapsed:?} exceeded target time {target_time:?}" + ); + + info!("TPS test finished successfully"); + + Ok(()) +} pub(crate) struct TpsTestManager { public_keypairs: Vec<(PrivateKey, AccountId)>, @@ -32,7 +107,7 @@ impl TpsTestManager { let account_id = AccountId::from(&public_key); (private_key, account_id) }) - .collect::>(); + .collect(); Self { public_keypairs, target_tps, @@ -72,7 +147,7 @@ impl TpsTestManager { /// Generates a sequencer configuration with initial balance in a number of public accounts. /// The transactions generated with the function `build_public_txs` will be valid in a node /// started with the config from this method. - pub(crate) fn generate_tps_test_config(&self) -> SequencerConfig { + pub(crate) fn generate_sequencer_config(&self) -> SequencerConfig { // Create public public keypairs let initial_public_accounts = self .public_keypairs @@ -118,7 +193,7 @@ impl TpsTestManager { /// it may take a while to run. In normal execution of the node this transaction will be accepted /// only once. Disabling the node's nullifier uniqueness check allows to submit this transaction /// multiple times with the purpose of testing the node's processing performance. -#[allow(unused)] +#[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 sender_nsk = [1; 32]; @@ -159,15 +234,16 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { ]], ); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ (sender_npk.clone(), sender_ss), (recipient_npk.clone(), recipient_ss), ], - &[(sender_nsk, proof)], + vec![sender_nsk], + vec![Some(proof)], &program.into(), ) .unwrap(); diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 103a1dee..39c1028a 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -4,22 +4,19 @@ version = "0.1.0" edition = "2024" [dependencies] +nssa.workspace = true +nssa_core.workspace = true +common.workspace = true + anyhow.workspace = true serde.workspace = true k256.workspace = true sha2.workspace = true rand.workspace = true base58.workspace = true -hex = "0.4.3" +hex.workspace = true aes-gcm.workspace = true bip39.workspace = true hmac-sha512.workspace = true thiserror.workspace = true -nssa-core = { path = "../nssa/core", features = ["host"] } itertools.workspace = true - -[dependencies.common] -path = "../common" - -[dependencies.nssa] -path = "../nssa" diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index b46c46c9..4814552a 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -165,6 +165,14 @@ impl NSSAUserData { .map(Into::into) } } + + pub fn account_ids(&self) -> impl Iterator { + self.default_pub_account_signing_keys + .keys() + .chain(self.public_key_tree.account_id_map.keys()) + .chain(self.default_user_private_accounts.keys()) + .chain(self.private_key_tree.account_id_map.keys()) + } } impl Default for NSSAUserData { diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index 915212c4..a508cc08 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -4,26 +4,29 @@ version = "0.1.0" edition = "2024" [dependencies] -thiserror = "2.0.12" -risc0-zkvm = { version = "3.0.3", features = ['std'] } -nssa-core = { path = "core", features = ["host"] } -program-methods = { path = "program_methods", optional = true } -serde = "1.0.219" -sha2 = "0.10.9" +nssa_core = { workspace = true, features = ["host"] } + +thiserror.workspace = true +risc0-zkvm.workspace = true +serde.workspace = true +sha2.workspace = true +rand.workspace = true +borsh.workspace = true +hex.workspace = true secp256k1 = "0.31.1" -rand = "0.8" -borsh = "1.5.7" -hex = "0.4.3" risc0-binfmt = "3.0.2" +bytemuck = "1.24.0" +log.workspace = true [build-dependencies] risc0-build = "3.0.3" risc0-binfmt = "3.0.2" [dev-dependencies] -test-program-methods = { path = "test_program_methods" } +test_program_methods.workspace = true +env_logger.workspace = true hex-literal = "1.0.0" +test-case = "3.3.1" [features] default = [] -no_docker = ["program-methods"] diff --git a/nssa/build.rs b/nssa/build.rs index cc4608ef..020b838c 100644 --- a/nssa/build.rs +++ b/nssa/build.rs @@ -1,43 +1,21 @@ -fn main() { - if cfg!(feature = "no_docker") { - println!("cargo:warning=NO_DOCKER feature enabled โ€“ deterministic build skipped"); - return; - } - - build_deterministic().expect("Deterministic build failed"); -} - -fn build_deterministic() -> Result<(), Box> { - use std::{env, fs, path::PathBuf, process::Command}; +use std::{env, 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/guest/src"); - println!("cargo:rerun-if-changed=program_methods/guest/Cargo.toml"); + println!("cargo:rerun-if-changed={}", program_methods_dir.display()); - let guest_manifest = manifest_dir.join("program_methods/guest/Cargo.toml"); - - let status = Command::new("cargo") - .args(["risczero", "build", "--manifest-path"]) - .arg(&guest_manifest) - .status()?; - if !status.success() { - return Err("Risc0 deterministic build failed".into()); - } - - let target_dir = - manifest_dir.join("program_methods/guest/target/riscv32im-risc0-zkvm-elf/docker/"); - - let bins = fs::read_dir(&target_dir)? + 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 {:?}", target_dir).into()); + return Err(format!("No .bin files found in {:?}", program_methods_dir).into()); } fs::create_dir_all(&mod_dir)?; diff --git a/nssa/core/Cargo.toml b/nssa/core/Cargo.toml index 80fe7df9..473cde90 100644 --- a/nssa/core/Cargo.toml +++ b/nssa/core/Cargo.toml @@ -1,22 +1,23 @@ [package] -name = "nssa-core" +name = "nssa_core" version = "0.1.0" edition = "2024" [dependencies] -risc0-zkvm = { version = "3.0.3", features = ['std'] } -serde = { version = "1.0", default-features = false } -thiserror = { version = "2.0.12" } -bytemuck = { version = "1.13", optional = true } +risc0-zkvm.workspace = true +borsh.workspace = true +serde.workspace = true +thiserror.workspace = true +bytemuck.workspace = true +k256 = { workspace = true, optional = true } +base58 = { workspace = true, optional = true } +anyhow = { workspace = true, optional = true } + chacha20 = { version = "0.9", default-features = false } -k256 = { version = "0.13.3", optional = true } -base58 = { version = "0.2.0", optional = true } -anyhow = { version = "1.0.98", optional = true } -borsh = "1.5.7" [dev-dependencies] -serde_json = "1.0.81" +serde_json.workspace = true [features] default = [] -host = ["dep:bytemuck", "dep:k256", "dep:base58", "dep:anyhow"] +host = ["dep:k256", "dep:base58", "dep:anyhow"] diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index c152581d..55ab0ded 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -15,9 +15,8 @@ pub type Nonce = u128; /// Account to be used both in public and private contexts #[derive( - Serialize, Deserialize, Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize, + Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, )] -#[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct Account { pub program_owner: ProgramId, pub balance: u128, @@ -25,8 +24,7 @@ pub struct Account { pub nonce: Nonce, } -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] -#[cfg_attr(any(feature = "host", test), derive(Debug))] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, @@ -44,11 +42,20 @@ impl AccountWithMetadata { } } -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize)] -#[cfg_attr( - any(feature = "host", test), - derive(Debug, Copy, PartialOrd, Ord, Default) +#[derive( + Debug, + Default, + Copy, + Clone, + Serialize, + Deserialize, + PartialEq, + Eq, + Hash, + BorshSerialize, + BorshDeserialize, )] +#[cfg_attr(any(feature = "host", test), derive(PartialOrd, Ord))] pub struct AccountId { value: [u8; 32], } @@ -181,4 +188,11 @@ mod tests { let result = base58_str.parse::().unwrap_err(); assert!(matches!(result, AccountIdError::InvalidLength(_))); } + + #[test] + fn default_account_id() { + let default_account_id = AccountId::default(); + let expected_account_id = AccountId::new([0; 32]); + assert!(default_account_id == expected_account_id); + } } diff --git a/nssa/core/src/account/data.rs b/nssa/core/src/account/data.rs index 974cb06c..396bbe6e 100644 --- a/nssa/core/src/account/data.rs +++ b/nssa/core/src/account/data.rs @@ -5,8 +5,7 @@ use serde::{Deserialize, Serialize}; pub const DATA_MAX_LENGTH_IN_BYTES: usize = 100 * 1024; // 100 KiB -#[derive(Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] -#[cfg_attr(any(feature = "host", test), derive(Debug))] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] pub struct Data(Vec); impl Data { diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 848fe3e6..dedcf780 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -10,11 +10,23 @@ use crate::{ #[derive(Serialize, Deserialize)] pub struct PrivacyPreservingCircuitInput { + /// Outputs of the program execution. pub program_outputs: Vec, + /// Visibility mask for accounts. + /// + /// - `0` - public account + /// - `1` - private account with authentication + /// - `2` - private account without authentication pub visibility_mask: Vec, + /// Nonces of private accounts. pub private_account_nonces: Vec, + /// Public keys of private accounts. pub private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, - pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, + /// Nullifier secret keys for authorized private accounts. + pub private_account_nsks: Vec, + /// Membership proofs for private accounts. Can be [`None`] for uninitialized accounts. + pub private_account_membership_proofs: Vec>, + /// Program ID. pub program_id: ProgramId, } diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index 8f0c6bef..c953d4d3 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -16,7 +16,7 @@ use crate::{Commitment, account::Account}; pub type Scalar = [u8; 32]; -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Copy)] pub struct SharedSecretKey(pub [u8; 32]); pub struct EncryptionScheme; diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index ec30700b..8d9d59fa 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{Commitment, account::AccountId}; -#[derive(Serialize, Deserialize, PartialEq, Eq)] -#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Clone, Hash))] pub struct NullifierPublicKey(pub [u8; 32]); impl From<&NullifierPublicKey> for AccountId { diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 26ee8deb..32b3e2c0 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -3,9 +3,7 @@ use std::collections::HashSet; use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; -#[cfg(feature = "host")] -use crate::account::AccountId; -use crate::account::{Account, AccountWithMetadata}; +use crate::account::{Account, AccountId, AccountWithMetadata}; pub type ProgramId = [u32; 8]; pub type InstructionData = Vec; @@ -22,17 +20,30 @@ pub struct ProgramInput { /// Each program can derive up to `2^256` unique account IDs by choosing different /// seeds. PDAs allow programs to control namespaced account identifiers without /// collisions between programs. -#[derive(Serialize, Deserialize, Clone)] -#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +#[derive(Serialize, Deserialize, Clone, Eq, PartialEq)] +#[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct PdaSeed([u8; 32]); impl PdaSeed { - pub fn new(value: [u8; 32]) -> Self { + pub const fn new(value: [u8; 32]) -> Self { Self(value) } } -#[cfg(feature = "host")] +pub fn compute_authorized_pdas( + caller_program_id: Option, + pda_seeds: &[PdaSeed], +) -> HashSet { + caller_program_id + .map(|caller_program_id| { + pda_seeds + .iter() + .map(|pda_seed| AccountId::from((&caller_program_id, pda_seed))) + .collect() + }) + .unwrap_or_default() +} + impl From<(&ProgramId, &PdaSeed)> for AccountId { fn from(value: (&ProgramId, &PdaSeed)) -> Self { use risc0_zkvm::sha::{Impl, Sha256}; @@ -54,8 +65,8 @@ impl From<(&ProgramId, &PdaSeed)> for AccountId { } } -#[derive(Serialize, Deserialize, Clone)] -#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Debug,))] pub struct ChainedCall { /// The program ID of the program to execute pub program_id: ProgramId, @@ -96,6 +107,13 @@ impl AccountPostState { } } + /// Creates a post state that requests ownership of the account + /// if the account's program owner is the default program ID. + pub fn new_claimed_if_default(account: Account) -> Self { + let claim = account.program_owner == DEFAULT_PROGRAM_ID; + Self { account, claim } + } + /// Returns `true` if this post state requests that the account /// be claimed (owned) by the executing program. pub fn requires_claim(&self) -> bool { @@ -111,6 +129,11 @@ impl AccountPostState { pub fn account_mut(&mut self) -> &mut Account { &mut self.account } + + /// Consumes the post state and returns the underlying account + pub fn into_account(self) -> Account { + self.account + } } #[derive(Serialize, Deserialize, Clone)] diff --git a/nssa/program_methods/guest/Cargo.toml b/nssa/program_methods/guest/Cargo.toml deleted file mode 100644 index 9e5f543f..00000000 --- a/nssa/program_methods/guest/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "programs" -version = "0.1.0" -edition = "2024" - -[workspace] - -[dependencies] -risc0-zkvm = { version = "3.0.3", features = ['std'] } -nssa-core = { path = "../../core" } -serde = { version = "1.0.219", default-features = false } diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs deleted file mode 100644 index 29162db9..00000000 --- a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ /dev/null @@ -1,246 +0,0 @@ -use std::collections::HashMap; - -use nssa_core::{ - Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, - NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, - account::{Account, AccountId, AccountWithMetadata}, - compute_digest_for_path, - encryption::Ciphertext, - program::{DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, validate_execution}, -}; -use risc0_zkvm::{guest::env, serde::to_vec}; - -fn main() { - let PrivacyPreservingCircuitInput { - program_outputs, - visibility_mask, - private_account_nonces, - private_account_keys, - private_account_auth, - mut program_id, - } = env::read(); - - let mut pre_states: Vec = Vec::new(); - let mut state_diff: HashMap = HashMap::new(); - - let num_calls = program_outputs.len(); - if num_calls > MAX_NUMBER_CHAINED_CALLS { - panic!("Max chained calls depth is exceeded"); - } - - let Some(last_program_call) = program_outputs.last() else { - panic!("Program outputs is empty") - }; - - if !last_program_call.chained_calls.is_empty() { - panic!("Call stack is incomplete"); - } - - for window in program_outputs.windows(2) { - let caller = &window[0]; - let callee = &window[1]; - - if caller.chained_calls.len() > 1 { - panic!("Privacy Multi-chained calls are not supported yet"); - } - - // TODO: Modify when multi-chain calls are supported in the circuit - let Some(caller_chained_call) = &caller.chained_calls.first() else { - panic!("Expected chained call"); - }; - - // Check that instruction data in caller is the instruction data in callee - if caller_chained_call.instruction_data != callee.instruction_data { - panic!("Invalid instruction data"); - } - - // Check that account pre_states in caller are the ones in calle - if caller_chained_call.pre_states != callee.pre_states { - panic!("Invalid pre states"); - } - } - - for (i, program_output) in program_outputs.iter().enumerate() { - let mut program_output = program_output.clone(); - - // Check that `program_output` is consistent with the execution of the corresponding program. - let program_output_words = - &to_vec(&program_output).expect("program_output must be serializable"); - env::verify(program_id, program_output_words) - .expect("program output must match the program's execution"); - - // Check that the program is well behaved. - // See the # Programs section for the definition of the `validate_execution` method. - if !validate_execution( - &program_output.pre_states, - &program_output.post_states, - program_id, - ) { - panic!("Bad behaved program"); - } - - // The invoked program claims the accounts with default program id. - for post in program_output - .post_states - .iter_mut() - .filter(|post| post.requires_claim()) - { - // The invoked program can only claim accounts with default program id. - if post.account().program_owner == DEFAULT_PROGRAM_ID { - post.account_mut().program_owner = program_id; - } else { - panic!("Cannot claim an initialized account") - } - } - - for (pre, post) in program_output - .pre_states - .iter() - .zip(&program_output.post_states) - { - if let Some(account_pre) = state_diff.get(&pre.account_id) { - if account_pre != &pre.account { - panic!("Invalid input"); - } - } else { - pre_states.push(pre.clone()); - } - state_diff.insert(pre.account_id.clone(), post.account().clone()); - } - - // TODO: Modify when multi-chain calls are supported in the circuit - if let Some(next_chained_call) = &program_output.chained_calls.first() { - program_id = next_chained_call.program_id; - } else if i != program_outputs.len() - 1 { - panic!("Inner call without a chained call found") - }; - } - - let n_accounts = pre_states.len(); - if visibility_mask.len() != n_accounts { - panic!("Invalid visibility mask length"); - } - - // These lists will be the public outputs of this circuit - // and will be populated next. - let mut public_pre_states: Vec = Vec::new(); - let mut public_post_states: Vec = Vec::new(); - let mut ciphertexts: Vec = Vec::new(); - let mut new_commitments: Vec = Vec::new(); - let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new(); - - let mut private_nonces_iter = private_account_nonces.iter(); - let mut private_keys_iter = private_account_keys.iter(); - let mut private_auth_iter = private_account_auth.iter(); - - let mut output_index = 0; - for i in 0..n_accounts { - match visibility_mask[i] { - 0 => { - // Public account - public_pre_states.push(pre_states[i].clone()); - - let mut post = state_diff.get(&pre_states[i].account_id).unwrap().clone(); - - if post.program_owner == DEFAULT_PROGRAM_ID { - // Claim account - post.program_owner = program_id; - } - public_post_states.push(post); - } - 1 | 2 => { - let new_nonce = private_nonces_iter.next().expect("Missing private nonce"); - let (npk, shared_secret) = private_keys_iter.next().expect("Missing keys"); - - if AccountId::from(npk) != pre_states[i].account_id { - panic!("AccountId mismatch"); - } - - if visibility_mask[i] == 1 { - // Private account with authentication - let (nsk, membership_proof) = - private_auth_iter.next().expect("Missing private auth"); - - // Verify the nullifier public key - let expected_npk = NullifierPublicKey::from(nsk); - if &expected_npk != npk { - panic!("Nullifier public key mismatch"); - } - - // Compute commitment set digest associated with provided auth path - let commitment_pre = Commitment::new(npk, &pre_states[i].account); - let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); - - // Check pre_state authorization - if !pre_states[i].is_authorized { - panic!("Pre-state not authorized"); - } - - // Compute update nullifier - let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); - new_nullifiers.push((nullifier, set_digest)); - } else { - if pre_states[i].account != Account::default() { - panic!("Found new private account with non default values."); - } - - if pre_states[i].is_authorized { - panic!("Found new private account marked as authorized."); - } - - // Compute initialization nullifier - let nullifier = Nullifier::for_account_initialization(npk); - new_nullifiers.push((nullifier, DUMMY_COMMITMENT_HASH)); - } - - // Update post-state with new nonce - let mut post_with_updated_values = - state_diff.get(&pre_states[i].account_id).unwrap().clone(); - post_with_updated_values.nonce = *new_nonce; - - if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { - // Claim account - post_with_updated_values.program_owner = program_id; - } - - // Compute commitment - let commitment_post = Commitment::new(npk, &post_with_updated_values); - - // Encrypt and push post state - let encrypted_account = EncryptionScheme::encrypt( - &post_with_updated_values, - shared_secret, - &commitment_post, - output_index, - ); - - new_commitments.push(commitment_post); - ciphertexts.push(encrypted_account); - output_index += 1; - } - _ => panic!("Invalid visibility mask value"), - } - } - - if private_nonces_iter.next().is_some() { - panic!("Too many nonces."); - } - - if private_keys_iter.next().is_some() { - panic!("Too many private account keys."); - } - - if private_auth_iter.next().is_some() { - panic!("Too many private account authentication keys."); - } - - let output = PrivacyPreservingCircuitOutput { - public_pre_states, - public_post_states, - ciphertexts, - new_commitments, - new_nullifiers, - }; - - env::commit(&output); -} diff --git a/nssa/program_methods/guest/src/bin/token.rs b/nssa/program_methods/guest/src/bin/token.rs deleted file mode 100644 index 739295b3..00000000 --- a/nssa/program_methods/guest/src/bin/token.rs +++ /dev/null @@ -1,1321 +0,0 @@ -use nssa_core::{ - account::{Account, AccountId, AccountWithMetadata, Data}, - program::{ - AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, - }, -}; - -// The token program has three functions: -// 1. New token definition. -// Arguments to this function are: -// * Two **default** accounts: [definition_account, holding_account]. -// The first default account will be initialized with the token definition account values. The second account will -// be initialized to a token holding account for the new token, holding the entire total supply. -// * An instruction data of 23-bytes, indicating the total supply and the token name, with -// the following layout: -// [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] -// The name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] -// 2. Token transfer -// Arguments to this function are: -// * Two accounts: [sender_account, recipient_account]. -// * An instruction data byte string of length 23, indicating the total supply with the following layout -// [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 3. Initialize account with zero balance -// Arguments to this function are: -// * Two accounts: [definition_account, account_to_initialize]. -// * An dummy byte string of length 23, with the following layout -// [0x02 || 0x00 || 0x00 || 0x00 || ... || 0x00 || 0x00]. -// 4. Burn tokens from a Token Holding account (thus lowering total supply) -// Arguments to this function are: -// * Two accounts: [definition_account, holding_account]. -// * Authorization required: holding_account -// * An instruction data byte string of length 23, indicating the balance to burn with the folloiwng layout -// [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. -// 5. Mint additional supply of tokens tokens to a Token Holding account (thus increasing total supply) -// Arguments to this function are: -// * Two accounts: [definition_account, holding_account]. -// * Authorization required: definition_account -// * An instruction data byte string of length 23, indicating the balance to mint with the folloiwng layout -// [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. - -const TOKEN_DEFINITION_TYPE: u8 = 0; -const TOKEN_DEFINITION_DATA_SIZE: usize = 23; - -const TOKEN_HOLDING_TYPE: u8 = 1; -const TOKEN_HOLDING_DATA_SIZE: usize = 49; - -struct TokenDefinition { - account_type: u8, - name: [u8; 6], - total_supply: u128, -} - -struct TokenHolding { - account_type: u8, - definition_id: AccountId, - balance: u128, -} - -impl TokenDefinition { - fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_DEFINITION_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..7].copy_from_slice(&self.name); - bytes[7..].copy_from_slice(&self.total_supply.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("23 bytes should fit into Data") - } - - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { - None - } else { - let account_type = data[0]; - let name = data[1..7].try_into().unwrap(); - let total_supply = u128::from_le_bytes( - data[7..] - .try_into() - .expect("Total supply must be 16 bytes little-endian"), - ); - Some(Self { - account_type, - name, - total_supply, - }) - } - } -} - -impl TokenHolding { - fn new(definition_id: &AccountId) -> Self { - Self { - account_type: TOKEN_HOLDING_TYPE, - definition_id: definition_id.clone(), - balance: 0, - } - } - - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { - return None; - } - - let account_type = data[0]; - let definition_id = AccountId::new( - data[1..33] - .try_into() - .expect("Defintion ID must be 32 bytes long"), - ); - let balance = u128::from_le_bytes( - data[33..] - .try_into() - .expect("balance must be 16 bytes little-endian"), - ); - Some(Self { - definition_id, - balance, - account_type, - }) - } - - fn into_data(self) -> Data { - let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; - bytes[0] = self.account_type; - bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); - bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); - bytes - .to_vec() - .try_into() - .expect("33 bytes should fit into Data") - } -} - -fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { - if pre_states.len() != 2 { - panic!("Invalid number of input accounts"); - } - let sender = &pre_states[0]; - let recipient = &pre_states[1]; - - let mut sender_holding = - TokenHolding::parse(&sender.account.data).expect("Invalid sender data"); - let mut recipient_holding = if recipient.account == Account::default() { - TokenHolding::new(&sender_holding.definition_id) - } else { - TokenHolding::parse(&recipient.account.data).expect("Invalid recipient data") - }; - - if sender_holding.definition_id != recipient_holding.definition_id { - panic!("Sender and recipient definition id mismatch"); - } - - if sender_holding.balance < balance_to_move { - panic!("Insufficient balance"); - } - - if !sender.is_authorized { - panic!("Sender authorization is missing"); - } - - sender_holding.balance -= balance_to_move; - recipient_holding.balance = recipient_holding - .balance - .checked_add(balance_to_move) - .expect("Recipient balance overflow"); - - let sender_post = { - let mut this = sender.account.clone(); - this.data = sender_holding.into_data(); - AccountPostState::new(this) - }; - - let recipient_post = { - let mut this = recipient.account.clone(); - this.data = recipient_holding.into_data(); - - // Claim the recipient account if it has default program owner - if this.program_owner == DEFAULT_PROGRAM_ID { - AccountPostState::new_claimed(this) - } else { - AccountPostState::new(this) - } - }; - - vec![sender_post, recipient_post] -} - -fn new_definition( - pre_states: &[AccountWithMetadata], - name: [u8; 6], - total_supply: u128, -) -> Vec { - if pre_states.len() != 2 { - panic!("Invalid number of input accounts"); - } - let definition_target_account = &pre_states[0]; - let holding_target_account = &pre_states[1]; - - if definition_target_account.account != Account::default() { - panic!("Definition target account must have default values"); - } - - if holding_target_account.account != Account::default() { - panic!("Holding target account must have default values"); - } - - let token_definition = TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name, - total_supply, - }; - - let token_holding = TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: definition_target_account.account_id.clone(), - balance: total_supply, - }; - - let mut definition_target_account_post = definition_target_account.account.clone(); - definition_target_account_post.data = token_definition.into_data(); - - let mut holding_target_account_post = holding_target_account.account.clone(); - holding_target_account_post.data = token_holding.into_data(); - - vec![ - AccountPostState::new_claimed(definition_target_account_post), - AccountPostState::new_claimed(holding_target_account_post), - ] -} - -fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { - if pre_states.len() != 2 { - panic!("Invalid number of accounts"); - } - - let definition = &pre_states[0]; - let account_to_initialize = &pre_states[1]; - - if account_to_initialize.account != Account::default() { - panic!("Only Uninitialized accounts can be initialized"); - } - - // TODO: #212 We should check that this is an account owned by the token program. - // This check can't be done here since the ID of the program is known only after compiling it - // - // Check definition account is valid - let _definition_values = - TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); - let holding_values = TokenHolding::new(&definition.account_id); - - let definition_post = definition.account.clone(); - let mut account_to_initialize = account_to_initialize.account.clone(); - account_to_initialize.data = holding_values.into_data(); - - vec![ - AccountPostState::new(definition_post), - AccountPostState::new_claimed(account_to_initialize), - ] -} - -fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec { - if pre_states.len() != 2 { - panic!("Invalid number of accounts"); - } - - let definition = &pre_states[0]; - let user_holding = &pre_states[1]; - - let definition_values = - TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); - let user_values = TokenHolding::parse(&user_holding.account.data) - .expect("Token Holding account must be valid"); - - if definition.account_id != user_values.definition_id { - panic!("Mismatch token definition and token holding"); - } - - if !user_holding.is_authorized { - panic!("Authorization is missing"); - } - - if user_values.balance < balance_to_burn { - panic!("Insufficient balance to burn"); - } - - let mut post_user_holding = user_holding.account.clone(); - let mut post_definition = definition.account.clone(); - - post_user_holding.data = TokenHolding::into_data(TokenHolding { - account_type: user_values.account_type, - definition_id: user_values.definition_id, - balance: user_values - .balance - .checked_sub(balance_to_burn) - .expect("Checked above"), - }); - - post_definition.data = TokenDefinition::into_data(TokenDefinition { - account_type: definition_values.account_type, - name: definition_values.name, - total_supply: definition_values - .total_supply - .checked_sub(balance_to_burn) - .expect("Total supply underflow"), - }); - - vec![ - AccountPostState::new(post_definition), - AccountPostState::new(post_user_holding), - ] -} - -fn mint_additional_supply( - pre_states: &[AccountWithMetadata], - amount_to_mint: u128, -) -> Vec { - if pre_states.len() != 2 { - panic!("Invalid number of accounts"); - } - - let definition = &pre_states[0]; - let token_holding = &pre_states[1]; - - if !definition.is_authorized { - panic!("Definition authorization is missing"); - } - - let definition_values = - TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); - - let token_holding_values: TokenHolding = if token_holding.account == Account::default() { - TokenHolding::new(&definition.account_id) - } else { - TokenHolding::parse(&token_holding.account.data).expect("Holding account must be valid") - }; - - if definition.account_id != token_holding_values.definition_id { - panic!("Mismatch token definition and token holding"); - } - - let token_holding_post_data = TokenHolding { - account_type: token_holding_values.account_type, - definition_id: token_holding_values.definition_id, - balance: token_holding_values - .balance - .checked_add(amount_to_mint) - .expect("New balance overflow"), - }; - - let post_total_supply = definition_values - .total_supply - .checked_add(amount_to_mint) - .expect("Total supply overflow"); - - let post_definition_data = TokenDefinition { - account_type: definition_values.account_type, - name: definition_values.name, - total_supply: post_total_supply, - }; - - let post_definition = { - let mut this = definition.account.clone(); - this.data = post_definition_data.into_data(); - AccountPostState::new(this) - }; - - let token_holding_post = { - let mut this = token_holding.account.clone(); - this.data = token_holding_post_data.into_data(); - - // Claim the recipient account if it has default program owner - if this.program_owner == DEFAULT_PROGRAM_ID { - AccountPostState::new_claimed(this) - } else { - AccountPostState::new(this) - } - }; - vec![post_definition, token_holding_post] -} - -type Instruction = [u8; 23]; - -fn main() { - let ( - ProgramInput { - pre_states, - instruction, - }, - instruction_words, - ) = read_nssa_inputs::(); - - let post_states = match instruction[0] { - 0 => { - // Parse instruction - let total_supply = u128::from_le_bytes( - instruction[1..17] - .try_into() - .expect("Total supply must be 16 bytes little-endian"), - ); - let name: [u8; 6] = instruction[17..] - .try_into() - .expect("Name must be 6 bytes long"); - assert_ne!(name, [0; 6]); - - // Execute - new_definition(&pre_states, name, total_supply) - } - 1 => { - // Parse instruction - let balance_to_move = u128::from_le_bytes( - instruction[1..17] - .try_into() - .expect("Balance to move must be 16 bytes little-endian"), - ); - let name: [u8; 6] = instruction[17..] - .try_into() - .expect("Name must be 6 bytes long"); - assert_eq!(name, [0; 6]); - - // Execute - transfer(&pre_states, balance_to_move) - } - 2 => { - // Initialize account - if instruction[1..] != [0; 22] { - panic!("Invalid instruction for initialize account"); - } - initialize_account(&pre_states) - } - 3 => { - let balance_to_burn = u128::from_le_bytes( - instruction[1..17] - .try_into() - .expect("Balance to burn must be 16 bytes little-endian"), - ); - let name: [u8; 6] = instruction[17..] - .try_into() - .expect("Name must be 6 bytes long"); - assert_eq!(name, [0; 6]); - - // Execute - burn(&pre_states, balance_to_burn) - } - 4 => { - let balance_to_mint = u128::from_le_bytes( - instruction[1..17] - .try_into() - .expect("Balance to burn must be 16 bytes little-endian"), - ); - let name: [u8; 6] = instruction[17..] - .try_into() - .expect("Name must be 6 bytes long"); - assert_eq!(name, [0; 6]); - - // Execute - mint_additional_supply(&pre_states, balance_to_mint) - } - _ => panic!("Invalid instruction"), - }; - - write_nssa_outputs(instruction_words, pre_states, post_states); -} - -#[cfg(test)] -mod tests { - use nssa_core::account::{Account, AccountId, AccountWithMetadata}; - - use crate::{ - TOKEN_DEFINITION_DATA_SIZE, TOKEN_DEFINITION_TYPE, TOKEN_HOLDING_DATA_SIZE, - TOKEN_HOLDING_TYPE, TokenDefinition, TokenHolding, burn, initialize_account, - mint_additional_supply, new_definition, transfer, - }; - - #[should_panic(expected = "Invalid number of input accounts")] - #[test] - fn test_call_new_definition_with_invalid_number_of_accounts_1() { - let pre_states = vec![AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }]; - let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); - } - - #[should_panic(expected = "Invalid number of input accounts")] - #[test] - fn test_call_new_definition_with_invalid_number_of_accounts_2() { - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); - } - - #[should_panic(expected = "Definition target account must have default values")] - #[test] - fn test_new_definition_non_default_first_account_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - program_owner: [1, 2, 3, 4, 5, 6, 7, 8], - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); - } - - #[should_panic(expected = "Holding target account must have default values")] - #[test] - fn test_new_definition_non_default_second_account_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - program_owner: [1, 2, 3, 4, 5, 6, 7, 8], - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); - } - - #[test] - fn test_new_definition_with_valid_inputs_succeeds() { - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: AccountId::new([ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - ]), - }, - AccountWithMetadata { - account: Account { - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([2; 32]), - }, - ]; - - let post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); - let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); - assert_eq!( - definition_account.account().data.as_ref(), - &[ - 0, 0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - assert_eq!( - holding_account.account().data.as_ref(), - &[ - 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0 - ] - ); - } - - #[should_panic(expected = "Invalid number of input accounts")] - #[test] - fn test_call_transfer_with_invalid_number_of_accounts_1() { - let pre_states = vec![AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Invalid number of input accounts")] - #[test] - fn test_call_transfer_with_invalid_number_of_accounts_2() { - let pre_states = vec![ - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([3; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Invalid sender data")] - #[test] - fn test_transfer_invalid_instruction_type_should_fail() { - let invalid_type = TOKEN_HOLDING_TYPE ^ 1; - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // First byte should be `TOKEN_HOLDING_TYPE` for token holding accounts - data: vec![invalid_type; TOKEN_HOLDING_DATA_SIZE] - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Invalid sender data")] - #[test] - fn test_transfer_invalid_data_size_should_fail_1() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE - 1].try_into().unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Invalid sender data")] - #[test] - fn test_transfer_invalid_data_size_should_fail_2() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` - data: vec![1; TOKEN_HOLDING_DATA_SIZE + 1].try_into().unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Sender and recipient definition id mismatch")] - #[test] - fn test_transfer_with_different_definition_ids_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: [1] - .into_iter() - .chain(vec![2; TOKEN_HOLDING_DATA_SIZE - 1]) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 10); - } - - #[should_panic(expected = "Insufficient balance")] - #[test] - fn test_transfer_with_insufficient_balance_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - // Attempt to transfer 38 tokens - let _post_states = transfer(&pre_states, 38); - } - - #[should_panic(expected = "Sender authorization is missing")] - #[test] - fn test_transfer_without_sender_authorization_should_fail() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - data: vec![1; TOKEN_HOLDING_DATA_SIZE].try_into().unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let _post_states = transfer(&pre_states, 37); - } - - #[test] - fn test_transfer_with_valid_inputs_succeeds() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Account with balance 37 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(37)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account { - // Account with balance 255 - data: [1; TOKEN_HOLDING_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(255)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: true, - account_id: AccountId::new([2; 32]), - }, - ]; - let post_states = transfer(&pre_states, 11); - let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); - assert_eq!( - sender_post.account().data.as_ref(), - [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - assert_eq!( - recipient_post.account().data.as_ref(), - [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - #[test] - fn test_token_initialize_account_succeeds() { - let pre_states = vec![ - AccountWithMetadata { - account: Account { - // Definition ID with - data: [0; TOKEN_DEFINITION_DATA_SIZE - 16] - .into_iter() - .chain(u128::to_le_bytes(1000)) - .collect::>() - .try_into() - .unwrap(), - ..Account::default() - }, - is_authorized: false, - account_id: AccountId::new([1; 32]), - }, - AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: AccountId::new([2; 32]), - }, - ]; - let post_states = initialize_account(&pre_states); - let [definition, holding] = post_states.try_into().ok().unwrap(); - assert_eq!( - definition.account().data.as_ref(), - pre_states[0].account.data.as_ref() - ); - assert_eq!( - holding.account().data.as_ref(), - [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ] - ); - } - - enum BalanceEnum { - InitSupply, - HoldingBalance, - InitSupplyBurned, - HoldingBalanceBurned, - BurnSuccess, - BurnInsufficient, - MintSuccess, - InitSupplyMint, - HoldingBalanceMint, - MintOverflow, - } - - enum AccountsEnum { - DefinitionAccountAuth, - DefinitionAccountNotAuth, - HoldingDiffDef, - HoldingSameDefAuth, - HoldingSameDefNotAuth, - HoldingSameDefNotAuthOverflow, - DefinitionAccountPostBurn, - HoldingAccountPostBurn, - Uninit, - InitMint, - DefinitionAccountMint, - HoldingSameDefMint, - HoldingSameDefAuthLargeBalance, - } - - enum IdEnum { - PoolDefinitionId, - PoolDefinitionIdDiff, - HoldingId, - } - - fn helper_account_constructor(selection: AccountsEnum) -> AccountWithMetadata { - match selection { - AccountsEnum::DefinitionAccountAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingDiffDef => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionIdDiff), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuth => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalance), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefNotAuthOverflow => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::InitSupply), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::DefinitionAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyBurned), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingAccountPostBurn => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceBurned), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::Uninit => AccountWithMetadata { - account: Account::default(), - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::InitMint => AccountWithMetadata { - account: Account { - program_owner: [0u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintSuccess), - }), - nonce: 0, - }, - is_authorized: false, - account_id: helper_id_constructor(IdEnum::HoldingId), - }, - AccountsEnum::HoldingSameDefMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::HoldingBalanceMint), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::DefinitionAccountMint => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenDefinition::into_data(TokenDefinition { - account_type: TOKEN_DEFINITION_TYPE, - name: [2; 6], - total_supply: helper_balance_constructor(BalanceEnum::InitSupplyMint), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - AccountsEnum::HoldingSameDefAuthLargeBalance => AccountWithMetadata { - account: Account { - program_owner: [5u32; 8], - balance: 0u128, - data: TokenHolding::into_data(TokenHolding { - account_type: TOKEN_HOLDING_TYPE, - definition_id: helper_id_constructor(IdEnum::PoolDefinitionId), - balance: helper_balance_constructor(BalanceEnum::MintOverflow), - }), - nonce: 0, - }, - is_authorized: true, - account_id: helper_id_constructor(IdEnum::PoolDefinitionId), - }, - _ => panic!("Invalid selection"), - } - } - - fn helper_balance_constructor(selection: BalanceEnum) -> u128 { - match selection { - BalanceEnum::InitSupply => 100_000, - BalanceEnum::HoldingBalance => 1_000, - BalanceEnum::InitSupplyBurned => 99_500, - BalanceEnum::HoldingBalanceBurned => 500, - BalanceEnum::BurnSuccess => 500, - BalanceEnum::BurnInsufficient => 1_500, - BalanceEnum::MintSuccess => 50_000, - BalanceEnum::InitSupplyMint => 150_000, - BalanceEnum::HoldingBalanceMint => 51_000, - BalanceEnum::MintOverflow => (2 as u128).pow(128) - 40_000, - _ => panic!("Invalid selection"), - } - } - - fn helper_id_constructor(selection: IdEnum) -> AccountId { - match selection { - IdEnum::PoolDefinitionId => AccountId::new([15; 32]), - IdEnum::PoolDefinitionIdDiff => AccountId::new([16; 32]), - IdEnum::HoldingId => AccountId::new([17; 32]), - } - } - - #[test] - #[should_panic(expected = "Invalid number of accounts")] - fn test_burn_invalid_number_of_accounts() { - let pre_states = vec![helper_account_constructor( - AccountsEnum::DefinitionAccountAuth, - )]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); - } - - #[test] - #[should_panic(expected = "Mismatch token definition and token holding")] - fn test_burn_mismatch_def() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingDiffDef), - ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); - } - - #[test] - #[should_panic(expected = "Authorization is missing")] - fn test_burn_missing_authorization() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), - ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); - } - - #[test] - #[should_panic(expected = "Insufficient balance to burn")] - fn test_burn_insufficient_balance() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnInsufficient), - ); - } - - #[test] - #[should_panic(expected = "Total supply underflow")] - fn test_burn_total_supply_underflow() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefAuthLargeBalance), - ]; - let _post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::MintOverflow), - ); - } - - #[test] - fn test_burn_success() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefAuth), - ]; - let post_states = burn( - &pre_states, - helper_balance_constructor(BalanceEnum::BurnSuccess), - ); - - let def_post = post_states[0].clone(); - let holding_post = post_states[1].clone(); - - assert!( - *def_post.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountPostBurn).account - ); - assert!( - *holding_post.account() - == helper_account_constructor(AccountsEnum::HoldingAccountPostBurn).account - ); - } - - #[test] - #[should_panic(expected = "Invalid number of accounts")] - fn test_mint_invalid_number_of_accounts() { - let pre_states = vec![helper_account_constructor( - AccountsEnum::DefinitionAccountAuth, - )]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - } - - #[test] - #[should_panic(expected = "Holding account must be valid")] - fn test_mint_not_valid_holding_account() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::DefinitionAccountNotAuth), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - } - - #[test] - #[should_panic(expected = "Definition authorization is missing")] - fn test_mint_missing_authorization() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountNotAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - } - - #[test] - #[should_panic(expected = "Mismatch token definition and token holding")] - fn test_mint_mismatched_token_definition() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingDiffDef), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - } - - #[test] - fn test_mint_success() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), - ]; - let post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - - let def_post = post_states[0].clone(); - let holding_post = post_states[1].clone(); - - assert!( - *def_post.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountMint).account - ); - assert!( - *holding_post.account() - == helper_account_constructor(AccountsEnum::HoldingSameDefMint).account - ); - } - - #[test] - fn test_mint_uninit_holding_success() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::Uninit), - ]; - let post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintSuccess), - ); - - let def_post = post_states[0].clone(); - let holding_post = post_states[1].clone(); - - assert!( - *def_post.account() - == helper_account_constructor(AccountsEnum::DefinitionAccountMint).account - ); - assert!( - *holding_post.account() == helper_account_constructor(AccountsEnum::InitMint).account - ); - assert!(holding_post.requires_claim() == true); - } - - #[test] - #[should_panic(expected = "Total supply overflow")] - fn test_mint_total_supply_overflow() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuth), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintOverflow), - ); - } - - #[test] - #[should_panic(expected = "New balance overflow")] - fn test_mint_holding_account_overflow() { - let pre_states = vec![ - helper_account_constructor(AccountsEnum::DefinitionAccountAuth), - helper_account_constructor(AccountsEnum::HoldingSameDefNotAuthOverflow), - ]; - let _post_states = mint_additional_supply( - &pre_states, - helper_balance_constructor(BalanceEnum::MintOverflow), - ); - } -} diff --git a/nssa/src/lib.rs b/nssa/src/lib.rs index e7182c91..de4b65b2 100644 --- a/nssa/src/lib.rs +++ b/nssa/src/lib.rs @@ -1,12 +1,7 @@ -#[cfg(not(feature = "no_docker"))] pub mod program_methods { include!(concat!(env!("OUT_DIR"), "/program_methods/mod.rs")); } -#[cfg(feature = "no_docker")] -#[allow(clippy::single_component_path_imports)] -use program_methods; - pub mod encoding; pub mod error; mod merkle_tree; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 95933a32..1b490de8 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,11 +1,11 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::AccountWithMetadata, - program::{InstructionData, ProgramId, ProgramOutput}, + program::{ChainedCall, InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -43,25 +43,44 @@ impl From for ProgramWithDependencies { } /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution -/// circuit +/// circuit. +#[expect(clippy::too_many_arguments, reason = "TODO: fix later")] pub fn execute_and_prove( - pre_states: &[AccountWithMetadata], - instruction_data: &InstructionData, - visibility_mask: &[u8], - private_account_nonces: &[u128], - private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], - private_account_auth: &[(NullifierSecretKey, MembershipProof)], + pre_states: Vec, + instruction_data: InstructionData, + visibility_mask: Vec, + private_account_nonces: Vec, + private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, + private_account_nsks: Vec, + private_account_membership_proofs: Vec>, program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { - let mut program = &program_with_dependencies.program; - let dependencies = &program_with_dependencies.dependencies; - let mut instruction_data = instruction_data.clone(); - let mut pre_states = pre_states.to_vec(); + let ProgramWithDependencies { + program, + dependencies, + } = program_with_dependencies; let mut env_builder = ExecutorEnv::builder(); let mut program_outputs = Vec::new(); - for _i in 0..MAX_NUMBER_CHAINED_CALLS { - let inner_receipt = execute_and_prove_program(program, &pre_states, &instruction_data)?; + let initial_call = ChainedCall { + program_id: program.id(), + instruction_data: instruction_data.clone(), + pre_states, + pda_seeds: vec![], + }; + + let mut chained_calls = VecDeque::from_iter([(initial_call, program)]); + let mut chain_calls_counter = 0; + while let Some((chained_call, program)) = chained_calls.pop_front() { + if chain_calls_counter >= MAX_NUMBER_CHAINED_CALLS { + return Err(NssaError::MaxChainedCallsDepthExceeded); + } + + let inner_receipt = execute_and_prove_program( + program, + &chained_call.pre_states, + &chained_call.instruction_data, + )?; let program_output: ProgramOutput = inner_receipt .journal @@ -74,38 +93,23 @@ pub fn execute_and_prove( // Prove circuit. env_builder.add_assumption(inner_receipt); - // TODO: Remove when multi-chain calls are supported in the circuit - assert!(program_output.chained_calls.len() <= 1); - // TODO: Modify when multi-chain calls are supported in the circuit - if let Some(next_call) = program_output.chained_calls.first() { - program = dependencies - .get(&next_call.program_id) + for new_call in program_output.chained_calls.into_iter().rev() { + let next_program = dependencies + .get(&new_call.program_id) .ok_or(NssaError::InvalidProgramBehavior)?; - instruction_data = next_call.instruction_data.clone(); - // Build post states with metadata for next call - let mut post_states_with_metadata = Vec::new(); - for (pre, post) in program_output - .pre_states - .iter() - .zip(program_output.post_states) - { - let mut post_with_metadata = pre.clone(); - post_with_metadata.account = post.account().clone(); - post_states_with_metadata.push(post_with_metadata); - } - - pre_states = next_call.pre_states.clone(); - } else { - break; + chained_calls.push_front((new_call, next_program)); } + + chain_calls_counter += 1; } let circuit_input = PrivacyPreservingCircuitInput { program_outputs, - visibility_mask: visibility_mask.to_vec(), - private_account_nonces: private_account_nonces.to_vec(), - private_account_keys: private_account_keys.to_vec(), - private_account_auth: private_account_auth.to_vec(), + visibility_mask, + private_account_nonces, + private_account_keys, + private_account_nsks, + private_account_membership_proofs, program_id: program_with_dependencies.program.id(), }; @@ -212,12 +216,13 @@ mod tests { let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( - &[sender, recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 2], - &[0xdeadbeef], - &[(recipient_keys.npk(), shared_secret.clone())], - &[], + vec![sender, recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![0, 2], + vec![0xdeadbeef], + vec![(recipient_keys.npk(), shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -307,18 +312,16 @@ mod tests { let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( - &[sender_pre.clone(), recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ - (sender_keys.npk(), shared_secret_1.clone()), - (recipient_keys.npk(), shared_secret_2.clone()), + vec![sender_pre.clone(), recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ + (sender_keys.npk(), shared_secret_1), + (recipient_keys.npk(), shared_secret_2), ], - &[( - sender_keys.nsk, - commitment_set.get_proof_for(&commitment_sender).unwrap(), - )], + vec![sender_keys.nsk], + vec![commitment_set.get_proof_for(&commitment_sender), None], &program.into(), ) .unwrap(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 1865248a..943b16ed 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -7,7 +7,7 @@ use serde::Serialize; use crate::{ error::NssaError, - program_methods::{AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}, + program_methods::{AMM_ELF, AUTHENTICATED_TRANSFER_ELF, PINATA_ELF, TOKEN_ELF}, }; /// Maximum number of cycles for a public execution. @@ -95,6 +95,10 @@ impl Program { // `program_methods` Self::new(TOKEN_ELF.to_vec()).unwrap() } + + pub fn amm() -> Self { + Self::new(AMM_ELF.to_vec()).expect("The AMM program must be a valid Risc0 program") + } } // TODO: Testnet only. Refactor to prevent compilation on mainnet. @@ -222,6 +226,35 @@ mod tests { } } + pub fn changer_claimer() -> Self { + use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID}; + + Program { + id: CHANGER_CLAIMER_ID, + elf: CHANGER_CLAIMER_ELF.to_vec(), + } + } + + pub fn noop() -> Self { + use test_program_methods::{NOOP_ELF, NOOP_ID}; + + Program { + id: NOOP_ID, + elf: NOOP_ELF.to_vec(), + } + } + + pub fn malicious_authorization_changer() -> Self { + use test_program_methods::{ + MALICIOUS_AUTHORIZATION_CHANGER_ELF, MALICIOUS_AUTHORIZATION_CHANGER_ID, + }; + + Program { + id: MALICIOUS_AUTHORIZATION_CHANGER_ID, + elf: MALICIOUS_AUTHORIZATION_CHANGER_ELF.to_vec(), + } + } + pub fn modified_transfer_program() -> Self { use test_program_methods::MODIFIED_TRANSFER_ELF; // This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of diff --git a/nssa/src/public_transaction/message.rs b/nssa/src/public_transaction/message.rs index 63ed03f5..d8bd2da0 100644 --- a/nssa/src/public_transaction/message.rs +++ b/nssa/src/public_transaction/message.rs @@ -23,6 +23,7 @@ impl Message { instruction: T, ) -> Result { let instruction_data = Program::serialize_instruction(instruction)?; + Ok(Self { program_id, account_ids, @@ -30,4 +31,18 @@ impl Message { instruction_data, }) } + + pub fn new_preserialized( + program_id: ProgramId, + account_ids: Vec, + nonces: Vec, + instruction_data: InstructionData, + ) -> Self { + Self { + program_id, + account_ids, + nonces, + instruction_data, + } + } } diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 751c63f0..f5badb6a 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -1,9 +1,10 @@ use std::collections::{HashMap, HashSet, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; +use log::debug; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, - program::{ChainedCall, DEFAULT_PROGRAM_ID, PdaSeed, ProgramId, validate_execution}, + program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, }; use sha2::{Digest, digest::FixedOutput}; @@ -118,20 +119,30 @@ impl PublicTransaction { return Err(NssaError::MaxChainedCallsDepthExceeded); } - // Check the `program_id` corresponds to a deployed program + // Check that the `program_id` corresponds to a deployed program let Some(program) = state.programs().get(&chained_call.program_id) else { return Err(NssaError::InvalidInput("Unknown program".into())); }; + debug!( + "Program {:?} pre_states: {:?}, instruction_data: {:?}", + chained_call.program_id, chained_call.pre_states, chained_call.instruction_data + ); let mut program_output = program.execute(&chained_call.pre_states, &chained_call.instruction_data)?; + debug!( + "Program {:?} output: {:?}", + chained_call.program_id, program_output + ); - let authorized_pdas = - self.compute_authorized_pdas(&caller_program_id, &chained_call.pda_seeds); + let authorized_pdas = nssa_core::program::compute_authorized_pdas( + caller_program_id, + &chained_call.pda_seeds, + ); for pre in &program_output.pre_states { let account_id = pre.account_id; - // Check that the program output pre_states coinicide with the values in the public + // Check that the program output pre_states coincide with the values in the public // state or with any modifications to those values during the chain of calls. let expected_pre = state_diff .get(&account_id) @@ -189,22 +200,23 @@ impl PublicTransaction { chain_calls_counter += 1; } - Ok(state_diff) - } - - fn compute_authorized_pdas( - &self, - caller_program_id: &Option, - pda_seeds: &[PdaSeed], - ) -> HashSet { - if let Some(caller_program_id) = caller_program_id { - pda_seeds - .iter() - .map(|pda_seed| AccountId::from((caller_program_id, pda_seed))) - .collect() - } else { - HashSet::new() + // Check that all modified uninitialized accounts where claimed + for post in state_diff.iter().filter_map(|(account_id, post)| { + let pre = state.get_account_by_id(account_id); + if pre.program_owner != DEFAULT_PROGRAM_ID { + return None; + } + if pre == *post { + return None; + } + Some(post) + }) { + if post.program_owner == DEFAULT_PROGRAM_ID { + return Err(NssaError::InvalidProgramBehavior); + } } + + Ok(state_diff) } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 86df3a5e..1a384b2f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -99,6 +99,7 @@ impl V02State { this.insert_program(Program::authenticated_transfer_program()); this.insert_program(Program::token()); + this.insert_program(Program::amm()); this } @@ -340,6 +341,7 @@ pub mod tests { authenticated_transfers_program, ); this.insert(Program::token().id(), Program::token()); + this.insert(Program::amm().id(), Program::amm()); this }; @@ -500,7 +502,9 @@ pub mod tests { 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 } @@ -862,12 +866,13 @@ pub mod tests { let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( - &[sender, recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 2], - &[0xdeadbeef], - &[(recipient_keys.npk(), shared_secret)], - &[], + vec![sender, recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![0, 2], + vec![0xdeadbeef], + vec![(recipient_keys.npk(), shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -908,18 +913,16 @@ pub mod tests { let epk_2 = EphemeralPublicKey::from_scalar(esk_2); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &new_nonces, - &[ + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + new_nonces.to_vec(), + vec![ (sender_keys.npk(), shared_secret_1), (recipient_keys.npk(), shared_secret_2), ], - &[( - sender_keys.nsk, - state.get_proof_for_commitment(&sender_commitment).unwrap(), - )], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment), None], &program.into(), ) .unwrap(); @@ -963,15 +966,13 @@ pub mod tests { let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 0], - &[new_nonce], - &[(sender_keys.npk(), shared_secret)], - &[( - sender_keys.nsk, - state.get_proof_for_commitment(&sender_commitment).unwrap(), - )], + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 0], + vec![new_nonce], + vec![(sender_keys.npk(), shared_secret)], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment)], &program.into(), ) .unwrap(); @@ -1179,12 +1180,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(10u128).unwrap(), - &[0], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(10u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1205,12 +1207,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(10u128).unwrap(), - &[0], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(10u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1231,12 +1234,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1257,12 +1261,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(vec![0]).unwrap(), - &[0], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(vec![0]).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1285,12 +1290,13 @@ pub mod tests { let large_data: Vec = vec![0; nssa_core::account::data::DATA_MAX_LENGTH_IN_BYTES + 1]; let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(large_data).unwrap(), - &[0], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(large_data).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.to_owned().into(), ); @@ -1311,12 +1317,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1346,12 +1353,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(()).unwrap(), - &[0, 0], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(()).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1372,12 +1380,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1407,12 +1416,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[0, 0], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1444,12 +1454,13 @@ pub mod tests { // Setting only one visibility mask for a circuit execution with two pre_state accounts. let visibility_mask = [0]; let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1476,11 +1487,11 @@ pub mod tests { // Setting only one nonce for an execution with two private accounts. let private_account_nonces = [0xdeadbeef1]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &private_account_nonces, - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1490,7 +1501,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[(sender_keys.nsk, (0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1519,12 +1531,55 @@ pub mod tests { SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), )]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &[(sender_keys.nsk, (0, vec![]))], + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_circuit_fails_if_insufficient_commitment_proofs_are_provided() { + let program = Program::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( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + &sender_keys.npk(), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); + + // Setting no second commitment proof. + let private_account_membership_proofs = [Some((0, vec![]))]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ + ( + sender_keys.npk(), + SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), + ), + ( + recipient_keys.npk(), + SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), + ), + ], + vec![sender_keys.nsk], + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -1549,13 +1604,13 @@ pub mod tests { AccountWithMetadata::new(Account::default(), false, &recipient_keys.npk()); // Setting no auth key for an execution with one non default private accounts. - let private_account_auth = []; + let private_account_nsks = []; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1565,7 +1620,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &private_account_auth, + private_account_nsks.to_vec(), + vec![], &program.into(), ); @@ -1601,19 +1657,20 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ]; - let private_account_auth = [ - // Setting the recipient key to authorize the sender. - // This should be set to the sender private account in - // a normal circumstance. The recipient can't authorize this. - (recipient_keys.nsk, (0, vec![])), - ]; + + // Setting the recipient key to authorize the sender. + // This should be set to the sender private account in + // a normal circumstance. The recipient can't authorize this. + let private_account_nsks = [recipient_keys.nsk]; + let private_account_membership_proofs = [Some((0, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &private_account_auth, + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -1645,11 +1702,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1659,7 +1716,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[(sender_keys.nsk, (0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1692,11 +1750,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1706,7 +1764,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[(sender_keys.nsk, (0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1738,11 +1797,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1752,7 +1811,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[(sender_keys.nsk, (0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1784,11 +1844,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1798,7 +1858,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[(sender_keys.nsk, (0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1828,11 +1889,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1842,7 +1903,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[(sender_keys.nsk, (0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1866,12 +1928,13 @@ pub mod tests { let visibility_mask = [0, 3]; let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1899,11 +1962,11 @@ pub mod tests { // accounts. let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &private_account_nonces, - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1913,7 +1976,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[(sender_keys.nsk, (0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1954,12 +2018,13 @@ pub mod tests { ), ]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &[(sender_keys.nsk, (0, vec![]))], + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1986,16 +2051,14 @@ pub mod tests { // Setting two private account keys for a circuit execution with only one non default // private account (visibility mask equal to 1 means that auth keys are expected). let visibility_mask = [1, 2]; - let private_account_auth = [ - (sender_keys.nsk, (0, vec![])), - (recipient_keys.nsk, (1, vec![])), - ]; + let private_account_nsks = [sender_keys.nsk, recipient_keys.nsk]; + let private_account_membership_proofs = [Some((0, vec![])), Some((1, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -2005,7 +2068,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &private_account_auth, + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -2082,21 +2146,20 @@ pub mod tests { ); let visibility_mask = [1, 1]; - let private_account_auth = [ - (sender_keys.nsk, (1, vec![])), - (sender_keys.nsk, (1, vec![])), - ]; + let private_account_nsks = [sender_keys.nsk, sender_keys.nsk]; + let private_account_membership_proofs = [Some((1, vec![])), Some((1, vec![]))]; let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.ivk()); let result = execute_and_prove( - &[private_account_1.clone(), private_account_1], - &Program::serialize_instruction(100u128).unwrap(), - &visibility_mask, - &[0xdeadbeef1, 0xdeadbeef2], - &[ - (sender_keys.npk(), shared_secret.clone()), + vec![private_account_1.clone(), private_account_1], + Program::serialize_instruction(100u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ + (sender_keys.npk(), shared_secret), (sender_keys.npk(), shared_secret), ], - &private_account_auth, + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -2221,6 +2284,1566 @@ pub mod tests { )); } + // TODO: repeated code needs to be cleaned up + // from token.rs (also repeated in amm.rs) + const TOKEN_DEFINITION_DATA_SIZE: usize = 55; + + const TOKEN_HOLDING_DATA_SIZE: usize = 49; + + struct TokenDefinition { + account_type: u8, + name: [u8; 6], + total_supply: u128, + metadata_id: AccountId, + } + + struct TokenHolding { + account_type: u8, + definition_id: AccountId, + balance: u128, + } + impl TokenDefinition { + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Token definition data size must fit into data") + } + } + + impl TokenHolding { + fn into_data(self) -> Data { + let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; + bytes[0] = self.account_type; + bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); + bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); + bytes + .to_vec() + .try_into() + .expect("33 bytes should fit into Data") + } + } + + // TODO repeated code should ultimately be removed; + fn compute_pool_pda( + amm_program_id: ProgramId, + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, + ) -> AccountId { + AccountId::from(( + &amm_program_id, + &compute_pool_pda_seed(definition_token_a_id, definition_token_b_id), + )) + } + + fn compute_pool_pda_seed( + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, + ) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut i: usize = 0; + let (token_1, token_2) = loop { + if definition_token_a_id.value()[i] > definition_token_b_id.value()[i] { + let token_1 = definition_token_a_id; + let token_2 = definition_token_b_id; + break (token_1, token_2); + } else if definition_token_a_id.value()[i] < definition_token_b_id.value()[i] { + let token_1 = definition_token_b_id; + let token_2 = definition_token_a_id; + break (token_1, token_2); + } + + if i == 32 { + panic!("Definitions match"); + } else { + i += 1; + } + }; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&token_1.to_bytes()); + bytes[32..].copy_from_slice(&token_2.to_bytes()); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) + } + + fn compute_vault_pda( + amm_program_id: ProgramId, + pool_id: AccountId, + definition_token_id: AccountId, + ) -> AccountId { + AccountId::from(( + &amm_program_id, + &compute_vault_pda_seed(pool_id, definition_token_id), + )) + } + + fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&pool_id.to_bytes()); + bytes[32..].copy_from_slice(&definition_token_id.to_bytes()); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) + } + + fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId { + AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id))) + } + + fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&pool_id.to_bytes()); + bytes[32..].copy_from_slice(&[0; 32]); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) + } + + const POOL_DEFINITION_DATA_SIZE: usize = 225; + + #[derive(Default)] + struct PoolDefinition { + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, + vault_a_id: AccountId, + vault_b_id: AccountId, + liquidity_pool_id: AccountId, + liquidity_pool_supply: u128, + reserve_a: u128, + reserve_b: u128, + fees: u128, + active: bool, + } + + impl PoolDefinition { + fn into_data(self) -> Data { + let mut bytes = [0; POOL_DEFINITION_DATA_SIZE]; + bytes[0..32].copy_from_slice(&self.definition_token_a_id.to_bytes()); + bytes[32..64].copy_from_slice(&self.definition_token_b_id.to_bytes()); + bytes[64..96].copy_from_slice(&self.vault_a_id.to_bytes()); + bytes[96..128].copy_from_slice(&self.vault_b_id.to_bytes()); + bytes[128..160].copy_from_slice(&self.liquidity_pool_id.to_bytes()); + bytes[160..176].copy_from_slice(&self.liquidity_pool_supply.to_le_bytes()); + bytes[176..192].copy_from_slice(&self.reserve_a.to_le_bytes()); + bytes[192..208].copy_from_slice(&self.reserve_b.to_le_bytes()); + bytes[208..224].copy_from_slice(&self.fees.to_le_bytes()); + bytes[224] = self.active as u8; + + bytes + .to_vec() + .try_into() + .expect("225 bytes should fit into Data") + } + } + + struct PrivateKeysForTests; + + impl PrivateKeysForTests { + fn user_token_a_key() -> PrivateKey { + PrivateKey::try_new([31; 32]).expect("Keys constructor expects valid private key") + } + + fn user_token_b_key() -> PrivateKey { + PrivateKey::try_new([32; 32]).expect("Keys constructor expects valid private key") + } + + fn user_token_lp_key() -> PrivateKey { + PrivateKey::try_new([33; 32]).expect("Keys constructor expects valid private key") + } + } + + struct BalanceForTests; + + impl BalanceForTests { + fn user_token_a_holding_init() -> u128 { + 10_000 + } + + fn user_token_b_holding_init() -> u128 { + 10_000 + } + + fn user_token_lp_holding_init() -> u128 { + 2_000 + } + + fn vault_a_balance_init() -> u128 { + 5_000 + } + + fn vault_b_balance_init() -> u128 { + 2_500 + } + + fn pool_lp_supply_init() -> u128 { + 5_000 + } + + fn token_a_supply() -> u128 { + 100_000 + } + + fn token_b_supply() -> u128 { + 100_000 + } + + fn token_lp_supply() -> u128 { + 5_000 + } + + fn remove_lp() -> u128 { + 1_000 + } + + fn remove_min_amount_a() -> u128 { + 500 + } + + fn remove_min_amount_b() -> u128 { + 500 + } + + fn add_min_amount_lp() -> u128 { + 1_000 + } + + fn add_max_amount_a() -> u128 { + 2_000 + } + + fn add_max_amount_b() -> u128 { + 1_000 + } + + fn swap_amount_in() -> u128 { + 1_000 + } + + fn swap_min_amount_out() -> u128 { + 200 + } + + fn vault_a_balance_swap_1() -> u128 { + 3_572 + } + + fn vault_b_balance_swap_1() -> u128 { + 3_500 + } + + fn user_token_a_holding_swap_1() -> u128 { + 11_428 + } + + fn user_token_b_holding_swap_1() -> u128 { + 9_000 + } + + fn vault_a_balance_swap_2() -> u128 { + 6_000 + } + + fn vault_b_balance_swap_2() -> u128 { + 2_084 + } + + fn user_token_a_holding_swap_2() -> u128 { + 9_000 + } + + fn user_token_b_holding_swap_2() -> u128 { + 10_416 + } + + fn vault_a_balance_add() -> u128 { + 7_000 + } + + fn vault_b_balance_add() -> u128 { + 3_500 + } + + fn user_token_a_holding_add() -> u128 { + 8_000 + } + + fn user_token_b_holding_add() -> u128 { + 9_000 + } + + fn user_token_lp_holding_add() -> u128 { + 4_000 + } + + fn token_lp_supply_add() -> u128 { + 7_000 + } + + fn vault_a_balance_remove() -> u128 { + 4_000 + } + + fn vault_b_balance_remove() -> u128 { + 2_000 + } + + fn user_token_a_holding_remove() -> u128 { + 11_000 + } + + fn user_token_b_holding_remove() -> u128 { + 10_500 + } + + fn user_token_lp_holding_remove() -> u128 { + 1_000 + } + + fn token_lp_supply_remove() -> u128 { + 4_000 + } + + fn user_token_a_holding_new_definition() -> u128 { + 5_000 + } + + fn user_token_b_holding_new_definition() -> u128 { + 7_500 + } + } + + struct IdForTests; + + impl IdForTests { + fn pool_definition_id() -> AccountId { + compute_pool_pda( + Program::amm().id(), + IdForTests::token_a_definition_id(), + IdForTests::token_b_definition_id(), + ) + } + + fn token_lp_definition_id() -> AccountId { + compute_liquidity_token_pda(Program::amm().id(), IdForTests::pool_definition_id()) + } + + fn token_a_definition_id() -> AccountId { + AccountId::new([3; 32]) + } + + fn token_b_definition_id() -> AccountId { + AccountId::new([4; 32]) + } + + fn user_token_a_id() -> AccountId { + AccountId::from(&PublicKey::new_from_private_key( + &PrivateKeysForTests::user_token_a_key(), + )) + } + + fn user_token_b_id() -> AccountId { + AccountId::from(&PublicKey::new_from_private_key( + &PrivateKeysForTests::user_token_b_key(), + )) + } + + fn user_token_lp_id() -> AccountId { + AccountId::from(&PublicKey::new_from_private_key( + &PrivateKeysForTests::user_token_lp_key(), + )) + } + + fn vault_a_id() -> AccountId { + compute_vault_pda( + Program::amm().id(), + IdForTests::pool_definition_id(), + IdForTests::token_a_definition_id(), + ) + } + + fn vault_b_id() -> AccountId { + compute_vault_pda( + Program::amm().id(), + IdForTests::pool_definition_id(), + IdForTests::token_b_definition_id(), + ) + } + } + + struct AccountForTests; + + impl AccountForTests { + fn user_token_a_holding() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::user_token_a_holding_init(), + }), + nonce: 0, + } + } + + fn user_token_b_holding() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::user_token_b_holding_init(), + }), + nonce: 0, + } + } + + fn pool_definition_init() -> Account { + Account { + program_owner: Program::amm().id(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::pool_lp_supply_init(), + reserve_a: BalanceForTests::vault_a_balance_init(), + reserve_b: BalanceForTests::vault_b_balance_init(), + fees: 0u128, + active: true, + }), + nonce: 0, + } + } + + fn token_a_definition_account() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1u8; 6], + total_supply: BalanceForTests::token_a_supply(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + } + } + + fn token_b_definition_acc() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1u8; 6], + total_supply: BalanceForTests::token_b_supply(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + } + } + + fn token_lp_definition_acc() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1u8; 6], + total_supply: BalanceForTests::token_lp_supply(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + } + } + + fn vault_a_init() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_balance_init(), + }), + nonce: 0, + } + } + + fn vault_b_init() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_balance_init(), + }), + nonce: 0, + } + } + + fn user_token_lp_holding() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_lp_definition_id(), + balance: BalanceForTests::user_token_lp_holding_init(), + }), + nonce: 0, + } + } + + fn vault_a_swap_1() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_balance_swap_1(), + }), + nonce: 0, + } + } + + fn vault_b_swap_1() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_balance_swap_1(), + }), + nonce: 0, + } + } + + fn pool_definition_swap_1() -> Account { + Account { + program_owner: Program::amm().id(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::pool_lp_supply_init(), + reserve_a: BalanceForTests::vault_a_balance_swap_1(), + reserve_b: BalanceForTests::vault_b_balance_swap_1(), + fees: 0u128, + active: true, + }), + nonce: 0, + } + } + + fn user_token_a_holding_swap_1() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::user_token_a_holding_swap_1(), + }), + nonce: 0, + } + } + + fn user_token_b_holding_swap_1() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::user_token_b_holding_swap_1(), + }), + nonce: 1, + } + } + + fn vault_a_swap_2() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_balance_swap_2(), + }), + nonce: 0, + } + } + + fn vault_b_swap_2() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_balance_swap_2(), + }), + nonce: 0, + } + } + + fn pool_definition_swap_2() -> Account { + Account { + program_owner: Program::amm().id(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::pool_lp_supply_init(), + reserve_a: BalanceForTests::vault_a_balance_swap_2(), + reserve_b: BalanceForTests::vault_b_balance_swap_2(), + fees: 0u128, + active: true, + }), + nonce: 0, + } + } + + fn user_token_a_holding_swap_2() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::user_token_a_holding_swap_2(), + }), + nonce: 1, + } + } + + fn user_token_b_holding_swap_2() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::user_token_b_holding_swap_2(), + }), + nonce: 0, + } + } + + fn vault_a_add() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_balance_add(), + }), + nonce: 0, + } + } + + fn vault_b_add() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_balance_add(), + }), + nonce: 0, + } + } + + fn pool_definition_add() -> Account { + Account { + program_owner: Program::amm().id(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::token_lp_supply_add(), + reserve_a: BalanceForTests::vault_a_balance_add(), + reserve_b: BalanceForTests::vault_b_balance_add(), + fees: 0u128, + active: true, + }), + nonce: 0, + } + } + + fn user_token_a_holding_add() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::user_token_a_holding_add(), + }), + nonce: 1, + } + } + + fn user_token_b_holding_add() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::user_token_b_holding_add(), + }), + nonce: 1, + } + } + + fn user_token_lp_holding_add() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_lp_definition_id(), + balance: BalanceForTests::user_token_lp_holding_add(), + }), + nonce: 0, + } + } + + fn token_lp_definition_add() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1u8; 6], + total_supply: BalanceForTests::token_lp_supply_add(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + } + } + + fn vault_a_remove() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_balance_remove(), + }), + nonce: 0, + } + } + + fn vault_b_remove() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_balance_remove(), + }), + nonce: 0, + } + } + + fn pool_definition_remove() -> Account { + Account { + program_owner: Program::amm().id(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::token_lp_supply_remove(), + reserve_a: BalanceForTests::vault_a_balance_remove(), + reserve_b: BalanceForTests::vault_b_balance_remove(), + fees: 0u128, + active: true, + }), + nonce: 0, + } + } + + fn user_token_a_holding_remove() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::user_token_a_holding_remove(), + }), + nonce: 0, + } + } + + fn user_token_b_holding_remove() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::user_token_b_holding_remove(), + }), + nonce: 0, + } + } + + fn user_token_lp_holding_remove() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_lp_definition_id(), + balance: BalanceForTests::user_token_lp_holding_remove(), + }), + nonce: 1, + } + } + + fn token_lp_definition_remove() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1u8; 6], + total_supply: BalanceForTests::token_lp_supply_remove(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + } + } + + fn token_lp_definition_init_inactive() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1u8; 6], + total_supply: 0, + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + } + } + + fn vault_a_init_inactive() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: 0, + }), + nonce: 0, + } + } + + fn vault_b_init_inactive() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: 0, + }), + nonce: 0, + } + } + + fn pool_definition_inactive() -> Account { + Account { + program_owner: Program::amm().id(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: 0, + reserve_a: 0, + reserve_b: 0, + fees: 0u128, + active: false, + }), + nonce: 0, + } + } + + fn user_token_a_holding_new_init() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::user_token_a_holding_new_definition(), + }), + nonce: 1, + } + } + + fn user_token_b_holding_new_init() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::user_token_b_holding_new_definition(), + }), + nonce: 1, + } + } + + fn user_token_lp_holding_new_init() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_lp_definition_id(), + balance: BalanceForTests::user_token_a_holding_new_definition(), + }), + nonce: 0, + } + } + + fn token_lp_definition_new_init() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1u8; 6], + total_supply: BalanceForTests::vault_a_balance_init(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + } + } + + fn pool_definition_new_init() -> Account { + Account { + program_owner: Program::amm().id(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::user_token_a_holding_new_definition(), + reserve_a: BalanceForTests::vault_a_balance_init(), + reserve_b: BalanceForTests::vault_b_balance_init(), + fees: 0u128, + active: true, + }), + nonce: 0, + } + } + + fn user_token_lp_holding_init_zero() -> Account { + Account { + program_owner: Program::token().id(), + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_lp_definition_id(), + balance: 0, + }), + nonce: 0, + } + } + } + + const AMM_NEW_DEFINITION: u8 = 0; + const AMM_SWAP: u8 = 1; + const AMM_ADD_LIQUIDITY: u8 = 2; + const AMM_REMOVE_LIQUIDITY: u8 = 3; + + fn state_for_amm_tests() -> V02State { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + state.force_insert_account( + IdForTests::pool_definition_id(), + AccountForTests::pool_definition_init(), + ); + state.force_insert_account( + IdForTests::token_a_definition_id(), + AccountForTests::token_a_definition_account(), + ); + state.force_insert_account( + IdForTests::token_b_definition_id(), + AccountForTests::token_b_definition_acc(), + ); + state.force_insert_account( + IdForTests::token_lp_definition_id(), + AccountForTests::token_lp_definition_acc(), + ); + state.force_insert_account( + IdForTests::user_token_a_id(), + AccountForTests::user_token_a_holding(), + ); + state.force_insert_account( + IdForTests::user_token_b_id(), + AccountForTests::user_token_b_holding(), + ); + state.force_insert_account( + IdForTests::user_token_lp_id(), + AccountForTests::user_token_lp_holding(), + ); + state.force_insert_account(IdForTests::vault_a_id(), AccountForTests::vault_a_init()); + state.force_insert_account(IdForTests::vault_b_id(), AccountForTests::vault_b_init()); + + state + } + + fn state_for_amm_tests_with_new_def() -> V02State { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + state.force_insert_account( + IdForTests::token_a_definition_id(), + AccountForTests::token_a_definition_account(), + ); + state.force_insert_account( + IdForTests::token_b_definition_id(), + AccountForTests::token_b_definition_acc(), + ); + state.force_insert_account( + IdForTests::user_token_a_id(), + AccountForTests::user_token_a_holding(), + ); + state.force_insert_account( + IdForTests::user_token_b_id(), + AccountForTests::user_token_b_holding(), + ); + state + } + + #[test] + fn test_simple_amm_remove() { + let mut state = state_for_amm_tests(); + + let mut instruction: Vec = Vec::new(); + instruction.push(AMM_REMOVE_LIQUIDITY); + instruction.extend_from_slice(&BalanceForTests::remove_lp().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::remove_min_amount_a().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::remove_min_amount_b().to_le_bytes()); + + let message = public_transaction::Message::try_new( + Program::amm().id(), + vec![ + IdForTests::pool_definition_id(), + IdForTests::vault_a_id(), + IdForTests::vault_b_id(), + IdForTests::token_lp_definition_id(), + IdForTests::user_token_a_id(), + IdForTests::user_token_b_id(), + IdForTests::user_token_lp_id(), + ], + vec![0], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[&PrivateKeysForTests::user_token_lp_key()], + ); + + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let pool_post = state.get_account_by_id(&IdForTests::pool_definition_id()); + let vault_a_post = state.get_account_by_id(&IdForTests::vault_a_id()); + let vault_b_post = state.get_account_by_id(&IdForTests::vault_b_id()); + let token_lp_post = state.get_account_by_id(&IdForTests::token_lp_definition_id()); + let user_token_a_post = state.get_account_by_id(&IdForTests::user_token_a_id()); + let user_token_b_post = state.get_account_by_id(&IdForTests::user_token_b_id()); + let user_token_lp_post = state.get_account_by_id(&IdForTests::user_token_lp_id()); + + let expected_pool = AccountForTests::pool_definition_remove(); + let expected_vault_a = AccountForTests::vault_a_remove(); + let expected_vault_b = AccountForTests::vault_b_remove(); + let expected_token_lp = AccountForTests::token_lp_definition_remove(); + let expected_user_token_a = AccountForTests::user_token_a_holding_remove(); + let expected_user_token_b = AccountForTests::user_token_b_holding_remove(); + let expected_user_token_lp = AccountForTests::user_token_lp_holding_remove(); + + assert_eq!(pool_post, expected_pool); + assert_eq!(vault_a_post, expected_vault_a); + assert_eq!(vault_b_post, expected_vault_b); + assert_eq!(token_lp_post, expected_token_lp); + assert_eq!(user_token_a_post, expected_user_token_a); + assert_eq!(user_token_b_post, expected_user_token_b); + assert_eq!(user_token_lp_post, expected_user_token_lp); + } + + #[test] + fn test_simple_amm_new_definition_inactive_initialized_pool_and_uninit_user_lp() { + let mut state = state_for_amm_tests_with_new_def(); + + // Uninitialized in constructor + state.force_insert_account( + IdForTests::vault_a_id(), + AccountForTests::vault_a_init_inactive(), + ); + state.force_insert_account( + IdForTests::vault_b_id(), + AccountForTests::vault_b_init_inactive(), + ); + state.force_insert_account( + IdForTests::pool_definition_id(), + AccountForTests::pool_definition_inactive(), + ); + state.force_insert_account( + IdForTests::token_lp_definition_id(), + AccountForTests::token_lp_definition_init_inactive(), + ); + + let mut instruction: Vec = Vec::new(); + instruction.push(AMM_NEW_DEFINITION); + instruction.extend_from_slice(&BalanceForTests::vault_a_balance_init().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::vault_b_balance_init().to_le_bytes()); + let amm_program_u8: [u8; 32] = bytemuck::cast(Program::amm().id()); + instruction.extend_from_slice(&amm_program_u8); + + let message = public_transaction::Message::try_new( + Program::amm().id(), + vec![ + IdForTests::pool_definition_id(), + IdForTests::vault_a_id(), + IdForTests::vault_b_id(), + IdForTests::token_lp_definition_id(), + IdForTests::user_token_a_id(), + IdForTests::user_token_b_id(), + IdForTests::user_token_lp_id(), + ], + vec![0, 0], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[ + &PrivateKeysForTests::user_token_a_key(), + &PrivateKeysForTests::user_token_b_key(), + ], + ); + + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let pool_post = state.get_account_by_id(&IdForTests::pool_definition_id()); + let vault_a_post = state.get_account_by_id(&IdForTests::vault_a_id()); + let vault_b_post = state.get_account_by_id(&IdForTests::vault_b_id()); + let token_lp_post = state.get_account_by_id(&IdForTests::token_lp_definition_id()); + let user_token_a_post = state.get_account_by_id(&IdForTests::user_token_a_id()); + let user_token_b_post = state.get_account_by_id(&IdForTests::user_token_b_id()); + let user_token_lp_post = state.get_account_by_id(&IdForTests::user_token_lp_id()); + + let expected_pool = AccountForTests::pool_definition_new_init(); + let expected_vault_a = AccountForTests::vault_a_init(); + let expected_vault_b = AccountForTests::vault_b_init(); + let expected_token_lp = AccountForTests::token_lp_definition_new_init(); + let expected_user_token_a = AccountForTests::user_token_a_holding_new_init(); + let expected_user_token_b = AccountForTests::user_token_b_holding_new_init(); + let expected_user_token_lp = AccountForTests::user_token_lp_holding_new_init(); + + assert_eq!(pool_post, expected_pool); + assert_eq!(vault_a_post, expected_vault_a); + assert_eq!(vault_b_post, expected_vault_b); + assert_eq!(token_lp_post, expected_token_lp); + assert_eq!(user_token_a_post, expected_user_token_a); + assert_eq!(user_token_b_post, expected_user_token_b); + assert_eq!(user_token_lp_post, expected_user_token_lp); + } + + #[test] + fn test_simple_amm_new_definition_inactive_initialized_pool_init_user_lp() { + let mut state = state_for_amm_tests_with_new_def(); + + // Uninitialized in constructor + state.force_insert_account( + IdForTests::vault_a_id(), + AccountForTests::vault_a_init_inactive(), + ); + state.force_insert_account( + IdForTests::vault_b_id(), + AccountForTests::vault_b_init_inactive(), + ); + state.force_insert_account( + IdForTests::pool_definition_id(), + AccountForTests::pool_definition_inactive(), + ); + state.force_insert_account( + IdForTests::token_lp_definition_id(), + AccountForTests::token_lp_definition_init_inactive(), + ); + state.force_insert_account( + IdForTests::user_token_lp_id(), + AccountForTests::user_token_lp_holding_init_zero(), + ); + + let mut instruction: Vec = Vec::new(); + instruction.push(AMM_NEW_DEFINITION); + instruction.extend_from_slice(&BalanceForTests::vault_a_balance_init().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::vault_b_balance_init().to_le_bytes()); + let amm_program_u8: [u8; 32] = bytemuck::cast(Program::amm().id()); + instruction.extend_from_slice(&amm_program_u8); + + let message = public_transaction::Message::try_new( + Program::amm().id(), + vec![ + IdForTests::pool_definition_id(), + IdForTests::vault_a_id(), + IdForTests::vault_b_id(), + IdForTests::token_lp_definition_id(), + IdForTests::user_token_a_id(), + IdForTests::user_token_b_id(), + IdForTests::user_token_lp_id(), + ], + vec![0, 0], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[ + &PrivateKeysForTests::user_token_a_key(), + &PrivateKeysForTests::user_token_b_key(), + ], + ); + + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let pool_post = state.get_account_by_id(&IdForTests::pool_definition_id()); + let vault_a_post = state.get_account_by_id(&IdForTests::vault_a_id()); + let vault_b_post = state.get_account_by_id(&IdForTests::vault_b_id()); + let token_lp_post = state.get_account_by_id(&IdForTests::token_lp_definition_id()); + let user_token_a_post = state.get_account_by_id(&IdForTests::user_token_a_id()); + let user_token_b_post = state.get_account_by_id(&IdForTests::user_token_b_id()); + let user_token_lp_post = state.get_account_by_id(&IdForTests::user_token_lp_id()); + + let expected_pool = AccountForTests::pool_definition_init(); + let expected_vault_a = AccountForTests::vault_a_init(); + let expected_vault_b = AccountForTests::vault_b_init(); + let expected_token_lp = AccountForTests::token_lp_definition_new_init(); + let expected_user_token_a = AccountForTests::user_token_a_holding_new_init(); + let expected_user_token_b = AccountForTests::user_token_b_holding_new_init(); + let expected_user_token_lp = AccountForTests::user_token_lp_holding_new_init(); + + assert_eq!(pool_post, expected_pool); + assert_eq!(vault_a_post, expected_vault_a); + assert_eq!(vault_b_post, expected_vault_b); + assert_eq!(token_lp_post, expected_token_lp); + assert_eq!(user_token_a_post, expected_user_token_a); + assert_eq!(user_token_b_post, expected_user_token_b); + assert_eq!(user_token_lp_post, expected_user_token_lp); + } + + #[test] + fn test_simple_amm_new_definition_uninitialized_pool() { + let mut state = state_for_amm_tests_with_new_def(); + + // Uninitialized in constructor + state.force_insert_account( + IdForTests::vault_a_id(), + AccountForTests::vault_a_init_inactive(), + ); + state.force_insert_account( + IdForTests::vault_b_id(), + AccountForTests::vault_b_init_inactive(), + ); + + let mut instruction: Vec = Vec::new(); + instruction.push(AMM_NEW_DEFINITION); + instruction.extend_from_slice(&BalanceForTests::vault_a_balance_init().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::vault_b_balance_init().to_le_bytes()); + let amm_program_u8: [u8; 32] = bytemuck::cast(Program::amm().id()); + instruction.extend_from_slice(&amm_program_u8); + + let message = public_transaction::Message::try_new( + Program::amm().id(), + vec![ + IdForTests::pool_definition_id(), + IdForTests::vault_a_id(), + IdForTests::vault_b_id(), + IdForTests::token_lp_definition_id(), + IdForTests::user_token_a_id(), + IdForTests::user_token_b_id(), + IdForTests::user_token_lp_id(), + ], + vec![0, 0], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[ + &PrivateKeysForTests::user_token_a_key(), + &PrivateKeysForTests::user_token_b_key(), + ], + ); + + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let pool_post = state.get_account_by_id(&IdForTests::pool_definition_id()); + let vault_a_post = state.get_account_by_id(&IdForTests::vault_a_id()); + let vault_b_post = state.get_account_by_id(&IdForTests::vault_b_id()); + let token_lp_post = state.get_account_by_id(&IdForTests::token_lp_definition_id()); + let user_token_a_post = state.get_account_by_id(&IdForTests::user_token_a_id()); + let user_token_b_post = state.get_account_by_id(&IdForTests::user_token_b_id()); + let user_token_lp_post = state.get_account_by_id(&IdForTests::user_token_lp_id()); + + let expected_pool = AccountForTests::pool_definition_new_init(); + let expected_vault_a = AccountForTests::vault_a_init(); + let expected_vault_b = AccountForTests::vault_b_init(); + let expected_token_lp = AccountForTests::token_lp_definition_new_init(); + let expected_user_token_a = AccountForTests::user_token_a_holding_new_init(); + let expected_user_token_b = AccountForTests::user_token_b_holding_new_init(); + let expected_user_token_lp = AccountForTests::user_token_lp_holding_new_init(); + + assert_eq!(pool_post, expected_pool); + assert_eq!(vault_a_post, expected_vault_a); + assert_eq!(vault_b_post, expected_vault_b); + assert_eq!(token_lp_post, expected_token_lp); + assert_eq!(user_token_a_post, expected_user_token_a); + assert_eq!(user_token_b_post, expected_user_token_b); + assert_eq!(user_token_lp_post, expected_user_token_lp); + } + + #[test] + fn test_simple_amm_add() { + env_logger::init(); + let mut state = state_for_amm_tests(); + + let mut instruction: Vec = Vec::new(); + instruction.push(AMM_ADD_LIQUIDITY); + instruction.extend_from_slice(&BalanceForTests::add_min_amount_lp().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::add_max_amount_a().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::add_max_amount_b().to_le_bytes()); + + let message = public_transaction::Message::try_new( + Program::amm().id(), + vec![ + IdForTests::pool_definition_id(), + IdForTests::vault_a_id(), + IdForTests::vault_b_id(), + IdForTests::token_lp_definition_id(), + IdForTests::user_token_a_id(), + IdForTests::user_token_b_id(), + IdForTests::user_token_lp_id(), + ], + vec![0, 0], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[ + &PrivateKeysForTests::user_token_a_key(), + &PrivateKeysForTests::user_token_b_key(), + ], + ); + + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let pool_post = state.get_account_by_id(&IdForTests::pool_definition_id()); + let vault_a_post = state.get_account_by_id(&IdForTests::vault_a_id()); + let vault_b_post = state.get_account_by_id(&IdForTests::vault_b_id()); + let token_lp_post = state.get_account_by_id(&IdForTests::token_lp_definition_id()); + let user_token_a_post = state.get_account_by_id(&IdForTests::user_token_a_id()); + let user_token_b_post = state.get_account_by_id(&IdForTests::user_token_b_id()); + let user_token_lp_post = state.get_account_by_id(&IdForTests::user_token_lp_id()); + + let expected_pool = AccountForTests::pool_definition_add(); + let expected_vault_a = AccountForTests::vault_a_add(); + let expected_vault_b = AccountForTests::vault_b_add(); + let expected_token_lp = AccountForTests::token_lp_definition_add(); + let expected_user_token_a = AccountForTests::user_token_a_holding_add(); + let expected_user_token_b = AccountForTests::user_token_b_holding_add(); + let expected_user_token_lp = AccountForTests::user_token_lp_holding_add(); + + assert_eq!(pool_post, expected_pool); + assert_eq!(vault_a_post, expected_vault_a); + assert_eq!(vault_b_post, expected_vault_b); + assert_eq!(token_lp_post, expected_token_lp); + assert_eq!(user_token_a_post, expected_user_token_a); + assert_eq!(user_token_b_post, expected_user_token_b); + assert_eq!(user_token_lp_post, expected_user_token_lp); + } + + #[test] + fn test_simple_amm_swap_1() { + let mut state = state_for_amm_tests(); + + let mut instruction: Vec = Vec::new(); + instruction.push(AMM_SWAP); + instruction.extend_from_slice(&BalanceForTests::swap_amount_in().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::swap_min_amount_out().to_le_bytes()); + instruction.extend_from_slice(&IdForTests::token_b_definition_id().to_bytes()); + + let message = public_transaction::Message::try_new( + Program::amm().id(), + vec![ + IdForTests::pool_definition_id(), + IdForTests::vault_a_id(), + IdForTests::vault_b_id(), + IdForTests::user_token_a_id(), + IdForTests::user_token_b_id(), + ], + vec![0], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[&PrivateKeysForTests::user_token_b_key()], + ); + + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let pool_post = state.get_account_by_id(&IdForTests::pool_definition_id()); + let vault_a_post = state.get_account_by_id(&IdForTests::vault_a_id()); + let vault_b_post = state.get_account_by_id(&IdForTests::vault_b_id()); + let user_token_a_post = state.get_account_by_id(&IdForTests::user_token_a_id()); + let user_token_b_post = state.get_account_by_id(&IdForTests::user_token_b_id()); + + let expected_pool = AccountForTests::pool_definition_swap_1(); + let expected_vault_a = AccountForTests::vault_a_swap_1(); + let expected_vault_b = AccountForTests::vault_b_swap_1(); + let expected_user_token_a = AccountForTests::user_token_a_holding_swap_1(); + let expected_user_token_b = AccountForTests::user_token_b_holding_swap_1(); + + assert_eq!(pool_post, expected_pool); + assert_eq!(vault_a_post, expected_vault_a); + assert_eq!(vault_b_post, expected_vault_b); + assert_eq!(user_token_a_post, expected_user_token_a); + assert_eq!(user_token_b_post, expected_user_token_b); + } + + #[test] + fn test_simple_amm_swap_2() { + let mut state = state_for_amm_tests(); + + let mut instruction: Vec = Vec::new(); + instruction.push(AMM_SWAP); + instruction.extend_from_slice(&BalanceForTests::swap_amount_in().to_le_bytes()); + instruction.extend_from_slice(&BalanceForTests::swap_min_amount_out().to_le_bytes()); + instruction.extend_from_slice(&IdForTests::token_a_definition_id().to_bytes()); + + let message = public_transaction::Message::try_new( + Program::amm().id(), + vec![ + IdForTests::pool_definition_id(), + IdForTests::vault_a_id(), + IdForTests::vault_b_id(), + IdForTests::user_token_a_id(), + IdForTests::user_token_b_id(), + ], + vec![0], + instruction, + ) + .unwrap(); + + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[&PrivateKeysForTests::user_token_a_key()], + ); + + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx).unwrap(); + + let pool_post = state.get_account_by_id(&IdForTests::pool_definition_id()); + let vault_a_post = state.get_account_by_id(&IdForTests::vault_a_id()); + let vault_b_post = state.get_account_by_id(&IdForTests::vault_b_id()); + let user_token_a_post = state.get_account_by_id(&IdForTests::user_token_a_id()); + let user_token_b_post = state.get_account_by_id(&IdForTests::user_token_b_id()); + + let expected_pool = AccountForTests::pool_definition_swap_2(); + let expected_vault_a = AccountForTests::vault_a_swap_2(); + let expected_vault_b = AccountForTests::vault_b_swap_2(); + let expected_user_token_a = AccountForTests::user_token_a_holding_swap_2(); + let expected_user_token_b = AccountForTests::user_token_b_holding_swap_2(); + + assert_eq!(pool_post, expected_pool); + assert_eq!(vault_a_post, expected_vault_a); + assert_eq!(vault_b_post, expected_vault_b); + assert_eq!(user_token_a_post, expected_user_token_a); + assert_eq!(user_token_b_post, expected_user_token_b); + } + #[test] fn test_execution_that_requires_authentication_of_a_program_derived_account_id_succeeds() { let chain_caller = Program::chain_caller(); @@ -2319,8 +3942,9 @@ pub mod tests { assert_eq!(to_post, expected_to_post); } - #[test] - fn test_private_chained_call() { + #[test_case::test_case(1; "single call")] + #[test_case::test_case(2; "two calls")] + fn test_private_chained_call(number_of_calls: u32) { // Arrange let chain_caller = Program::chain_caller(); let auth_transfers = Program::authenticated_transfer_program(); @@ -2356,7 +3980,7 @@ pub mod tests { let instruction: (u128, ProgramId, u32, Option) = ( amount, Program::authenticated_transfer_program().id(), - 1, + number_of_calls, None, ); @@ -2377,14 +4001,14 @@ pub mod tests { let to_new_nonce = 0xdeadbeef2; let from_expected_post = Account { - balance: initial_balance - amount, + balance: initial_balance - number_of_calls as u128 * amount, nonce: from_new_nonce, ..from_account.account.clone() }; let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post); let to_expected_post = Account { - balance: amount, + balance: number_of_calls as u128 * amount, nonce: to_new_nonce, ..to_account.account.clone() }; @@ -2392,20 +4016,15 @@ pub mod tests { // Act let (output, proof) = execute_and_prove( - &[to_account, from_account], - &Program::serialize_instruction(instruction).unwrap(), - &[1, 1], - &[from_new_nonce, to_new_nonce], - &[(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], - &[ - ( - from_keys.nsk, - state.get_proof_for_commitment(&from_commitment).unwrap(), - ), - ( - to_keys.nsk, - state.get_proof_for_commitment(&to_commitment).unwrap(), - ), + vec![to_account, from_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1, 1], + vec![from_new_nonce, to_new_nonce], + vec![(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], + vec![from_keys.nsk, to_keys.nsk], + vec![ + state.get_proof_for_commitment(&from_commitment), + state.get_proof_for_commitment(&to_commitment), ], &program_with_deps, ) @@ -2469,7 +4088,7 @@ pub mod tests { // definition and supply accounts let total_supply: u128 = 10_000_000; // instruction: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(b"PINATA"); let message = public_transaction::Message::try_new( @@ -2484,7 +4103,7 @@ pub mod tests { state.transition_from_public_transaction(&tx).unwrap(); // Execution of the token program transfer just to initialize the winner token account - let mut instruction: [u8; 23] = [0; 23]; + let mut instruction = vec![0; 23]; instruction[0] = 2; let message = public_transaction::Message::try_new( token.id(), @@ -2609,7 +4228,304 @@ pub mod tests { this }; - assert!(expected_sender_post == sender_post); - assert!(expected_recipient_post == recipient_post); + assert_eq!(expected_sender_post, sender_post); + assert_eq!(expected_recipient_post, recipient_post); + } + + #[test] + fn test_private_authorized_uninitialized_account() { + let mut state = V02State::new_with_genesis_accounts(&[], &[]); + + // Set up keys for the authorized private account + let private_keys = test_private_account_keys_1(); + + // Create an authorized private account with default values (new account being initialized) + let authorized_account = + AccountWithMetadata::new(Account::default(), true, &private_keys.npk()); + + let program = Program::authenticated_transfer_program(); + + // Set up parameters for the new account + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &private_keys.ivk()); + let epk = EphemeralPublicKey::from_scalar(esk); + + // Balance to initialize the account with (0 for a new account) + let balance: u128 = 0; + + let nonce = 0xdeadbeef1; + + // Execute and prove the circuit with the authorized account but no commitment proof + let (output, proof) = execute_and_prove( + vec![authorized_account], + Program::serialize_instruction(balance).unwrap(), + vec![1], + vec![nonce], + vec![(private_keys.npk(), shared_secret)], + vec![private_keys.nsk], + vec![None], + &program.into(), + ) + .unwrap(); + + // Create message from circuit output + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![(private_keys.npk(), private_keys.ivk(), epk)], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + let result = state.transition_from_privacy_preserving_transaction(&tx); + assert!(result.is_ok()); + + let nullifier = Nullifier::for_account_initialization(&private_keys.npk()); + assert!(state.private_state.1.contains(&nullifier)); + } + + #[test] + fn test_private_account_claimed_then_used_without_init_flag_should_fail() { + let mut state = V02State::new_with_genesis_accounts(&[], &[]).with_test_programs(); + + // Set up keys for the private account + let private_keys = test_private_account_keys_1(); + + // Step 1: Create a new private account with authorization + let authorized_account = + AccountWithMetadata::new(Account::default(), true, &private_keys.npk()); + + let claimer_program = Program::claimer(); + + // Set up parameters for claiming the new account + let esk = [3; 32]; + let shared_secret = SharedSecretKey::new(&esk, &private_keys.ivk()); + let epk = EphemeralPublicKey::from_scalar(esk); + + let balance: u128 = 0; + let nonce = 0xdeadbeef1; + + // Step 2: Execute claimer program to claim the account with authentication + let (output, proof) = execute_and_prove( + vec![authorized_account.clone()], + Program::serialize_instruction(balance).unwrap(), + vec![1], + vec![nonce], + vec![(private_keys.npk(), shared_secret)], + vec![private_keys.nsk], + vec![None], + &claimer_program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![(private_keys.npk(), private_keys.ivk(), epk)], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + // Claim should succeed + assert!( + state + .transition_from_privacy_preserving_transaction(&tx) + .is_ok() + ); + + // Verify the account is now initialized (nullifier exists) + let nullifier = Nullifier::for_account_initialization(&private_keys.npk()); + assert!(state.private_state.1.contains(&nullifier)); + + // Prepare new state of account + let account_metadata = { + let mut acc = authorized_account.clone(); + acc.account.program_owner = Program::claimer().id(); + acc + }; + + let noop_program = Program::noop(); + let esk2 = [4; 32]; + let shared_secret2 = SharedSecretKey::new(&esk2, &private_keys.ivk()); + + let nonce2 = 0xdeadbeef2; + + // Step 3: Try to execute noop program with authentication but without initialization + let res = execute_and_prove( + vec![account_metadata], + Program::serialize_instruction(()).unwrap(), + vec![1], + vec![nonce2], + vec![(private_keys.npk(), shared_secret2)], + vec![private_keys.nsk], + vec![None], + &noop_program.into(), + ); + + assert!(matches!(res, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_public_changer_claimer_no_data_change_no_claim_succeeds() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::changer_claimer().id(); + // Don't change data (None) and don't claim (false) + let instruction: (Option>, bool) = (None, false); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], instruction) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + // Should succeed - no changes made, no claim needed + assert!(result.is_ok()); + // Account should remain default/unclaimed + assert_eq!(state.get_account_by_id(&account_id), Account::default()); + } + + #[test] + fn test_public_changer_claimer_data_change_no_claim_fails() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::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); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], instruction) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + // Should fail - cannot modify data without claiming the account + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_private_changer_claimer_no_data_change_no_claim_succeeds() { + let program = Program::changer_claimer(); + let sender_keys = test_private_account_keys_1(); + let private_account = + AccountWithMetadata::new(Account::default(), true, &sender_keys.npk()); + // Don't change data (None) and don't claim (false) + let instruction: (Option>, bool) = (None, false); + + let result = execute_and_prove( + vec![private_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1], + vec![2], + vec![( + sender_keys.npk(), + SharedSecretKey::new(&[3; 32], &sender_keys.ivk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + // Should succeed - no changes made, no claim needed + assert!(result.is_ok()); + } + + #[test] + fn test_private_changer_claimer_data_change_no_claim_fails() { + let program = Program::changer_claimer(); + let sender_keys = test_private_account_keys_1(); + let private_account = + AccountWithMetadata::new(Account::default(), true, &sender_keys.npk()); + // 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); + + let result = execute_and_prove( + vec![private_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1], + vec![2], + vec![( + sender_keys.npk(), + SharedSecretKey::new(&[3; 32], &sender_keys.ivk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + // Should fail - cannot modify data without claiming the account + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_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 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(), + balance: 100, + ..Default::default() + }, + false, + sender_keys.account_id(), + ); + let recipient_account = + AccountWithMetadata::new(Account::default(), true, &recipient_keys.npk()); + + let recipient_commitment = + Commitment::new(&recipient_keys.npk(), &recipient_account.account); + let state = V02State::new_with_genesis_accounts( + &[(sender_account.account_id, sender_account.account.balance)], + std::slice::from_ref(&recipient_commitment), + ) + .with_test_programs(); + + let balance_to_transfer = 10u128; + let instruction = (balance_to_transfer, auth_transfers.id()); + + let recipient_esk = [3; 32]; + let recipient = SharedSecretKey::new(&recipient_esk, &recipient_keys.ivk()); + + let mut dependencies = HashMap::new(); + dependencies.insert(auth_transfers.id(), auth_transfers); + let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies); + + let recipient_new_nonce = 0xdeadbeef1; + + // Act - execute the malicious program - this should fail during proving + let result = execute_and_prove( + vec![sender_account, recipient_account], + Program::serialize_instruction(instruction).unwrap(), + vec![0, 1], + vec![recipient_new_nonce], + vec![(recipient_keys.npk(), recipient)], + vec![recipient_keys.nsk], + vec![state.get_proof_for_commitment(&recipient_commitment)], + &program_with_deps, + ); + + // Assert - should fail because the malicious program tries to manipulate is_authorized + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); } } diff --git a/nssa/test_program_methods/guest/Cargo.lock b/nssa/test_program_methods/guest/Cargo.lock deleted file mode 100644 index b2337cc2..00000000 --- a/nssa/test_program_methods/guest/Cargo.lock +++ /dev/null @@ -1,3601 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" - -[[package]] -name = "ark-bn254" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" -dependencies = [ - "ark-ec", - "ark-ff", - "ark-r1cs-std", - "ark-std", -] - -[[package]] -name = "ark-crypto-primitives" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" -dependencies = [ - "ahash", - "ark-crypto-primitives-macros", - "ark-ec", - "ark-ff", - "ark-relations", - "ark-serialize", - "ark-snark", - "ark-std", - "blake2", - "derivative", - "digest", - "fnv", - "merlin", - "sha2", -] - -[[package]] -name = "ark-crypto-primitives-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "ark-ec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" -dependencies = [ - "ahash", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "educe", - "fnv", - "hashbrown 0.15.5", - "itertools", - "num-bigint", - "num-integer", - "num-traits", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" -dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", - "arrayvec", - "digest", - "educe", - "itertools", - "num-bigint", - "num-traits", - "paste", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "ark-ff-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "ark-groth16" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" -dependencies = [ - "ark-crypto-primitives", - "ark-ec", - "ark-ff", - "ark-poly", - "ark-relations", - "ark-serialize", - "ark-std", -] - -[[package]] -name = "ark-poly" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" -dependencies = [ - "ahash", - "ark-ff", - "ark-serialize", - "ark-std", - "educe", - "fnv", - "hashbrown 0.15.5", -] - -[[package]] -name = "ark-r1cs-std" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" -dependencies = [ - "ark-ec", - "ark-ff", - "ark-relations", - "ark-std", - "educe", - "num-bigint", - "num-integer", - "num-traits", - "tracing", -] - -[[package]] -name = "ark-relations" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" -dependencies = [ - "ark-ff", - "ark-std", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "ark-serialize" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" -dependencies = [ - "ark-serialize-derive", - "ark-std", - "arrayvec", - "digest", - "num-bigint", -] - -[[package]] -name = "ark-serialize-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "ark-snark" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" -dependencies = [ - "ark-ff", - "ark-relations", - "ark-serialize", - "ark-std", -] - -[[package]] -name = "ark-std" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "arraydeque" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bonsai-sdk" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21055e2f49cbbdbfe9f8f96d597c5527b0c6ab7933341fdc2f147180e48a988e" -dependencies = [ - "duplicate", - "maybe-async", - "reqwest", - "serde", - "thiserror", -] - -[[package]] -name = "borsh" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "bytemuck" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -dependencies = [ - "serde", -] - -[[package]] -name = "camino" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0b03af37dad7a14518b7691d81acb0f8222604ad3d1b02f6b4bed5188c0cd5" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cc" -version = "1.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.0", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.106", -] - -[[package]] -name = "derive_more" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.0", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "docker-generate" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf673e0848ef09fa4aeeba78e681cf651c0c7d35f76ee38cec8e55bc32fa111" - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "duplicate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1" -dependencies = [ - "heck", - "proc-macro2", - "proc-macro2-diagnostics", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "educe" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "elf" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-ordinalize" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.0", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "find-msvc-tools" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "foldhash", -] - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" - -[[package]] -name = "icu_properties" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "potential_utf", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" - -[[package]] -name = "icu_provider" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" -dependencies = [ - "displaydoc", - "icu_locale_core", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "include_bytes_aligned" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee796ad498c8d9a1d68e477df8f754ed784ef875de1414ebdaf169f70a6a784" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" -dependencies = [ - "equivalent", - "hashbrown 0.15.5", - "serde", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "io-uring" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" -dependencies = [ - "bitflags 2.9.3", - "cfg-if", - "libc", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6247da8b8658ad4e73a186e747fcc5fc2a29f979d6fe6269127fdb5fd08298d0" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lazy-regex" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" -dependencies = [ - "lazy-regex-proc_macros", - "once_cell", - "regex", -] - -[[package]] -name = "lazy-regex-proc_macros" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 2.0.106", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "libc" -version = "0.2.175" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" - -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags 2.9.3", - "libc", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "memchr" -version = "2.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" - -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.6.4", - "zeroize", -] - -[[package]] -name = "metal" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" -dependencies = [ - "bitflags 2.9.3", - "block", - "core-graphics-types", - "foreign-types", - "log", - "objc", - "paste", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", -] - -[[package]] -name = "no_std_strings" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e" - -[[package]] -name = "nssa-core" -version = "0.1.0" -dependencies = [ - "borsh", - "chacha20", - "risc0-zkvm", - "serde", - "thiserror", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_enum" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "serde", -] - -[[package]] -name = "potential_utf" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "version_check", - "yansi", -] - -[[package]] -name = "programs" -version = "0.1.0" -dependencies = [ - "nssa-core", - "risc0-zkvm", - "serde", -] - -[[package]] -name = "proptest" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" -dependencies = [ - "bitflags 2.9.3", - "num-traits", - "rand 0.9.2", - "rand_chacha 0.9.0", - "rand_xorshift", - "unarray", -] - -[[package]] -name = "prost" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" -dependencies = [ - "bytes", - "getrandom 0.3.3", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", -] - -[[package]] -name = "rand_xorshift" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" -dependencies = [ - "rand_core 0.9.3", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror", -] - -[[package]] -name = "ref-cast" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "regex" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" - -[[package]] -name = "reqwest" -version = "0.12.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.16", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "risc0-binfmt" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c8f97f81bcdead4101bca06469ecef481a2695cd04e7e877b49dea56a7f6f2a" -dependencies = [ - "anyhow", - "borsh", - "bytemuck", - "derive_more", - "elf", - "lazy_static", - "postcard", - "rand 0.9.2", - "risc0-zkp", - "risc0-zkvm-platform", - "ruint", - "semver", - "serde", - "tracing", -] - -[[package]] -name = "risc0-build" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bbb512d728e011d03ce0958ca7954624ee13a215bcafd859623b3c63b2a3f60" -dependencies = [ - "anyhow", - "cargo_metadata", - "derive_builder", - "dirs", - "docker-generate", - "hex", - "risc0-binfmt", - "risc0-zkos-v1compat", - "risc0-zkp", - "risc0-zkvm-platform", - "rzup", - "semver", - "serde", - "serde_json", - "stability", - "tempfile", -] - -[[package]] -name = "risc0-circuit-keccak" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f195f865ac1afdc21a172d7756fdcc21be18e13eb01d78d3d7f2b128fa881ba" -dependencies = [ - "anyhow", - "bytemuck", - "paste", - "risc0-binfmt", - "risc0-circuit-recursion", - "risc0-core", - "risc0-zkp", - "tracing", -] - -[[package]] -name = "risc0-circuit-recursion" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca8f15c8abc0fd8c097aa7459879110334d191c63dd51d4c28881c4a497279e" -dependencies = [ - "anyhow", - "bytemuck", - "hex", - "metal", - "risc0-core", - "risc0-zkp", - "tracing", -] - -[[package]] -name = "risc0-circuit-rv32im" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1b0689f4a270a2f247b04397ebb431b8f64fe5170e98ee4f9d71bd04825205" -dependencies = [ - "anyhow", - "bit-vec", - "bytemuck", - "derive_more", - "paste", - "risc0-binfmt", - "risc0-core", - "risc0-zkp", - "serde", - "tracing", -] - -[[package]] -name = "risc0-core" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f2723fedace48c6c5a505bd8f97ac4e1712bc4cb769083e10536d862b66987" -dependencies = [ - "bytemuck", - "rand_core 0.9.3", -] - -[[package]] -name = "risc0-groth16" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724285dc79604abfb2d40feaefe3e335420a6b293511661f77d6af62f1f5fae9" -dependencies = [ - "anyhow", - "ark-bn254", - "ark-ec", - "ark-ff", - "ark-groth16", - "ark-serialize", - "bytemuck", - "hex", - "num-bigint", - "num-traits", - "risc0-binfmt", - "risc0-zkp", - "serde", -] - -[[package]] -name = "risc0-zkos-v1compat" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840c2228803557a8b7dc035a8f196516b6fd68c9dc6ac092f0c86241b5b1bafb" -dependencies = [ - "include_bytes_aligned", - "no_std_strings", - "risc0-zkvm-platform", -] - -[[package]] -name = "risc0-zkp" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb6bf356f469bb8744f72a07a37134c5812c1d55d6271bba80e87bdb7a58c8e" -dependencies = [ - "anyhow", - "blake2", - "borsh", - "bytemuck", - "cfg-if", - "digest", - "hex", - "hex-literal", - "metal", - "paste", - "rand_core 0.9.3", - "risc0-core", - "risc0-zkvm-platform", - "serde", - "sha2", - "stability", - "tracing", -] - -[[package]] -name = "risc0-zkvm" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fcce11648a9ff60b8e7af2f0ce7fbf8d25275ab6d414cc91b9da69ee75bc978" -dependencies = [ - "anyhow", - "bincode", - "bonsai-sdk", - "borsh", - "bytemuck", - "bytes", - "derive_more", - "hex", - "lazy-regex", - "prost", - "risc0-binfmt", - "risc0-build", - "risc0-circuit-keccak", - "risc0-circuit-recursion", - "risc0-circuit-rv32im", - "risc0-core", - "risc0-groth16", - "risc0-zkos-v1compat", - "risc0-zkp", - "risc0-zkvm-platform", - "rrs-lib", - "rzup", - "semver", - "serde", - "sha2", - "stability", - "tempfile", - "tracing", -] - -[[package]] -name = "risc0-zkvm-platform" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c826f09626ab2ae76671673e5a232548ddd95a34eece2ea4ced5f010383f95b" -dependencies = [ - "bytemuck", - "cfg-if", - "getrandom 0.2.16", - "getrandom 0.3.3", - "libm", - "num_enum", - "paste", - "stability", -] - -[[package]] -name = "rrs-lib" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4382d3af3a4ebdae7f64ba6edd9114fff92c89808004c4943b393377a25d001" -dependencies = [ - "downcast-rs", - "paste", -] - -[[package]] -name = "rsa" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "ruint" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" -dependencies = [ - "borsh", - "proptest", - "rand 0.8.5", - "rand 0.9.2", - "ruint-macro", - "serde", - "valuable", - "zeroize", -] - -[[package]] -name = "ruint-macro" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" - -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags 2.9.3", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.0", -] - -[[package]] -name = "rustls" -version = "0.23.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "rzup" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2aed296f203fa64bcb4b52069356dd86d6ec578593985b919b6995bee1f0ae" -dependencies = [ - "hex", - "rsa", - "semver", - "serde", - "serde_with", - "sha2", - "strum", - "tempfile", - "thiserror", - "toml", - "yaml-rust2", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "semver" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "serde_json" -version = "1.0.143" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.11.0", - "schemars 0.9.0", - "schemars 1.0.4", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stability" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn 2.0.106", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "tempfile" -version = "3.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys 0.61.0", -] - -[[package]] -name = "thiserror" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "time" -version = "0.3.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" - -[[package]] -name = "time-macros" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.47.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "pin-project-lite", - "slab", - "socket2", - "windows-sys 0.59.0", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap 2.11.0", - "serde", - "serde_spanned", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" -dependencies = [ - "bitflags 2.9.3", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "tracing-core", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad224d2776649cfb4f4471124f8176e54c1cca67a88108e30a0cd98b90e7ad3" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1364104bdcd3c03f22b16a3b1c9620891469f5e9f09bc38b2db121e593e732" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0a08ecf5d99d5604a6666a70b3cde6ab7cc6142f5e641a8ef48fc744ce8854" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d7ab4ca3e367bb1ed84ddbd83cc6e41e115f8337ed047239578210214e36c76" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a518014843a19e2dbbd0ed5dfb6b99b23fb886b14e6192a00803a3e14c552b0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "255eb0aa4cc2eea3662a00c2bbd66e93911b7361d5e0fcd62385acfd7e15dcee" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50462a022f46851b81d5441d1a6f5bac0b21a1d72d64bd4906fbdd4bf7230ec7" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-core" -version = "0.62.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.0", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-interface" -version = "0.59.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" - -[[package]] -name = "windows-result" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-strings" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.3", -] - -[[package]] -name = "windows-sys" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" -dependencies = [ - "windows-link 0.2.0", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" -dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.3", -] - -[[package]] -name = "writeable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" - -[[package]] -name = "yaml-rust2" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" -dependencies = [ - "arraydeque", - "encoding_rs", - "hashlink", -] - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yoke" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "zerotrie" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] diff --git a/nssa/test_program_methods/guest/Cargo.toml b/nssa/test_program_methods/guest/Cargo.toml deleted file mode 100644 index 9e5f543f..00000000 --- a/nssa/test_program_methods/guest/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "programs" -version = "0.1.0" -edition = "2024" - -[workspace] - -[dependencies] -risc0-zkvm = { version = "3.0.3", features = ['std'] } -nssa-core = { path = "../../core" } -serde = { version = "1.0.219", default-features = false } diff --git a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs b/nssa/test_program_methods/guest/src/bin/nonce_changer.rs deleted file mode 100644 index 4ca6c734..00000000 --- a/nssa/test_program_methods/guest/src/bin/nonce_changer.rs +++ /dev/null @@ -1,18 +0,0 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput}; - -type Instruction = (); - -fn main() { - let (ProgramInput { pre_states, .. } , instruction_words) = read_nssa_inputs::(); - - let [pre] = match pre_states.try_into() { - Ok(array) => array, - Err(_) => return, - }; - - let account_pre = &pre.account; - let mut account_post = account_pre.clone(); - account_post.nonce += 1; - - write_nssa_outputs(instruction_words ,vec![pre], vec![AccountPostState::new(account_post)]); -} diff --git a/nssa/program_methods/Cargo.toml b/program_methods/Cargo.toml similarity index 64% rename from nssa/program_methods/Cargo.toml rename to program_methods/Cargo.toml index 40dab21b..5f0688a4 100644 --- a/nssa/program_methods/Cargo.toml +++ b/program_methods/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "program-methods" +name = "program_methods" version = "0.1.0" edition = "2024" [build-dependencies] -risc0-build = { version = "3.0.3" } +risc0-build.workspace = true [package.metadata.risc0] methods = ["guest"] diff --git a/nssa/program_methods/build.rs b/program_methods/build.rs similarity index 100% rename from nssa/program_methods/build.rs rename to program_methods/build.rs diff --git a/program_methods/guest/Cargo.toml b/program_methods/guest/Cargo.toml new file mode 100644 index 00000000..37c1a8d9 --- /dev/null +++ b/program_methods/guest/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "programs" +version = "0.1.0" +edition = "2024" + +[dependencies] +nssa_core.workspace = true + +risc0-zkvm.workspace = true +serde = { workspace = true, default-features = false } diff --git a/program_methods/guest/src/bin/amm.rs b/program_methods/guest/src/bin/amm.rs new file mode 100644 index 00000000..9488db13 --- /dev/null +++ b/program_methods/guest/src/bin/amm.rs @@ -0,0 +1,3587 @@ +use nssa_core::{ + account::{Account, AccountId, AccountWithMetadata, Data}, + program::{ + AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, + }, +}; + +// The AMM program has five functions (four directly accessible via instructions): +// 1. New AMM definition. Arguments to this function are: +// * Seven accounts: [amm_pool, vault_holding_a, vault_holding_b, pool_lp, user_holding_a, +// user_holding_b, user_holding_lp]. For new AMM Pool: amm_pool, vault_holding_a, +// vault_holding_b, pool_lp and user_holding_lp are default accounts. amm_pool is a default +// account that will initiate the amm definition account values vault_holding_a is a token +// holding account for token a vault_holding_b is a token holding account for token b pool_lp +// is a token holding account for the pool's lp token user_holding_a is a token holding +// account for token a user_holding_b is a token holding account for token b user_holding_lp +// is a token holding account for lp token +// * PDA remark: Accounts amm_pool, vault_holding_a, vault_holding_b and pool_lp are PDA. The +// AccountId for these accounts must be computed using: amm_pool AccountId <- +// compute_pool_pda vault_holding_a, vault_holding_b <- compute_vault_pda pool_lp +// <-compute_liquidity_token_pda +// * Requires authorization: user_holding_a, user_holding_b +// * An instruction data of 65-bytes, indicating the initial amm reserves' balances and +// token_program_id with the following layout: [0x00 || array of balances (little-endian 16 +// bytes) || AMM_PROGRAM_ID)] +// * Internally, calls compute_liquidity_token_pda_seed, compute_vault_pda_seed to authorize +// transfers. +// * Internally, calls compute_pool_da, compute_vault_pda and compute_vault_pda to check +// various AccountIds are correct. +// 2. Swap assets Arguments to this function are: +// * Five accounts: [amm_pool, vault_holding_a, vault_holding_b, user_holding_a, +// user_holding_b]. +// * Requires authorization: user holding account associated to TOKEN_DEFINITION_ID (either +// user_holding_a or user_holding_b) +// * An instruction data byte string of length 65, indicating which token type to swap, +// quantity of tokens put into the swap (of type TOKEN_DEFINITION_ID) and min_amount_out. +// [0x01 || amount (little-endian 16 bytes) || TOKEN_DEFINITION_ID]. +// * Internally, calls swap logic. +// * Four accounts: [user_deposit, vault_deposit, vault_withdraw, user_withdraw]. +// user_deposit and vault_deposit define deposit transaction. vault_withdraw and +// user_withdraw define withdraw transaction. +// * deposit_amount is the amount for user_deposit -> vault_deposit transfer. +// * reserve_amounts is the pool's reserves; used to compute the withdraw amount. +// * Outputs the token transfers as a Vec and the withdraw amount. +// 3. Add liquidity Arguments to this function are: +// * Seven accounts: [amm_pool, vault_holding_a, vault_holding_b, pool_lp, user_holding_a, +// user_holding_a, user_holding_lp]. +// * Requires authorization: user_holding_a, user_holding_b +// * An instruction data byte string of length 49, amounts for minimum amount of liquidity from +// add (min_amount_lp), +// * max amount added for each token (max_amount_a and max_amount_b); indicate [0x02 || array +// of of balances (little-endian 16 bytes)]. +// * Internally, calls compute_liquidity_token_pda_seed to compute liquidity pool PDA seed. +// 4. Remove liquidity +// * Seven accounts: [amm_pool, vault_holding_a, vault_holding_b, pool_lp, user_holding_a, +// user_holding_a, user_holding_lp]. +// * Requires authorization: user_holding_lp +// * An instruction data byte string of length 49, amounts for minimum amount of liquidity to +// redeem (balance_lp), +// * minimum balance of each token to remove (min_amount_a and min_amount_b); indicate [0x03 || +// array of balances (little-endian 16 bytes)]. +// * Internally, calls compute_vault_pda_seed to compute vault_a and vault_b's PDA seed. + +const POOL_DEFINITION_DATA_SIZE: usize = 225; + +#[derive(Clone, Default)] +struct PoolDefinition { + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, + vault_a_id: AccountId, + vault_b_id: AccountId, + liquidity_pool_id: AccountId, + liquidity_pool_supply: u128, + reserve_a: u128, + reserve_b: u128, + /// Fees are currently not used + fees: u128, + /// A pool becomes inactive (active = false) + /// once all of its liquidity has been removed (e.g., reserves are emptied and + /// liquidity_pool_supply = 0) + active: bool, +} + +impl PoolDefinition { + fn into_data(self) -> Data { + let mut bytes = [0; POOL_DEFINITION_DATA_SIZE]; + bytes[0..32].copy_from_slice(&self.definition_token_a_id.to_bytes()); + bytes[32..64].copy_from_slice(&self.definition_token_b_id.to_bytes()); + bytes[64..96].copy_from_slice(&self.vault_a_id.to_bytes()); + bytes[96..128].copy_from_slice(&self.vault_b_id.to_bytes()); + bytes[128..160].copy_from_slice(&self.liquidity_pool_id.to_bytes()); + bytes[160..176].copy_from_slice(&self.liquidity_pool_supply.to_le_bytes()); + bytes[176..192].copy_from_slice(&self.reserve_a.to_le_bytes()); + bytes[192..208].copy_from_slice(&self.reserve_b.to_le_bytes()); + bytes[208..224].copy_from_slice(&self.fees.to_le_bytes()); + bytes[224] = self.active as u8; + + bytes + .to_vec() + .try_into() + .expect("225 bytes should fit into Data") + } + + fn parse(data: &[u8]) -> Option { + if data.len() != POOL_DEFINITION_DATA_SIZE { + None + } else { + let definition_token_a_id = AccountId::new(data[0..32].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token A definition")); + let definition_token_b_id = AccountId::new(data[32..64].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Vault B definition")); + let vault_a_id = AccountId::new(data[64..96].try_into().expect( + "Parse data: The AMM program must be provided a valid AccountId for Vault A", + )); + let vault_b_id = AccountId::new(data[96..128].try_into().expect( + "Parse data: The AMM program must be provided a valid AccountId for Vault B", + )); + let liquidity_pool_id = AccountId::new(data[128..160].try_into().expect("Parse data: The AMM program must be provided a valid AccountId for Token liquidity pool definition")); + let liquidity_pool_supply = u128::from_le_bytes(data[160..176].try_into().expect( + "Parse data: The AMM program must be provided a valid u128 for liquidity cap", + )); + let reserve_a = u128::from_le_bytes(data[176..192].try_into().expect( + "Parse data: The AMM program must be provided a valid u128 for reserve A balance", + )); + let reserve_b = u128::from_le_bytes(data[192..208].try_into().expect( + "Parse data: The AMM program must be provided a valid u128 for reserve B balance", + )); + let fees = u128::from_le_bytes( + data[208..224] + .try_into() + .expect("Parse data: The AMM program must be provided a valid u128 for fees"), + ); + + let active = match data[224] { + 0 => false, + 1 => true, + _ => panic!("Parse data: The AMM program must be provided a valid bool for active"), + }; + + Some(Self { + definition_token_a_id, + definition_token_b_id, + vault_a_id, + vault_b_id, + liquidity_pool_id, + liquidity_pool_supply, + reserve_a, + reserve_b, + fees, + active, + }) + } + } +} + +// TODO: remove repeated code for Token_Definition and TokenHoldling + +const TOKEN_HOLDING_TYPE: u8 = 1; +const TOKEN_HOLDING_DATA_SIZE: usize = 49; + +struct TokenHolding { + #[cfg_attr(not(test), expect(dead_code, reason = "TODO: fix later"))] + account_type: u8, + definition_id: AccountId, + balance: u128, +} + +impl TokenHolding { + fn parse(data: &[u8]) -> Option { + if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { + None + } else { + let account_type = data[0]; + let definition_id = AccountId::new( + data[1..33] + .try_into() + .expect("Defintion ID must be 32 bytes long"), + ); + let balance = u128::from_le_bytes( + data[33..] + .try_into() + .expect("balance must be 16 bytes little-endian"), + ); + Some(Self { + definition_id, + balance, + account_type, + }) + } + } + + #[cfg(test)] + fn into_data(self) -> Data { + let mut bytes = [0; TOKEN_HOLDING_DATA_SIZE]; + bytes[0] = self.account_type; + bytes[1..33].copy_from_slice(&self.definition_id.to_bytes()); + bytes[33..].copy_from_slice(&self.balance.to_le_bytes()); + + bytes + .to_vec() + .try_into() + .expect("49 bytes should fit into Data") + } +} + +type Instruction = Vec; +fn main() { + let ( + ProgramInput { + pre_states, + instruction, + }, + instruction_words, + ) = read_nssa_inputs::(); + + let (post_states, chained_calls) = + match instruction[0] { + 0 => { + let balance_a: u128 = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("New definition: AMM Program expects u128 for balance a"), + ); + let balance_b: u128 = u128::from_le_bytes( + instruction[17..33] + .try_into() + .expect("New definition: AMM Program expects u128 for balance b"), + ); + + // Convert Vec to ProgramId ([u32;8]) + let mut amm_program_id: [u32; 8] = [0; 8]; + amm_program_id[0] = u32::from_le_bytes( + instruction[33..37] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + amm_program_id[1] = u32::from_le_bytes( + instruction[37..41] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + amm_program_id[2] = u32::from_le_bytes( + instruction[41..45] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + amm_program_id[3] = u32::from_le_bytes( + instruction[45..49] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + amm_program_id[4] = u32::from_le_bytes( + instruction[49..53] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + amm_program_id[5] = u32::from_le_bytes( + instruction[53..57] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + amm_program_id[6] = u32::from_le_bytes( + instruction[57..61] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + amm_program_id[7] = u32::from_le_bytes( + instruction[61..65] + .try_into() + .expect("New definition: AMM Program expects valid u32"), + ); + + new_definition(&pre_states, &[balance_a, balance_b], amm_program_id) + } + 1 => { + let mut token_in_id: [u8; 32] = [0; 32]; + token_in_id[0..].copy_from_slice(&instruction[33..65]); + let token_in_id = AccountId::new(token_in_id); + + let amount_in = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Swap: AMM Program expects valid u128 for balance to move"), + ); + let min_amount_out = u128::from_le_bytes( + instruction[17..33] + .try_into() + .expect("Swap: AMM Program expects valid u128 for balance to move"), + ); + + swap(&pre_states, &[amount_in, min_amount_out], token_in_id) + } + 2 => { + let min_amount_lp = u128::from_le_bytes(instruction[1..17].try_into().expect( + "Add liquidity: AMM Program expects valid u128 for min amount liquidity", + )); + let max_amount_a = u128::from_le_bytes( + instruction[17..33] + .try_into() + .expect("Add liquidity: AMM Program expects valid u128 for max amount a"), + ); + let max_amount_b = u128::from_le_bytes( + instruction[33..49] + .try_into() + .expect("Add liquidity: AMM Program expects valid u128 for max amount b"), + ); + + add_liquidity(&pre_states, &[min_amount_lp, max_amount_a, max_amount_b]) + } + 3 => { + let balance_lp = u128::from_le_bytes(instruction[1..17].try_into().expect( + "Remove liquidity: AMM Program expects valid u128 for balance liquidity", + )); + let min_amount_a = u128::from_le_bytes( + instruction[17..33] + .try_into() + .expect("Remove liquidity: AMM Program expects valid u128 for balance a"), + ); + let min_amount_b = u128::from_le_bytes( + instruction[33..49] + .try_into() + .expect("Remove liquidity: AMM Program expects valid u128 for balance b"), + ); + + remove_liquidity(&pre_states, &[balance_lp, min_amount_a, min_amount_b]) + } + _ => panic!("Invalid instruction"), + }; + + write_nssa_outputs_with_chained_call(instruction_words, pre_states, post_states, chained_calls); +} + +fn compute_pool_pda( + amm_program_id: ProgramId, + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, +) -> AccountId { + AccountId::from(( + &amm_program_id, + &compute_pool_pda_seed(definition_token_a_id, definition_token_b_id), + )) +} + +fn compute_pool_pda_seed( + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, +) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let (token_1, token_2) = match definition_token_a_id + .value() + .cmp(definition_token_b_id.value()) + { + std::cmp::Ordering::Less => (definition_token_b_id, definition_token_a_id), + std::cmp::Ordering::Greater => (definition_token_a_id, definition_token_b_id), + std::cmp::Ordering::Equal => panic!("Definitions match"), + }; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&token_1.to_bytes()); + bytes[32..].copy_from_slice(&token_2.to_bytes()); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) +} + +fn compute_vault_pda( + amm_program_id: ProgramId, + pool_id: AccountId, + definition_token_id: AccountId, +) -> AccountId { + AccountId::from(( + &amm_program_id, + &compute_vault_pda_seed(pool_id, definition_token_id), + )) +} + +fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&pool_id.to_bytes()); + bytes[32..].copy_from_slice(&definition_token_id.to_bytes()); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) +} + +fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId { + AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id))) +} + +fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&pool_id.to_bytes()); + bytes[32..].copy_from_slice(&[0; 32]); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) +} + +const TOKEN_PROGRAM_NEW: u8 = 0; +const TOKEN_PROGRAM_TRANSFER: u8 = 1; +const TOKEN_PROGRAM_MINT: u8 = 4; +const TOKEN_PROGRAM_BURN: u8 = 3; + +fn initialize_token_transfer_chained_call( + token_program_command: u8, + sender: AccountWithMetadata, + recipient: AccountWithMetadata, + amount_to_move: u128, + pda_seed: Vec, +) -> ChainedCall { + let mut instruction_data = vec![0u8; 23]; + instruction_data[0] = token_program_command; + instruction_data[1..17].copy_from_slice(&amount_to_move.to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) + .expect("AMM Program expects valid token transfer instruction data"); + + ChainedCall { + program_id: sender.account.program_owner, + instruction_data, + pre_states: vec![sender, recipient], + pda_seeds: pda_seed, + } +} + +fn new_definition( + pre_states: &[AccountWithMetadata], + balance_in: &[u128], + amm_program_id: ProgramId, +) -> (Vec, Vec) { + // Pool accounts: pool itself, and its 2 vaults and LP token + // 2 accounts for funding tokens + // initial funder's LP account + if pre_states.len() != 7 { + panic!("Invalid number of input accounts") + } + + if balance_in.len() != 2 { + panic!("Invalid number of input balances") + } + + let pool = &pre_states[0]; + let vault_a = &pre_states[1]; + let vault_b = &pre_states[2]; + let pool_lp = &pre_states[3]; + let user_holding_a = &pre_states[4]; + let user_holding_b = &pre_states[5]; + let user_holding_lp = &pre_states[6]; + + let amount_a = balance_in[0]; + let amount_b = balance_in[1]; + + // Prevents pool constant coefficient (k) from being 0. + if amount_a == 0 || amount_b == 0 { + panic!("Balances must be nonzero") + } + + // Verify token_a and token_b are different + let definition_token_a_id = TokenHolding::parse(&user_holding_a.account.data) + .expect("New definition: AMM Program expects valid Token Holding account for Token A") + .definition_id; + let definition_token_b_id = TokenHolding::parse(&user_holding_b.account.data) + .expect("New definition: AMM Program expects valid Token Holding account for Token B") + .definition_id; + + // both instances of the same token program + let token_program = user_holding_a.account.program_owner; + + if user_holding_b.account.program_owner != token_program { + panic!("User Token holdings must use the same Token Program"); + } + + if definition_token_a_id == definition_token_b_id { + panic!("Cannot set up a swap for a token with itself") + } + + if pool.account_id + != compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id) + { + panic!("Pool Definition Account ID does not match PDA"); + } + + if vault_a.account_id + != compute_vault_pda(amm_program_id, pool.account_id, definition_token_a_id) + || vault_b.account_id + != compute_vault_pda(amm_program_id, pool.account_id, definition_token_b_id) + { + panic!("Vault ID does not match PDA"); + } + + if pool_lp.account_id != compute_liquidity_token_pda(amm_program_id, pool.account_id) { + panic!("Liquidity pool Token Definition Account ID does not match PDA"); + } + + // Verify that Pool Account is not active + let pool_account_data = if pool.account == Account::default() { + PoolDefinition::default() + } else { + PoolDefinition::parse(&pool.account.data).expect("AMM program expects a valid Pool account") + }; + + if pool_account_data.active { + panic!("Cannot initialize an active Pool Definition") + } + + // LP Token minting calculation + // We assume LP is based on the initial deposit amount for Token_A. + + // Update pool account + let mut pool_post = pool.account.clone(); + let pool_post_definition = PoolDefinition { + definition_token_a_id, + definition_token_b_id, + vault_a_id: vault_a.account_id, + vault_b_id: vault_b.account_id, + liquidity_pool_id: pool_lp.account_id, + liquidity_pool_supply: amount_a, + reserve_a: amount_a, + reserve_b: amount_b, + fees: 0u128, // TODO: we assume all fees are 0 for now. + active: true, + }; + + pool_post.data = pool_post_definition.into_data(); + let pool_post: AccountPostState = if pool.account == Account::default() { + AccountPostState::new_claimed(pool_post.clone()) + } else { + AccountPostState::new(pool_post.clone()) + }; + + let mut chained_calls = Vec::::new(); + + // Chain call for Token A (user_holding_a -> Vault_A) + let call_token_a = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + user_holding_a.clone(), + vault_a.clone(), + amount_a, + Vec::::new(), + ); + // Chain call for Token B (user_holding_b -> Vault_B) + let call_token_b = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + user_holding_b.clone(), + vault_b.clone(), + amount_b, + Vec::::new(), + ); + + // Chain call for liquidity token (TokenLP definition -> User LP Holding) + let mut instruction_data = vec![0u8; 23]; + instruction_data[0] = if pool.account == Account::default() { + TOKEN_PROGRAM_NEW + } else { + TOKEN_PROGRAM_MINT + }; //new or mint + let nme = if pool.account == Account::default() { + [1u8; 6] + } else { + [0u8; 6] + }; + + instruction_data[1..17].copy_from_slice(&amount_a.to_le_bytes()); + instruction_data[17..].copy_from_slice(&nme); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) + .expect("New definition: AMM Program expects valid instruction_data"); + + let mut pool_lp_auth = pool_lp.clone(); + pool_lp_auth.is_authorized = true; + + let token_program_id = user_holding_a.account.program_owner; + let call_token_lp = ChainedCall { + program_id: token_program_id, + instruction_data, + pre_states: vec![pool_lp_auth.clone(), user_holding_lp.clone()], + pda_seeds: vec![compute_liquidity_token_pda_seed(pool.account_id)], + }; + + chained_calls.push(call_token_lp); + chained_calls.push(call_token_b); + chained_calls.push(call_token_a); + + let post_states = vec![ + pool_post.clone(), + AccountPostState::new(pre_states[1].account.clone()), + AccountPostState::new(pre_states[2].account.clone()), + AccountPostState::new(pre_states[3].account.clone()), + AccountPostState::new(pre_states[4].account.clone()), + AccountPostState::new(pre_states[5].account.clone()), + AccountPostState::new(pre_states[6].account.clone()), + ]; + + (post_states.clone(), chained_calls) +} + +fn swap( + pre_states: &[AccountWithMetadata], + amounts: &[u128], + token_in_id: AccountId, +) -> (Vec, Vec) { + if pre_states.len() != 5 { + panic!("Invalid number of input accounts"); + } + + if amounts.len() != 2 { + panic!("Invalid number of amounts provided"); + } + + let pool = &pre_states[0]; + let vault_a = &pre_states[1]; + let vault_b = &pre_states[2]; + let user_holding_a = &pre_states[3]; + let user_holding_b = &pre_states[4]; + + // Verify vaults are in fact vaults + let pool_def_data = PoolDefinition::parse(&pool.account.data) + .expect("Swap: AMM Program expects a valid Pool Definition Account"); + + if !pool_def_data.active { + panic!("Pool is inactive"); + } + + if vault_a.account_id != pool_def_data.vault_a_id { + panic!("Vault A was not provided"); + } + + if vault_b.account_id != pool_def_data.vault_b_id { + panic!("Vault B was not provided"); + } + + // fetch pool reserves + // validates reserves is at least the vaults' balances + if TokenHolding::parse(&vault_a.account.data) + .expect("Swap: AMM Program expects a valid Token Holding Account for Vault A") + .balance + < pool_def_data.reserve_a + { + panic!("Reserve for Token A exceeds vault balance"); + } + if TokenHolding::parse(&vault_b.account.data) + .expect("Swap: AMM Program expects a valid Token Holding Account for Vault B") + .balance + < pool_def_data.reserve_b + { + panic!("Reserve for Token B exceeds vault balance"); + } + + let (chained_calls, [deposit_a, withdraw_a], [deposit_b, withdraw_b]) = + if token_in_id == pool_def_data.definition_token_a_id { + let (chained_calls, deposit_a, withdraw_b) = swap_logic( + user_holding_a.clone(), + vault_a.clone(), + vault_b.clone(), + user_holding_b.clone(), + amounts[0], + amounts[1], + &[pool_def_data.reserve_a, pool_def_data.reserve_b], + pool.account_id, + ); + + (chained_calls, [deposit_a, 0], [0, withdraw_b]) + } else if token_in_id == pool_def_data.definition_token_b_id { + let (chained_calls, deposit_b, withdraw_a) = swap_logic( + user_holding_b.clone(), + vault_b.clone(), + vault_a.clone(), + user_holding_a.clone(), + amounts[0], + amounts[1], + &[pool_def_data.reserve_b, pool_def_data.reserve_a], + pool.account_id, + ); + + (chained_calls, [0, withdraw_a], [deposit_b, 0]) + } else { + panic!("AccountId is not a token type for the pool"); + }; + + // Update pool account + let mut pool_post = pool.account.clone(); + let pool_post_definition = PoolDefinition { + reserve_a: pool_def_data.reserve_a + deposit_a - withdraw_a, + reserve_b: pool_def_data.reserve_b + deposit_b - withdraw_b, + ..pool_def_data + }; + + pool_post.data = pool_post_definition.into_data(); + + let post_states = vec![ + AccountPostState::new(pool_post.clone()), + AccountPostState::new(pre_states[1].account.clone()), + AccountPostState::new(pre_states[2].account.clone()), + AccountPostState::new(pre_states[3].account.clone()), + AccountPostState::new(pre_states[4].account.clone()), + ]; + + (post_states, chained_calls) +} + +#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")] +fn swap_logic( + user_deposit: AccountWithMetadata, + vault_deposit: AccountWithMetadata, + vault_withdraw: AccountWithMetadata, + user_withdraw: AccountWithMetadata, + deposit_amount: u128, + min_amount_out: u128, + reserve_amounts: &[u128], + pool_id: AccountId, +) -> (Vec, u128, u128) { + let reserve_deposit_vault_amount = reserve_amounts[0]; + let reserve_withdraw_vault_amount = reserve_amounts[1]; + + // Compute withdraw amount + // Maintains pool constant product + // k = pool_def_data.reserve_a * pool_def_data.reserve_b; + let withdraw_amount = (reserve_withdraw_vault_amount * deposit_amount) + / (reserve_deposit_vault_amount + deposit_amount); + + // Slippage check + if min_amount_out > withdraw_amount { + panic!("Withdraw amount is less than minimal amount out"); + } + + if withdraw_amount == 0 { + panic!("Withdraw amount should be nonzero"); + } + + let mut chained_calls = Vec::new(); + chained_calls.push(initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + user_deposit.clone(), + vault_deposit.clone(), + deposit_amount, + Vec::::new(), + )); + + let mut vault_withdraw = vault_withdraw.clone(); + vault_withdraw.is_authorized = true; + + chained_calls.push(initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + vault_withdraw.clone(), + user_withdraw.clone(), + withdraw_amount, + vec![compute_vault_pda_seed( + pool_id, + TokenHolding::parse(&vault_withdraw.account.data) + .expect("Swap Logic: AMM Program expects valid token data") + .definition_id, + )], + )); + + (chained_calls, deposit_amount, withdraw_amount) +} + +fn add_liquidity( + pre_states: &[AccountWithMetadata], + balances: &[u128], +) -> (Vec, Vec) { + if pre_states.len() != 7 { + panic!("Invalid number of input accounts"); + } + + let pool = &pre_states[0]; + let vault_a = &pre_states[1]; + let vault_b = &pre_states[2]; + let pool_definition_lp = &pre_states[3]; + let user_holding_a = &pre_states[4]; + let user_holding_b = &pre_states[5]; + let user_holding_lp = &pre_states[6]; + + // 1. Fetch Pool state + let pool_def_data = PoolDefinition::parse(&pool.account.data) + .expect("Add liquidity: AMM Program expects valid Pool Definition Account"); + if vault_a.account_id != pool_def_data.vault_a_id { + panic!("Vault A was not provided"); + } + + if pool_def_data.liquidity_pool_id != pool_definition_lp.account_id { + panic!("LP definition mismatch"); + } + + if vault_b.account_id != pool_def_data.vault_b_id { + panic!("Vault B was not provided"); + } + if balances.len() != 3 { + panic!("Invalid number of input balances"); + } + + let min_amount_lp = balances[0]; + let max_amount_a = balances[1]; + let max_amount_b = balances[2]; + + if max_amount_a == 0 || max_amount_b == 0 { + panic!("Both max-balances must be nonzero"); + } + + if min_amount_lp == 0 { + panic!("Min-lp must be nonzero"); + } + + // 2. Determine deposit amount + let vault_b_balance = TokenHolding::parse(&vault_b.account.data) + .expect("Add liquidity: AMM Program expects valid Token Holding Account for Vault B") + .balance; + let vault_a_balance = TokenHolding::parse(&vault_a.account.data) + .expect("Add liquidity: AMM Program expects valid Token Holding Account for Vault A") + .balance; + + if pool_def_data.reserve_a == 0 || pool_def_data.reserve_b == 0 { + panic!("Reserves must be nonzero"); + } + + if vault_a_balance < pool_def_data.reserve_a || vault_b_balance < pool_def_data.reserve_b { + panic!("Vaults' balances must be at least the reserve amounts"); + } + + // Calculate actual_amounts + let ideal_a: u128 = (pool_def_data.reserve_a * max_amount_b) / pool_def_data.reserve_b; + let ideal_b: u128 = (pool_def_data.reserve_b * max_amount_a) / pool_def_data.reserve_a; + + let actual_amount_a = if ideal_a > max_amount_a { + max_amount_a + } else { + ideal_a + }; + let actual_amount_b = if ideal_b > max_amount_b { + max_amount_b + } else { + ideal_b + }; + + // 3. Validate amounts + if max_amount_a < actual_amount_a || max_amount_b < actual_amount_b { + panic!("Actual trade amounts cannot exceed max_amounts"); + } + + if actual_amount_a == 0 || actual_amount_b == 0 { + panic!("A trade amount is 0"); + } + + // 4. Calculate LP to mint + let delta_lp = std::cmp::min( + pool_def_data.liquidity_pool_supply * actual_amount_a / pool_def_data.reserve_a, + pool_def_data.liquidity_pool_supply * actual_amount_b / pool_def_data.reserve_b, + ); + + if delta_lp == 0 { + panic!("Payable LP must be nonzero"); + } + + if delta_lp < min_amount_lp { + panic!("Payable LP is less than provided minimum LP amount"); + } + + // 5. Update pool account + let mut pool_post = pool.account.clone(); + let pool_post_definition = PoolDefinition { + liquidity_pool_supply: pool_def_data.liquidity_pool_supply + delta_lp, + reserve_a: pool_def_data.reserve_a + actual_amount_a, + reserve_b: pool_def_data.reserve_b + actual_amount_b, + ..pool_def_data + }; + + pool_post.data = pool_post_definition.into_data(); + let mut chained_call = Vec::new(); + + // Chain call for Token A (UserHoldingA -> Vault_A) + let call_token_a = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + user_holding_a.clone(), + vault_a.clone(), + actual_amount_a, + Vec::::new(), + ); + // Chain call for Token B (UserHoldingB -> Vault_B) + let call_token_b = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + user_holding_b.clone(), + vault_b.clone(), + actual_amount_b, + Vec::::new(), + ); + // Chain call for LP (mint new tokens for user_holding_lp) + let mut pool_definition_lp_auth = pool_definition_lp.clone(); + pool_definition_lp_auth.is_authorized = true; + let call_token_lp = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_MINT, + pool_definition_lp_auth.clone(), + user_holding_lp.clone(), + delta_lp, + vec![compute_liquidity_token_pda_seed(pool.account_id)], + ); + + chained_call.push(call_token_lp); + chained_call.push(call_token_b); + chained_call.push(call_token_a); + + let post_states = vec![ + AccountPostState::new(pool_post), + AccountPostState::new(pre_states[1].account.clone()), + AccountPostState::new(pre_states[2].account.clone()), + AccountPostState::new(pre_states[3].account.clone()), + AccountPostState::new(pre_states[4].account.clone()), + AccountPostState::new(pre_states[5].account.clone()), + AccountPostState::new(pre_states[6].account.clone()), + ]; + + (post_states, chained_call) +} + +fn remove_liquidity( + pre_states: &[AccountWithMetadata], + amounts: &[u128], +) -> (Vec, Vec) { + if pre_states.len() != 7 { + panic!("Invalid number of input accounts"); + } + + let pool = &pre_states[0]; + let vault_a = &pre_states[1]; + let vault_b = &pre_states[2]; + let pool_definition_lp = &pre_states[3]; + let user_holding_a = &pre_states[4]; + let user_holding_b = &pre_states[5]; + let user_holding_lp = &pre_states[6]; + + if amounts.len() != 3 { + panic!("Invalid number of balances"); + } + + let amount_lp = amounts[0]; + let amount_min_a = amounts[1]; + let amount_min_b = amounts[2]; + + // 1. Fetch Pool state + let pool_def_data = PoolDefinition::parse(&pool.account.data) + .expect("Remove liquidity: AMM Program expects a valid Pool Definition Account"); + + if !pool_def_data.active { + panic!("Pool is inactive"); + } + + if pool_def_data.liquidity_pool_id != pool_definition_lp.account_id { + panic!("LP definition mismatch"); + } + + if vault_a.account_id != pool_def_data.vault_a_id { + panic!("Vault A was not provided"); + } + + if vault_b.account_id != pool_def_data.vault_b_id { + panic!("Vault B was not provided"); + } + + // Vault addresses do not need to be checked with PDA + // calculation for setting authorization since stored + // in the Pool Definition. + let mut running_vault_a = vault_a.clone(); + let mut running_vault_b = vault_b.clone(); + running_vault_a.is_authorized = true; + running_vault_b.is_authorized = true; + + if amount_min_a == 0 || amount_min_b == 0 { + panic!("Minimum withdraw amount must be nonzero"); + } + + if amount_lp == 0 { + panic!("Liquidity amount must be nonzero"); + } + + // 2. Compute withdrawal amounts + let user_holding_lp_data = TokenHolding::parse(&user_holding_lp.account.data) + .expect("Remove liquidity: AMM Program expects a valid Token Account for liquidity token"); + + if user_holding_lp_data.balance > pool_def_data.liquidity_pool_supply + || user_holding_lp_data.definition_id != pool_def_data.liquidity_pool_id + { + panic!("Invalid liquidity account provided"); + } + + let withdraw_amount_a = + (pool_def_data.reserve_a * amount_lp) / pool_def_data.liquidity_pool_supply; + let withdraw_amount_b = + (pool_def_data.reserve_b * amount_lp) / pool_def_data.liquidity_pool_supply; + + // 3. Validate and slippage check + if withdraw_amount_a < amount_min_a { + panic!("Insufficient minimal withdraw amount (Token A) provided for liquidity amount"); + } + if withdraw_amount_b < amount_min_b { + panic!("Insufficient minimal withdraw amount (Token B) provided for liquidity amount"); + } + + // 4. Calculate LP to reduce cap by + let delta_lp: u128 = + (pool_def_data.liquidity_pool_supply * amount_lp) / pool_def_data.liquidity_pool_supply; + + let active: bool = pool_def_data.liquidity_pool_supply - delta_lp != 0; + + // 5. Update pool account + let mut pool_post = pool.account.clone(); + let pool_post_definition = PoolDefinition { + liquidity_pool_supply: pool_def_data.liquidity_pool_supply - delta_lp, + reserve_a: pool_def_data.reserve_a - withdraw_amount_a, + reserve_b: pool_def_data.reserve_b - withdraw_amount_b, + active, + ..pool_def_data.clone() + }; + + pool_post.data = pool_post_definition.into_data(); + + let mut chained_calls = Vec::new(); + + // Chaincall for Token A withdraw + let call_token_a = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + running_vault_a, + user_holding_a.clone(), + withdraw_amount_a, + vec![compute_vault_pda_seed( + pool.account_id, + pool_def_data.definition_token_a_id, + )], + ); + // Chaincall for Token B withdraw + let call_token_b = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_TRANSFER, + running_vault_b, + user_holding_b.clone(), + withdraw_amount_b, + vec![compute_vault_pda_seed( + pool.account_id, + pool_def_data.definition_token_b_id, + )], + ); + // Chaincall for LP adjustment + let mut pool_definition_lp_auth = pool_definition_lp.clone(); + pool_definition_lp_auth.is_authorized = true; + let call_token_lp = initialize_token_transfer_chained_call( + TOKEN_PROGRAM_BURN, + pool_definition_lp_auth.clone(), + user_holding_lp.clone(), + delta_lp, + vec![compute_liquidity_token_pda_seed(pool.account_id)], + ); + + chained_calls.push(call_token_lp); + chained_calls.push(call_token_b); + chained_calls.push(call_token_a); + + let post_states = vec![ + AccountPostState::new(pool_post.clone()), + AccountPostState::new(pre_states[1].account.clone()), + AccountPostState::new(pre_states[2].account.clone()), + AccountPostState::new(pre_states[3].account.clone()), + AccountPostState::new(pre_states[4].account.clone()), + AccountPostState::new(pre_states[5].account.clone()), + AccountPostState::new(pre_states[6].account.clone()), + ]; + + (post_states, chained_calls) +} + +#[cfg(test)] +mod tests { + use nssa_core::{ + account::{Account, AccountId, AccountWithMetadata, Data}, + program::{ChainedCall, PdaSeed, ProgramId}, + }; + + use crate::{ + PoolDefinition, TokenHolding, add_liquidity, compute_liquidity_token_pda, + compute_liquidity_token_pda_seed, compute_pool_pda, compute_vault_pda, + compute_vault_pda_seed, new_definition, remove_liquidity, swap, + }; + + const TOKEN_PROGRAM_ID: ProgramId = [15; 8]; + const AMM_PROGRAM_ID: ProgramId = [42; 8]; + const TOKEN_DEFINITION_DATA_SIZE: usize = 55; + + struct TokenDefinition { + account_type: u8, + name: [u8; 6], + total_supply: u128, + metadata_id: AccountId, + } + + impl TokenDefinition { + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Token definition data size must fit into data") + } + } + + struct BalanceForTests; + + impl BalanceForTests { + fn vault_a_reserve_init() -> u128 { + 1_000 + } + + fn vault_b_reserve_init() -> u128 { + 500 + } + + fn vault_a_reserve_low() -> u128 { + 10 + } + + fn vault_b_reserve_low() -> u128 { + 10 + } + + fn vault_a_reserve_high() -> u128 { + 500_000 + } + + fn vault_b_reserve_high() -> u128 { + 500_000 + } + + fn user_token_a_balance() -> u128 { + 1_000 + } + + fn user_token_b_balance() -> u128 { + 500 + } + + fn user_token_lp_balance() -> u128 { + 100 + } + + fn remove_min_amount_a() -> u128 { + 50 + } + + fn remove_min_amount_b() -> u128 { + 100 + } + + fn remove_actual_a_successful() -> u128 { + 100 + } + + fn remove_min_amount_b_low() -> u128 { + 50 + } + + fn remove_amount_lp() -> u128 { + 100 + } + + fn remove_amount_lp_1() -> u128 { + 30 + } + + fn add_max_amount_a() -> u128 { + 500 + } + + fn add_max_amount_b() -> u128 { + 200 + } + + fn add_max_amount_a_low() -> u128 { + 10 + } + + fn add_max_amount_b_low() -> u128 { + 10 + } + + fn add_min_amount_lp() -> u128 { + 20 + } + + fn vault_a_swap_test_1() -> u128 { + 1_500 + } + + fn vault_a_swap_test_2() -> u128 { + 715 + } + + fn vault_b_swap_test_1() -> u128 { + 334 + } + + fn vault_b_swap_test_2() -> u128 { + 700 + } + + fn min_amount_out() -> u128 { + 200 + } + + fn vault_a_add_successful() -> u128 { + 1_400 + } + + fn vault_b_add_successful() -> u128 { + 700 + } + + fn add_successful_amount_a() -> u128 { + 400 + } + + fn add_successful_amount_b() -> u128 { + 200 + } + + fn vault_a_remove_successful() -> u128 { + 900 + } + + fn vault_b_remove_successful() -> u128 { + 450 + } + } + + struct ChainedCallForTests; + + impl ChainedCallForTests { + fn cc_swap_token_a_test_1() -> ChainedCall { + let mut instruction_data = vec![0; 23]; + instruction_data[0] = 1; + instruction_data[1..17] + .copy_from_slice(&BalanceForTests::add_max_amount_a().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::user_holding_a(), + AccountForTests::vault_a_init(), + ], + pda_seeds: Vec::::new(), + } + } + + fn cc_swap_token_b_test_1() -> ChainedCall { + let swap_amount: u128 = 166; + + let mut vault_b_auth = AccountForTests::vault_b_init(); + vault_b_auth.is_authorized = true; + + let mut instruction = vec![0; 23]; + instruction[0] = 1; + instruction[1..17].copy_from_slice(&swap_amount.to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![vault_b_auth, AccountForTests::user_holding_b()], + pda_seeds: vec![compute_vault_pda_seed( + IdForTests::pool_definition_id(), + IdForTests::token_b_definition_id(), + )], + } + } + + fn cc_swap_token_a_test_2() -> ChainedCall { + let swap_amount: u128 = 285; + + let mut vault_a_auth = AccountForTests::vault_a_init(); + vault_a_auth.is_authorized = true; + + let mut instruction_data = vec![0; 23]; + instruction_data[0] = 1; + instruction_data[1..17].copy_from_slice(&swap_amount.to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction_data) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![vault_a_auth, AccountForTests::user_holding_a()], + pda_seeds: vec![compute_vault_pda_seed( + IdForTests::pool_definition_id(), + IdForTests::token_a_definition_id(), + )], + } + } + + fn cc_swap_token_b_test_2() -> ChainedCall { + let mut instruction = vec![0; 23]; + instruction[0] = 1; + instruction[1..17].copy_from_slice(&BalanceForTests::add_max_amount_b().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::user_holding_b(), + AccountForTests::vault_b_init(), + ], + pda_seeds: Vec::::new(), + } + } + + fn cc_add_token_a() -> ChainedCall { + let mut instruction = vec![0u8; 23]; + instruction[0] = 1; + instruction[1..17] + .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::user_holding_a(), + AccountForTests::vault_a_init(), + ], + pda_seeds: Vec::::new(), + } + } + + fn cc_add_token_b() -> ChainedCall { + let mut instruction = vec![0u8; 23]; + instruction[0] = 1; + instruction[1..17] + .copy_from_slice(&BalanceForTests::add_successful_amount_b().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("Swap Logic: AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::user_holding_b(), + AccountForTests::vault_b_init(), + ], + pda_seeds: Vec::::new(), + } + } + + fn cc_add_pool_lp() -> ChainedCall { + let mut pool_lp_auth = AccountForTests::pool_lp_init(); + pool_lp_auth.is_authorized = true; + + let mut instruction = vec![0u8; 23]; + instruction[0] = 4; + instruction[1..17] + .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("Swap Logic: AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![pool_lp_auth, AccountForTests::user_holding_lp_init()], + pda_seeds: vec![compute_liquidity_token_pda_seed( + IdForTests::pool_definition_id(), + )], + } + } + + fn cc_remove_token_a() -> ChainedCall { + let mut vault_a_auth = AccountForTests::vault_a_init(); + vault_a_auth.is_authorized = true; + + let mut instruction = vec![0; 23]; + instruction[0] = 1; + instruction[1..17] + .copy_from_slice(&BalanceForTests::remove_actual_a_successful().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![vault_a_auth, AccountForTests::user_holding_a()], + pda_seeds: vec![compute_vault_pda_seed( + IdForTests::pool_definition_id(), + IdForTests::token_a_definition_id(), + )], + } + } + + fn cc_remove_token_b() -> ChainedCall { + let mut vault_b_auth = AccountForTests::vault_b_init(); + vault_b_auth.is_authorized = true; + + let mut instruction = vec![0; 23]; + instruction[0] = 1; + instruction[1..17] + .copy_from_slice(&BalanceForTests::remove_min_amount_b_low().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![vault_b_auth, AccountForTests::user_holding_b()], + pda_seeds: vec![compute_vault_pda_seed( + IdForTests::pool_definition_id(), + IdForTests::token_b_definition_id(), + )], + } + } + + fn cc_remove_pool_lp() -> ChainedCall { + let mut pool_lp_auth = AccountForTests::pool_lp_init(); + pool_lp_auth.is_authorized = true; + + let mut instruction = vec![0; 23]; + instruction[0] = 3; + instruction[1..17] + .copy_from_slice(&BalanceForTests::remove_actual_a_successful().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_lp_init(), + ], + pda_seeds: vec![compute_liquidity_token_pda_seed( + IdForTests::pool_definition_id(), + )], + } + } + + fn cc_new_definition_token_a() -> ChainedCall { + let mut instruction = vec![0; 23]; + instruction[0] = 1; + instruction[1..17] + .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::user_holding_a(), + AccountForTests::vault_a_init(), + ], + pda_seeds: Vec::::new(), + } + } + + fn cc_new_definition_token_b() -> ChainedCall { + let mut instruction = vec![0; 23]; + instruction[0] = 1; + instruction[1..17] + .copy_from_slice(&BalanceForTests::add_successful_amount_b().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("Swap Logic: AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::user_holding_b(), + AccountForTests::vault_b_init(), + ], + pda_seeds: Vec::::new(), + } + } + + fn cc_new_definition_token_lp() -> ChainedCall { + let mut instruction = vec![0; 23]; + instruction[0] = 1; + instruction[1..17] + .copy_from_slice(&BalanceForTests::add_successful_amount_a().to_le_bytes()); + let instruction_data = risc0_zkvm::serde::to_vec(&instruction) + .expect("AMM Program expects valid transaction instruction data"); + ChainedCall { + program_id: TOKEN_PROGRAM_ID, + instruction_data, + pre_states: vec![ + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_lp_uninit(), + ], + pda_seeds: vec![compute_liquidity_token_pda_seed( + IdForTests::pool_definition_id(), + )], + } + } + } + + struct IdForTests; + + impl IdForTests { + fn token_a_definition_id() -> AccountId { + AccountId::new([42; 32]) + } + + fn token_b_definition_id() -> AccountId { + AccountId::new([43; 32]) + } + + fn token_lp_definition_id() -> AccountId { + compute_liquidity_token_pda(AMM_PROGRAM_ID, IdForTests::pool_definition_id()) + } + + fn user_token_a_id() -> AccountId { + AccountId::new([45; 32]) + } + + fn user_token_b_id() -> AccountId { + AccountId::new([46; 32]) + } + + fn user_token_lp_id() -> AccountId { + AccountId::new([47; 32]) + } + + fn pool_definition_id() -> AccountId { + compute_pool_pda( + AMM_PROGRAM_ID, + IdForTests::token_a_definition_id(), + IdForTests::token_b_definition_id(), + ) + } + + fn vault_a_id() -> AccountId { + compute_vault_pda( + AMM_PROGRAM_ID, + IdForTests::pool_definition_id(), + IdForTests::token_a_definition_id(), + ) + } + + fn vault_b_id() -> AccountId { + compute_vault_pda( + AMM_PROGRAM_ID, + IdForTests::pool_definition_id(), + IdForTests::token_b_definition_id(), + ) + } + } + + struct AccountForTests; + + impl AccountForTests { + fn user_holding_a() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::user_token_a_balance(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::user_token_a_id(), + } + } + + fn user_holding_b() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::user_token_b_balance(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::user_token_b_id(), + } + } + + fn vault_a_init() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_reserve_init(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_a_id(), + } + } + + fn vault_b_init() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_reserve_init(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_b_id(), + } + } + + fn vault_a_init_high() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_reserve_high(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_a_id(), + } + } + + fn vault_b_init_high() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_reserve_high(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_b_id(), + } + } + + fn vault_a_init_low() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_reserve_low(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_a_id(), + } + } + + fn vault_b_init_low() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_reserve_low(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_b_id(), + } + } + + fn vault_a_init_zero() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: 0, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_a_id(), + } + } + + fn vault_b_init_zero() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: 0, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_b_id(), + } + } + + fn pool_lp_init() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1; 6], + total_supply: BalanceForTests::vault_a_reserve_init(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::token_lp_definition_id(), + } + } + + fn pool_lp_with_wrong_id() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: 0u8, + name: [1; 6], + total_supply: BalanceForTests::vault_a_reserve_init(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::vault_a_id(), + } + } + + fn user_holding_lp_uninit() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_lp_definition_id(), + balance: 0, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::user_token_lp_id(), + } + } + + fn user_holding_lp_init() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_lp_definition_id(), + balance: BalanceForTests::user_token_lp_balance(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::user_token_lp_id(), + } + } + + fn pool_definition_uninit() -> AccountWithMetadata { + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_init() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: BalanceForTests::vault_a_reserve_init(), + reserve_b: BalanceForTests::vault_b_reserve_init(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_init_reserve_a_zero() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: 0, + reserve_b: BalanceForTests::vault_b_reserve_init(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_init_reserve_b_zero() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: BalanceForTests::vault_a_reserve_init(), + reserve_b: 0, + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_init_reserve_a_low() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(), + reserve_a: BalanceForTests::vault_a_reserve_low(), + reserve_b: BalanceForTests::vault_b_reserve_high(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_init_reserve_b_low() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_high(), + reserve_a: BalanceForTests::vault_a_reserve_high(), + reserve_b: BalanceForTests::vault_b_reserve_low(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_swap_test_1() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: BalanceForTests::vault_a_swap_test_1(), + reserve_b: BalanceForTests::vault_b_swap_test_1(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_swap_test_2() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: BalanceForTests::vault_a_swap_test_2(), + reserve_b: BalanceForTests::vault_b_swap_test_2(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_add_zero_lp() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_low(), + reserve_a: BalanceForTests::vault_a_reserve_init(), + reserve_b: BalanceForTests::vault_b_reserve_init(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_add_successful() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_add_successful(), + reserve_a: BalanceForTests::vault_a_add_successful(), + reserve_b: BalanceForTests::vault_b_add_successful(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_remove_successful() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_remove_successful(), + reserve_a: BalanceForTests::vault_a_remove_successful(), + reserve_b: BalanceForTests::vault_b_remove_successful(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_inactive() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: BalanceForTests::vault_a_reserve_init(), + reserve_b: BalanceForTests::vault_b_reserve_init(), + fees: 0u128, + active: false, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn pool_definition_with_wrong_id() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: BalanceForTests::vault_a_reserve_init(), + reserve_b: BalanceForTests::vault_b_reserve_init(), + fees: 0u128, + active: false, + }), + nonce: 0, + }, + is_authorized: true, + account_id: AccountId::new([4; 32]), + } + } + + fn vault_a_with_wrong_id() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_a_definition_id(), + balance: BalanceForTests::vault_a_reserve_init(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: AccountId::new([4; 32]), + } + } + + fn vault_b_with_wrong_id() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: TOKEN_PROGRAM_ID, + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: 1u8, + definition_id: IdForTests::token_b_definition_id(), + balance: BalanceForTests::vault_b_reserve_init(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: AccountId::new([4; 32]), + } + } + + fn pool_definition_active() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: ProgramId::default(), + balance: 0u128, + data: PoolDefinition::into_data(PoolDefinition { + definition_token_a_id: IdForTests::token_a_definition_id(), + definition_token_b_id: IdForTests::token_b_definition_id(), + vault_a_id: IdForTests::vault_a_id(), + vault_b_id: IdForTests::vault_b_id(), + liquidity_pool_id: IdForTests::token_lp_definition_id(), + liquidity_pool_supply: BalanceForTests::vault_a_reserve_init(), + reserve_a: BalanceForTests::vault_a_reserve_init(), + reserve_b: BalanceForTests::vault_b_reserve_init(), + fees: 0u128, + active: true, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + } + + #[test] + fn test_pool_pda_produces_unique_id_for_token_pair() { + // compute_pool_pda(amm_program_id: ProgramId, definition_token_a_id: AccountId, + // definition_token_b_id: AccountId) + assert!( + compute_pool_pda( + AMM_PROGRAM_ID, + IdForTests::token_a_definition_id(), + IdForTests::token_b_definition_id() + ) == compute_pool_pda( + AMM_PROGRAM_ID, + IdForTests::token_b_definition_id(), + IdForTests::token_a_definition_id() + ) + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_1() { + let pre_states = vec![AccountForTests::pool_definition_uninit()]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_3() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_4() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_5() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_6() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Invalid number of input balances")] + #[test] + fn test_call_new_definition_with_invalid_number_of_balances() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[BalanceForTests::vault_a_reserve_init()], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Balances must be nonzero")] + #[test] + fn test_call_new_definition_with_zero_balance_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[0, BalanceForTests::vault_b_reserve_init()], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Balances must be nonzero")] + #[test] + fn test_call_new_definition_with_zero_balance_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[BalanceForTests::vault_a_reserve_init(), 0], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Cannot set up a swap for a token with itself")] + #[test] + fn test_call_new_definition_same_token_definition() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Liquidity pool Token Definition Account ID does not match PDA")] + #[test] + fn test_call_new_definition_wrong_liquidity_id() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_with_wrong_id(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Pool Definition Account ID does not match PDA")] + #[test] + fn test_call_new_definition_wrong_pool_id() { + let pre_states = vec![ + AccountForTests::pool_definition_with_wrong_id(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Vault ID does not match PDA")] + #[test] + fn test_call_new_definition_wrong_vault_id_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_with_wrong_id(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Vault ID does not match PDA")] + #[test] + fn test_call_new_definition_wrong_vault_id_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_with_wrong_id(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Cannot initialize an active Pool Definition")] + #[test] + fn test_call_new_definition_cannot_initialize_active_pool() { + let pre_states = vec![ + AccountForTests::pool_definition_active(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let _post_states = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + } + + #[should_panic(expected = "Cannot initialize an active Pool Definition")] + #[test] + fn test_call_new_definition_chained_call_successful() { + let pre_states = vec![ + AccountForTests::pool_definition_active(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_uninit(), + ]; + let (post_states, chained_calls) = new_definition( + &pre_states, + &[ + BalanceForTests::vault_a_reserve_init(), + BalanceForTests::vault_b_reserve_init(), + ], + AMM_PROGRAM_ID, + ); + + let pool_post = post_states[0].clone(); + + assert!(AccountForTests::pool_definition_add_successful().account == *pool_post.account()); + + let chained_call_lp = chained_calls[0].clone(); + let chained_call_b = chained_calls[1].clone(); + let chained_call_a = chained_calls[2].clone(); + + assert!(chained_call_a == ChainedCallForTests::cc_new_definition_token_a()); + assert!(chained_call_b == ChainedCallForTests::cc_new_definition_token_b()); + assert!(chained_call_lp == ChainedCallForTests::cc_new_definition_token_lp()); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_remove_liquidity_with_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_remove_liquidity_with_invalid_number_of_accounts_3() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_remove_liquidity_with_invalid_number_of_accounts_4() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_remove_liquidity_with_invalid_number_of_accounts_5() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_remove_liquidity_with_invalid_number_of_accounts_6() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Vault A was not provided")] + #[test] + fn test_call_remove_liquidity_vault_a_omitted() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_with_wrong_id(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Vault B was not provided")] + #[test] + fn test_call_remove_liquidity_vault_b_omitted() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_with_wrong_id(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "LP definition mismatch")] + #[test] + fn test_call_remove_liquidity_lp_def_mismatch() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_with_wrong_id(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid liquidity account provided")] + #[test] + fn test_call_remove_liquidity_insufficient_liquidity_amount() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_a(), /* different token account than lp to create + * desired error */ + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic( + expected = "Insufficient minimal withdraw amount (Token A) provided for liquidity amount" + )] + #[test] + fn test_call_remove_liquidity_insufficient_balance_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp_1(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic( + expected = "Insufficient minimal withdraw amount (Token B) provided for liquidity amount" + )] + #[test] + fn test_call_remove_liquidity_insufficient_balance_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Minimum withdraw amount must be nonzero")] + #[test] + fn test_call_remove_liquidity_min_bal_zero_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + 0, + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[should_panic(expected = "Minimum withdraw amount must be nonzero")] + #[test] + fn test_call_remove_liquidity_min_bal_zero_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + 0, + ], + ); + } + + #[should_panic(expected = "Liquidity amount must be nonzero")] + #[test] + fn test_call_remove_liquidity_lp_bal_zero() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = remove_liquidity( + &pre_states, + &[ + 0, + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b(), + ], + ); + } + + #[test] + fn test_call_remove_liquidity_chained_call_successful() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let (post_states, chained_calls) = remove_liquidity( + &pre_states, + &[ + BalanceForTests::remove_amount_lp(), + BalanceForTests::remove_min_amount_a(), + BalanceForTests::remove_min_amount_b_low(), + ], + ); + + let pool_post = post_states[0].clone(); + + assert!( + AccountForTests::pool_definition_remove_successful().account == *pool_post.account() + ); + + let chained_call_lp = chained_calls[0].clone(); + let chained_call_b = chained_calls[1].clone(); + let chained_call_a = chained_calls[2].clone(); + + assert!(chained_call_a == ChainedCallForTests::cc_remove_token_a()); + assert!(chained_call_b == ChainedCallForTests::cc_remove_token_b()); + assert!(chained_call_lp == ChainedCallForTests::cc_remove_pool_lp()); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_add_liquidity_with_invalid_number_of_accounts_1() { + let pre_states = vec![AccountForTests::pool_definition_init()]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_add_liquidity_with_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_add_liquidity_with_invalid_number_of_accounts_3() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_add_liquidity_with_invalid_number_of_accounts_4() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_add_liquidity_with_invalid_number_of_accounts_5() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_add_liquidity_with_invalid_number_of_accounts_6() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Invalid number of input balances")] + #[test] + fn test_call_add_liquidity_invalid_number_of_balances_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity(&pre_states, &[BalanceForTests::add_min_amount_lp()]); + } + + #[should_panic(expected = "Invalid number of input balances")] + #[test] + fn test_call_add_liquidity_invalid_number_of_balances_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + ], + ); + } + + #[should_panic(expected = "Vault A was not provided")] + #[test] + fn test_call_add_liquidity_vault_a_omitted() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_with_wrong_id(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Vault B was not provided")] + #[test] + fn test_call_add_liquidity_vault_b_omitted() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_with_wrong_id(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "LP definition mismatch")] + #[test] + fn test_call_add_liquidity_lp_definition_mismatch() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_with_wrong_id(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Both max-balances must be nonzero")] + #[test] + fn test_call_add_liquidity_zero_balance_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + 0, + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Both max-balances must be nonzero")] + #[test] + fn test_call_add_liquidity_zero_balance_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + 0, + BalanceForTests::add_max_amount_a(), + ], + ); + } + + #[should_panic(expected = "Min-lp must be nonzero")] + #[test] + fn test_call_add_liquidity_zero_min_lp() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + 0, + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Vaults' balances must be at least the reserve amounts")] + #[test] + fn test_call_add_liquidity_vault_insufficient_balance_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init_zero(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + BalanceForTests::add_min_amount_lp(), + ], + ); + } + + #[should_panic(expected = "Vaults' balances must be at least the reserve amounts")] + #[test] + fn test_call_add_liquidity_vault_insufficient_balance_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init_zero(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + BalanceForTests::add_min_amount_lp(), + ], + ); + } + + #[should_panic(expected = "A trade amount is 0")] + #[test] + fn test_call_add_liquidity_actual_amount_zero_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init_reserve_a_low(), + AccountForTests::vault_a_init_low(), + AccountForTests::vault_b_init_high(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "A trade amount is 0")] + #[test] + fn test_call_add_liquidity_actual_amount_zero_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init_reserve_b_low(), + AccountForTests::vault_a_init_high(), + AccountForTests::vault_b_init_low(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a_low(), + BalanceForTests::add_max_amount_b_low(), + ], + ); + } + + #[should_panic(expected = "Reserves must be nonzero")] + #[test] + fn test_call_add_liquidity_reserves_zero_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init_reserve_a_zero(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Reserves must be nonzero")] + #[test] + fn test_call_add_liquidity_reserves_zero_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init_reserve_b_zero(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + } + + #[should_panic(expected = "Payable LP must be nonzero")] + #[test] + fn test_call_add_liquidity_payable_lp_zero() { + let pre_states = vec![ + AccountForTests::pool_definition_add_zero_lp(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let _post_states = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a_low(), + BalanceForTests::add_max_amount_b_low(), + ], + ); + } + + #[test] + fn test_call_add_liquidity_chained_call_successsful() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::pool_lp_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + AccountForTests::user_holding_lp_init(), + ]; + let (post_states, chained_calls) = add_liquidity( + &pre_states, + &[ + BalanceForTests::add_min_amount_lp(), + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_b(), + ], + ); + + let pool_post = post_states[0].clone(); + + assert!(AccountForTests::pool_definition_add_successful().account == *pool_post.account()); + + let chained_call_lp = chained_calls[0].clone(); + let chained_call_b = chained_calls[1].clone(); + let chained_call_a = chained_calls[2].clone(); + + assert!(chained_call_a == ChainedCallForTests::cc_add_token_a()); + assert!(chained_call_b == ChainedCallForTests::cc_add_token_b()); + assert!(chained_call_lp == ChainedCallForTests::cc_add_pool_lp()); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_swap_with_invalid_number_of_accounts_1() { + let pre_states = vec![AccountForTests::pool_definition_init()]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_swap_with_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_swap_with_invalid_number_of_accounts_3() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_swap_with_invalid_number_of_accounts_4() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Invalid number of amounts provided")] + #[test] + fn test_call_swap_with_invalid_number_of_amounts() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[BalanceForTests::add_max_amount_a()], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "AccountId is not a token type for the pool")] + #[test] + fn test_call_swap_incorrect_token_type() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_lp_definition_id(), + ); + } + + #[should_panic(expected = "Vault A was not provided")] + #[test] + fn test_call_swap_vault_a_omitted() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_with_wrong_id(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Vault B was not provided")] + #[test] + fn test_call_swap_vault_b_omitted() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_with_wrong_id(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Reserve for Token A exceeds vault balance")] + #[test] + fn test_call_swap_reserves_vault_mismatch_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init_low(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Reserve for Token B exceeds vault balance")] + #[test] + fn test_call_swap_reserves_vault_mismatch_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init_low(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Pool is inactive")] + #[test] + fn test_call_swap_ianctive() { + let pre_states = vec![ + AccountForTests::pool_definition_inactive(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[should_panic(expected = "Withdraw amount is less than minimal amount out")] + #[test] + fn test_call_swap_below_min_out() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let _post_states = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_a_definition_id(), + ); + } + + #[test] + fn test_call_swap_chained_call_successful_1() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let (post_states, chained_calls) = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_a(), + BalanceForTests::add_max_amount_a_low(), + ], + IdForTests::token_a_definition_id(), + ); + + let pool_post = post_states[0].clone(); + + assert!(AccountForTests::pool_definition_swap_test_1().account == *pool_post.account()); + + let chained_call_a = chained_calls[0].clone(); + let chained_call_b = chained_calls[1].clone(); + + assert!(chained_call_a == ChainedCallForTests::cc_swap_token_a_test_1()); + assert!(chained_call_b == ChainedCallForTests::cc_swap_token_b_test_1()); + } + + #[test] + fn test_call_swap_chained_call_successful_2() { + let pre_states = vec![ + AccountForTests::pool_definition_init(), + AccountForTests::vault_a_init(), + AccountForTests::vault_b_init(), + AccountForTests::user_holding_a(), + AccountForTests::user_holding_b(), + ]; + let (post_states, chained_calls) = swap( + &pre_states, + &[ + BalanceForTests::add_max_amount_b(), + BalanceForTests::min_amount_out(), + ], + IdForTests::token_b_definition_id(), + ); + + let pool_post = post_states[0].clone(); + + assert!(AccountForTests::pool_definition_swap_test_2().account == *pool_post.account()); + + let chained_call_a = chained_calls[1].clone(); + let chained_call_b = chained_calls[0].clone(); + + assert!(chained_call_a == ChainedCallForTests::cc_swap_token_a_test_2()); + assert!(chained_call_b == ChainedCallForTests::cc_swap_token_b_test_2()); + } +} diff --git a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs b/program_methods/guest/src/bin/authenticated_transfer.rs similarity index 95% rename from nssa/program_methods/guest/src/bin/authenticated_transfer.rs rename to program_methods/guest/src/bin/authenticated_transfer.rs index fe02d065..8a13173a 100644 --- a/nssa/program_methods/guest/src/bin/authenticated_transfer.rs +++ b/program_methods/guest/src/bin/authenticated_transfer.rs @@ -17,7 +17,7 @@ fn initialize_account(pre_state: AccountWithMetadata) -> AccountPostState { // Continue only if the owner authorized this operation if !is_authorized { - panic!("Invalid input"); + panic!("Account must be authorized"); } account_to_claim @@ -31,12 +31,12 @@ fn transfer( ) -> Vec { // Continue only if the sender has authorized this operation if !sender.is_authorized { - panic!("Invalid input"); + panic!("Sender must be authorized"); } // Continue only if the sender has enough balance if sender.account.balance < balance_to_move { - panic!("Invalid input"); + panic!("Sender has insufficient balance"); } // Create accounts post states, with updated balances diff --git a/nssa/program_methods/guest/src/bin/pinata.rs b/program_methods/guest/src/bin/pinata.rs similarity index 97% rename from nssa/program_methods/guest/src/bin/pinata.rs rename to program_methods/guest/src/bin/pinata.rs index a0c46a1a..0dc3c108 100644 --- a/nssa/program_methods/guest/src/bin/pinata.rs +++ b/program_methods/guest/src/bin/pinata.rs @@ -77,7 +77,7 @@ fn main() { instruction_words, vec![pinata, winner], vec![ - AccountPostState::new(pinata_post), + AccountPostState::new_claimed_if_default(pinata_post), AccountPostState::new(winner_post), ], ); diff --git a/nssa/program_methods/guest/src/bin/pinata_token.rs b/program_methods/guest/src/bin/pinata_token.rs similarity index 98% rename from nssa/program_methods/guest/src/bin/pinata_token.rs rename to program_methods/guest/src/bin/pinata_token.rs index f988be9e..04613791 100644 --- a/nssa/program_methods/guest/src/bin/pinata_token.rs +++ b/program_methods/guest/src/bin/pinata_token.rs @@ -82,7 +82,7 @@ fn main() { let winner_token_holding_post = winner_token_holding.account.clone(); pinata_definition_post.data = data.next_data(); - let mut instruction_data: [u8; 23] = [0; 23]; + let mut instruction_data = vec![0; 23]; instruction_data[0] = 1; instruction_data[1..17].copy_from_slice(&PRIZE.to_le_bytes()); diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs new file mode 100644 index 00000000..4bbd895f --- /dev/null +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -0,0 +1,404 @@ +use std::{ + collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, + convert::Infallible, +}; + +use nssa_core::{ + Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, MembershipProof, + Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, + PrivacyPreservingCircuitOutput, SharedSecretKey, + account::{Account, AccountId, AccountWithMetadata, Nonce}, + compute_digest_for_path, + program::{ + AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId, + ProgramOutput, validate_execution, + }, +}; +use risc0_zkvm::{guest::env, serde::to_vec}; + +fn main() { + let PrivacyPreservingCircuitInput { + program_outputs, + visibility_mask, + private_account_nonces, + private_account_keys, + private_account_nsks, + private_account_membership_proofs, + program_id, + } = env::read(); + + let execution_state = ExecutionState::derive_from_outputs(program_id, program_outputs); + + let output = compute_circuit_output( + execution_state, + &visibility_mask, + &private_account_nonces, + &private_account_keys, + &private_account_nsks, + &private_account_membership_proofs, + ); + + env::commit(&output); +} + +/// State of the involved accounts before and after program execution. +struct ExecutionState { + pre_states: Vec, + post_states: HashMap, +} + +impl ExecutionState { + /// Validate program outputs and derive the overall execution state. + pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec) -> Self { + let Some(first_output) = program_outputs.first() else { + panic!("No program outputs provided"); + }; + + let initial_call = ChainedCall { + program_id, + instruction_data: first_output.instruction_data.clone(), + pre_states: first_output.pre_states.clone(), + pda_seeds: Vec::new(), + }; + let mut chained_calls = VecDeque::from_iter([(initial_call, None)]); + + let mut execution_state = ExecutionState { + pre_states: Vec::new(), + post_states: HashMap::new(), + }; + + let mut program_outputs_iter = program_outputs.into_iter(); + let mut chain_calls_counter = 0; + + while let Some((chained_call, caller_program_id)) = chained_calls.pop_front() { + assert!( + chain_calls_counter <= MAX_NUMBER_CHAINED_CALLS, + "Max chained calls depth is exceeded" + ); + + let Some(program_output) = program_outputs_iter.next() else { + panic!("Insufficient program outputs for chained calls"); + }; + + // Check that instruction data in chained call is the instruction data in program output + assert_eq!( + chained_call.instruction_data, program_output.instruction_data, + "Mismatched instruction data between chained call and program output" + ); + + // Check that `program_output` is consistent with the execution of the corresponding + // program. + let program_output_words = + &to_vec(&program_output).expect("program_output must be serializable"); + env::verify(chained_call.program_id, program_output_words).unwrap_or_else( + |_: Infallible| unreachable!("Infallible error is never constructed"), + ); + + // Check that the program is well behaved. + // See the # Programs section for the definition of the `validate_execution` method. + let execution_valid = validate_execution( + &program_output.pre_states, + &program_output.post_states, + chained_call.program_id, + ); + assert!(execution_valid, "Bad behaved program"); + + for next_call in program_output.chained_calls.iter().rev() { + chained_calls.push_front((next_call.clone(), Some(chained_call.program_id))); + } + + let authorized_pdas = nssa_core::program::compute_authorized_pdas( + caller_program_id, + &chained_call.pda_seeds, + ); + execution_state.validate_and_sync_states( + chained_call.program_id, + authorized_pdas, + program_output.pre_states, + program_output.post_states, + ); + chain_calls_counter += 1; + } + + assert!( + program_outputs_iter.next().is_none(), + "Inner call without a chained call found", + ); + + // Check that all modified uninitialized accounts were claimed + for (account_id, post) in execution_state + .pre_states + .iter() + .filter(|a| a.account.program_owner == DEFAULT_PROGRAM_ID) + .map(|a| { + let post = execution_state + .post_states + .get(&a.account_id) + .expect("Post state must exist for pre state"); + (a, post) + }) + .filter(|(pre_default, post)| pre_default.account != **post) + .map(|(pre, post)| (pre.account_id, post)) + { + assert_ne!( + post.program_owner, DEFAULT_PROGRAM_ID, + "Account {account_id:?} was modified but not claimed" + ); + } + + execution_state + } + + /// Validate program pre and post states and populate the execution state. + fn validate_and_sync_states( + &mut self, + program_id: ProgramId, + authorized_pdas: HashSet, + pre_states: Vec, + post_states: Vec, + ) { + for (pre, mut post) in pre_states.into_iter().zip(post_states) { + let pre_account_id = pre.account_id; + let post_states_entry = self.post_states.entry(pre.account_id); + match &post_states_entry { + Entry::Occupied(occupied) => { + // Ensure that new pre state is the same as known post state + assert_eq!( + occupied.get(), + &pre.account, + "Inconsistent pre state for account {pre_account_id:?}", + ); + + let previous_is_authorized = self + .pre_states + .iter() + .find(|acc| acc.account_id == pre_account_id) + .map(|acc| acc.is_authorized) + .unwrap_or_else(|| { + panic!( + "Pre state must exist in execution state for account {pre_account_id:?}", + ) + }); + + let is_authorized = + previous_is_authorized || authorized_pdas.contains(&pre_account_id); + + assert_eq!( + pre.is_authorized, is_authorized, + "Inconsistent authorization for account {pre_account_id:?}", + ); + } + Entry::Vacant(_) => { + self.pre_states.push(pre); + } + } + + if post.requires_claim() { + // The invoked program can only claim accounts with default program id. + if post.account().program_owner == DEFAULT_PROGRAM_ID { + post.account_mut().program_owner = program_id; + } else { + panic!("Cannot claim an initialized account {pre_account_id:?}"); + } + } + + post_states_entry.insert_entry(post.into_account()); + } + } + + /// Get an iterator over pre and post states of each account involved in the execution. + pub fn into_states_iter( + mut self, + ) -> impl ExactSizeIterator { + self.pre_states.into_iter().map(move |pre| { + let post = self + .post_states + .remove(&pre.account_id) + .expect("Account from pre states should exist in state diff"); + (pre, post) + }) + } +} + +fn compute_circuit_output( + execution_state: ExecutionState, + visibility_mask: &[u8], + private_account_nonces: &[Nonce], + private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], + private_account_nsks: &[NullifierSecretKey], + private_account_membership_proofs: &[Option], +) -> PrivacyPreservingCircuitOutput { + let mut output = PrivacyPreservingCircuitOutput { + public_pre_states: Vec::new(), + public_post_states: Vec::new(), + ciphertexts: Vec::new(), + new_commitments: Vec::new(), + new_nullifiers: Vec::new(), + }; + + let states_iter = execution_state.into_states_iter(); + assert_eq!( + visibility_mask.len(), + states_iter.len(), + "Invalid visibility mask length" + ); + + let mut private_nonces_iter = private_account_nonces.iter(); + let mut private_keys_iter = private_account_keys.iter(); + let mut private_nsks_iter = private_account_nsks.iter(); + let mut private_membership_proofs_iter = private_account_membership_proofs.iter(); + + let mut output_index = 0; + for (visibility_mask, (pre_state, post_state)) in + visibility_mask.iter().copied().zip(states_iter) + { + match visibility_mask { + 0 => { + // Public account + output.public_pre_states.push(pre_state); + output.public_post_states.push(post_state); + } + 1 | 2 => { + let Some((npk, shared_secret)) = private_keys_iter.next() else { + panic!("Missing private account key"); + }; + + assert_eq!( + AccountId::from(npk), + pre_state.account_id, + "AccountId mismatch" + ); + + let new_nullifier = if visibility_mask == 1 { + // Private account with authentication + + let Some(nsk) = private_nsks_iter.next() else { + panic!("Missing private account nullifier secret key"); + }; + + // Verify the nullifier public key + assert_eq!( + npk, + &NullifierPublicKey::from(nsk), + "Nullifier public key mismatch" + ); + + // Check pre_state authorization + assert!( + pre_state.is_authorized, + "Pre-state not authorized for authenticated private account" + ); + + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; + + compute_nullifier_and_set_digest( + membership_proof_opt.as_ref(), + &pre_state.account, + npk, + nsk, + ) + } else { + // Private account without authentication + + assert_eq!( + pre_state.account, + Account::default(), + "Found new private account with non default values", + ); + + assert!( + !pre_state.is_authorized, + "Found new private account marked as authorized." + ); + + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; + + assert!( + membership_proof_opt.is_none(), + "Membership proof must be None for unauthorized accounts" + ); + + let nullifier = Nullifier::for_account_initialization(npk); + (nullifier, DUMMY_COMMITMENT_HASH) + }; + output.new_nullifiers.push(new_nullifier); + + // Update post-state with new nonce + let mut post_with_updated_nonce = post_state; + let Some(new_nonce) = private_nonces_iter.next() else { + panic!("Missing private account nonce"); + }; + post_with_updated_nonce.nonce = *new_nonce; + + // Compute commitment + let commitment_post = Commitment::new(npk, &post_with_updated_nonce); + + // Encrypt and push post state + let encrypted_account = EncryptionScheme::encrypt( + &post_with_updated_nonce, + shared_secret, + &commitment_post, + output_index, + ); + + output.new_commitments.push(commitment_post); + output.ciphertexts.push(encrypted_account); + output_index += 1; + } + _ => panic!("Invalid visibility mask value"), + } + } + + assert!(private_nonces_iter.next().is_none(), "Too many nonces"); + + assert!( + private_keys_iter.next().is_none(), + "Too many private account keys" + ); + + assert!( + private_nsks_iter.next().is_none(), + "Too many private account nullifier secret keys" + ); + + assert!( + private_membership_proofs_iter.next().is_none(), + "Too many private account membership proofs" + ); + + output +} + +fn compute_nullifier_and_set_digest( + membership_proof_opt: Option<&MembershipProof>, + pre_account: &Account, + npk: &NullifierPublicKey, + nsk: &NullifierSecretKey, +) -> (Nullifier, CommitmentSetDigest) { + membership_proof_opt + .as_ref() + .map(|membership_proof| { + // Compute commitment set digest associated with provided auth path + let commitment_pre = Commitment::new(npk, pre_account); + let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); + + // Compute update nullifier + let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); + (nullifier, set_digest) + }) + .unwrap_or_else(|| { + assert_eq!( + *pre_account, + Account::default(), + "Found new private account with non default values" + ); + + // Compute initialization nullifier + let nullifier = Nullifier::for_account_initialization(npk); + (nullifier, DUMMY_COMMITMENT_HASH) + }) +} diff --git a/program_methods/guest/src/bin/token.rs b/program_methods/guest/src/bin/token.rs new file mode 100644 index 00000000..0f7b6287 --- /dev/null +++ b/program_methods/guest/src/bin/token.rs @@ -0,0 +1,2326 @@ +use nssa_core::{ + account::{Account, AccountId, AccountWithMetadata, Data}, + program::{ + AccountPostState, DEFAULT_PROGRAM_ID, ProgramInput, read_nssa_inputs, write_nssa_outputs, + }, +}; + +// The token program has three functions: +// 1. New token definition. Arguments to this function are: +// * Two **default** accounts: [definition_account, holding_account]. The first default account +// will be initialized with the token definition account values. The second account will be +// initialized to a token holding account for the new token, holding the entire total supply. +// * An instruction data of 23-bytes, indicating the total supply and the token name, with the +// following layout: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] The +// name cannot be equal to [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] +// 2. Token transfer Arguments to this function are: +// * Two accounts: [sender_account, recipient_account]. +// * An instruction data byte string of length 23, indicating the total supply with the +// following layout [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 +// || 0x00 || 0x00]. +// 3. Initialize account with zero balance Arguments to this function are: +// * Two accounts: [definition_account, account_to_initialize]. +// * An dummy byte string of length 23, with the following layout [0x02 || 0x00 || 0x00 || 0x00 +// || ... || 0x00 || 0x00]. +// 4. Burn tokens from a Token Holding account (thus lowering total supply) Arguments to this +// function are: +// * Two accounts: [definition_account, holding_account]. +// * Authorization required: holding_account +// * An instruction data byte string of length 23, indicating the balance to burn with the +// folloiwng layout +// [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 5. Mint additional supply of tokens tokens to a Token Holding account (thus increasing total +// supply) Arguments to this function are: +// * Two accounts: [definition_account, holding_account]. +// * Authorization required: definition_account +// * An instruction data byte string of length 23, indicating the balance to mint with the +// folloiwng layout +// [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || 0x00 || 0x00 || 0x00]. +// 6. New token definition with metadata. Arguments to this function are: +// * Three **default** accounts: [definition_account, metadata_account. holding_account]. The +// first default account will be initialized with the token definition account values. The +// second account will be initialized to a token metadata account for the new token +// definition. The third account will be initialized to a token holding account for the new +// token, holding the entire total supply. +// * An instruction data of 474-bytes, indicating the token name, total supply, token standard, +// metadata standard and metadata_values (uri and creators). the following layout: [0x05 || +// total_supply (little-endian 16 bytes) || name (6 bytes) || token_standard || +// metadata_standard || metadata_values] The name cannot be equal to [0x00, 0x00, 0x00, 0x00, +// 0x00, 0x00] +// 7. Print NFT copy from Master NFT Arguments to this function are: +// * Two accounts: [master_nft, printed_account (default)]. +// * Authorization required: master_nft +// * An dummy byte string of length 23, with the following layout [0x06 || 0x00 || 0x00 || 0x00 +// || ... || 0x00 || 0x00]. +const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; +const TOKEN_STANDARD_FUNGIBLE_ASSET: u8 = 1; +const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; +const TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE: u8 = 3; + +const METADATA_TYPE_SIMPLE: u8 = 0; +const METADATA_TYPE_EXPANDED: u8 = 1; + +const TOKEN_DEFINITION_DATA_SIZE: usize = 55; + +const TOKEN_HOLDING_STANDARD: u8 = 1; +const TOKEN_HOLDING_NFT_MASTER: u8 = 2; +const TOKEN_HOLDING_NFT_PRINTED_COPY: u8 = 3; + +const TOKEN_HOLDING_DATA_SIZE: usize = 49; +const CURRENT_VERSION: u8 = 1; + +const TOKEN_METADATA_DATA_SIZE: usize = 463; + +fn is_token_standard_valid(standard: u8) -> bool { + matches!( + standard, + TOKEN_STANDARD_FUNGIBLE_TOKEN + | TOKEN_STANDARD_FUNGIBLE_ASSET + | TOKEN_STANDARD_NONFUNGIBLE + | TOKEN_STANDARD_NONFUNGIBLE_PRINTABLE + ) +} + +fn is_metadata_type_valid(standard: u8) -> bool { + matches!(standard, METADATA_TYPE_SIMPLE | METADATA_TYPE_EXPANDED) +} + +fn is_token_holding_type_valid(standard: u8) -> bool { + matches!(standard, |TOKEN_HOLDING_STANDARD| TOKEN_HOLDING_NFT_MASTER + | TOKEN_HOLDING_NFT_PRINTED_COPY) +} + +struct TokenDefinition { + account_type: u8, + name: [u8; 6], + total_supply: u128, + metadata_id: AccountId, +} + +impl TokenDefinition { + fn into_data(self) -> Data { + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.name); + bytes.extend_from_slice(&self.total_supply.to_le_bytes()); + bytes.extend_from_slice(&self.metadata_id.to_bytes()); + + if bytes.len() != TOKEN_DEFINITION_DATA_SIZE { + panic!("Invalid Token Definition data"); + } + + Data::try_from(bytes).expect("Token definition data size must fit into data") + } + + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_DEFINITION_DATA_SIZE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().expect("Name must be a 6 bytes"); + let total_supply = u128::from_le_bytes( + data[7..23] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let metadata_id = AccountId::new( + data[23..TOKEN_DEFINITION_DATA_SIZE] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); + + let this = Some(Self { + account_type, + name, + total_supply, + metadata_id, + }); + + match account_type { + TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 => None, + TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None, + _ => this, + } + } + } +} + +struct TokenHolding { + account_type: u8, + definition_id: AccountId, + balance: u128, +} + +impl TokenHolding { + fn new(definition_id: &AccountId) -> Self { + Self { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: *definition_id, + balance: 0, + } + } + + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_HOLDING_DATA_SIZE { + return None; + } + + // Check account_type + if !is_token_holding_type_valid(data[0]) { + return None; + } + + let account_type = data[0]; + let definition_id = AccountId::new( + data[1..33] + .try_into() + .expect("Defintion ID must be 32 bytes long"), + ); + let balance = u128::from_le_bytes( + data[33..] + .try_into() + .expect("balance must be 16 bytes little-endian"), + ); + + Some(Self { + definition_id, + balance, + account_type, + }) + } + + fn into_data(self) -> Data { + if !is_token_holding_type_valid(self.account_type) { + panic!("Invalid Token Holding type"); + } + + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&self.definition_id.to_bytes()); + bytes.extend_from_slice(&self.balance.to_le_bytes()); + + if bytes.len() != TOKEN_HOLDING_DATA_SIZE { + panic!("Invalid Token Holding data"); + } + + Data::try_from(bytes).expect("Invalid data") + } +} + +struct TokenMetadata { + account_type: u8, + version: u8, + definition_id: AccountId, + uri: [u8; 200], + creators: [u8; 250], + /// Block id + primary_sale_date: u64, +} + +impl TokenMetadata { + fn into_data(self) -> Data { + if !is_metadata_type_valid(self.account_type) { + panic!("Invalid Metadata type"); + } + + let mut bytes = Vec::::new(); + bytes.extend_from_slice(&[self.account_type]); + bytes.extend_from_slice(&[self.version]); + bytes.extend_from_slice(&self.definition_id.to_bytes()); + bytes.extend_from_slice(&self.uri); + bytes.extend_from_slice(&self.creators); + bytes.extend_from_slice(&self.primary_sale_date.to_le_bytes()); + + if bytes.len() != TOKEN_METADATA_DATA_SIZE { + panic!("Invalid Token Definition data length"); + } + + Data::try_from(bytes).expect("Invalid data") + } +} + +fn transfer(pre_states: &[AccountWithMetadata], balance_to_move: u128) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of input accounts"); + } + let sender = &pre_states[0]; + let recipient = &pre_states[1]; + + if !sender.is_authorized { + panic!("Sender authorization is missing"); + } + + let sender_holding = TokenHolding::parse(&sender.account.data).expect("Invalid sender data"); + + let recipient_holding = if recipient.account == Account::default() { + TokenHolding::new(&sender_holding.definition_id) + } else { + TokenHolding::parse(&recipient.account.data).expect("Invalid recipient data") + }; + + if sender_holding.definition_id != recipient_holding.definition_id { + panic!("Sender and recipient definition id mismatch"); + } + + let (sender_holding, recipient_holding) = + if sender_holding.account_type != TOKEN_HOLDING_NFT_MASTER { + standard_transfer(sender_holding, recipient_holding, balance_to_move) + } else { + nft_master_transfer(sender_holding, recipient_holding, balance_to_move) + }; + + let sender_post = { + let mut this = sender.account.clone(); + this.data = sender_holding.into_data(); + AccountPostState::new(this) + }; + + let recipient_post = { + let mut this = recipient.account.clone(); + this.data = recipient_holding.into_data(); + + // Claim the recipient account if it has default program owner + if this.program_owner == DEFAULT_PROGRAM_ID { + AccountPostState::new_claimed(this) + } else { + AccountPostState::new(this) + } + }; + + vec![sender_post, recipient_post] +} + +fn standard_transfer( + sender_holding: TokenHolding, + recipient_holding: TokenHolding, + balance_to_move: u128, +) -> (TokenHolding, TokenHolding) { + let mut sender_holding = sender_holding; + let mut recipient_holding = recipient_holding; + + if sender_holding.balance < balance_to_move { + panic!("Insufficient balance"); + } + + sender_holding.balance = sender_holding + .balance + .checked_sub(balance_to_move) + .expect("Checked above"); + recipient_holding.balance = recipient_holding + .balance + .checked_add(balance_to_move) + .expect("Recipient balance overflow"); + + recipient_holding.account_type = sender_holding.account_type; + + (sender_holding, recipient_holding) +} + +fn nft_master_transfer( + sender_holding: TokenHolding, + recipient_holding: TokenHolding, + balance_to_move: u128, +) -> (TokenHolding, TokenHolding) { + let mut sender_holding = sender_holding; + let mut recipient_holding = recipient_holding; + + if recipient_holding.balance != 0 { + panic!("Invalid balance in recipient account for NFT transfer"); + } + + if sender_holding.balance != balance_to_move { + panic!("Invalid balance for NFT Master transfer"); + } + + sender_holding.balance = 0; + recipient_holding.balance = balance_to_move; + recipient_holding.account_type = sender_holding.account_type; + + (sender_holding, recipient_holding) +} + +fn new_definition( + pre_states: &[AccountWithMetadata], + name: [u8; 6], + total_supply: u128, +) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of input accounts"); + } + + let definition_target_account = &pre_states[0]; + let holding_target_account = &pre_states[1]; + + if definition_target_account.account != Account::default() { + panic!("Definition target account must have default values"); + } + + if holding_target_account.account != Account::default() { + panic!("Holding target account must have default values"); + } + + let token_definition = TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name, + total_supply, + metadata_id: AccountId::new([0; 32]), + }; + + let token_holding = TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: definition_target_account.account_id, + balance: total_supply, + }; + + let mut definition_target_account_post = definition_target_account.account.clone(); + definition_target_account_post.data = token_definition.into_data(); + + let mut holding_target_account_post = holding_target_account.account.clone(); + holding_target_account_post.data = token_holding.into_data(); + + vec![ + AccountPostState::new_claimed(definition_target_account_post), + AccountPostState::new_claimed(holding_target_account_post), + ] +} + +fn new_definition_with_metadata( + pre_states: &[AccountWithMetadata], + name: [u8; 6], + total_supply: u128, + token_standard: u8, + metadata_standard: u8, + metadata_values: &Data, +) -> Vec { + if pre_states.len() != 3 { + panic!("Invalid number of input accounts"); + } + + let definition_target_account = &pre_states[0]; + let metadata_target_account = &pre_states[1]; + let holding_target_account = &pre_states[2]; + + if definition_target_account.account != Account::default() { + panic!("Definition target account must have default values"); + } + + if metadata_target_account.account != Account::default() { + panic!("Metadata target account must have default values"); + } + + if holding_target_account.account != Account::default() { + panic!("Holding target account must have default values"); + } + + if !is_token_standard_valid(token_standard) { + panic!("Invalid Token Standard provided"); + } + + if !is_metadata_type_valid(metadata_standard) { + panic!("Invalid Metadata Standadard provided"); + } + + if !valid_total_supply_for_token_standard(total_supply, token_standard) { + panic!("Invalid total supply for the specified token supply"); + } + + let token_definition = TokenDefinition { + account_type: token_standard, + name, + total_supply, + metadata_id: metadata_target_account.account_id, + }; + + let token_holding = TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: definition_target_account.account_id, + balance: total_supply, + }; + + if metadata_values.len() != 450 { + panic!("Metadata values data should be 450 bytes"); + } + + let uri: [u8; 200] = metadata_values[0..200] + .try_into() + .expect("Token program expects valid uri for Metadata"); + let creators: [u8; 250] = metadata_values[200..450] + .try_into() + .expect("Token program expects valid creators for Metadata"); + + let token_metadata = TokenMetadata { + account_type: metadata_standard, + version: CURRENT_VERSION, + definition_id: definition_target_account.account_id, + uri, + creators, + primary_sale_date: 0u64, // TODO #261: future works to implement this + }; + + let mut definition_target_account_post = definition_target_account.account.clone(); + definition_target_account_post.data = token_definition.into_data(); + + let mut holding_target_account_post = holding_target_account.account.clone(); + holding_target_account_post.data = token_holding.into_data(); + + let mut metadata_target_account_post = metadata_target_account.account.clone(); + metadata_target_account_post.data = token_metadata.into_data(); + + vec![ + AccountPostState::new_claimed(definition_target_account_post), + AccountPostState::new_claimed(holding_target_account_post), + AccountPostState::new_claimed(metadata_target_account_post), + ] +} + +fn valid_total_supply_for_token_standard(total_supply: u128, token_standard: u8) -> bool { + token_standard != TOKEN_STANDARD_NONFUNGIBLE || total_supply == 1 +} + +fn initialize_account(pre_states: &[AccountWithMetadata]) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let account_to_initialize = &pre_states[1]; + + if account_to_initialize.account != Account::default() { + panic!("Only Uninitialized accounts can be initialized"); + } + + // TODO: #212 We should check that this is an account owned by the token program. + // This check can't be done here since the ID of the program is known only after compiling it + // + // Check definition account is valid + let _definition_values = + TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); + let holding_values = TokenHolding::new(&definition.account_id); + + let definition_post = definition.account.clone(); + let mut account_to_initialize = account_to_initialize.account.clone(); + account_to_initialize.data = holding_values.into_data(); + + vec![ + AccountPostState::new(definition_post), + AccountPostState::new_claimed(account_to_initialize), + ] +} + +fn burn(pre_states: &[AccountWithMetadata], balance_to_burn: u128) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let user_holding = &pre_states[1]; + + if !user_holding.is_authorized { + panic!("Authorization is missing"); + } + + let definition_values = TokenDefinition::parse(&definition.account.data) + .expect("Token Definition account must be valid"); + let user_values = TokenHolding::parse(&user_holding.account.data) + .expect("Token Holding account must be valid"); + + if definition.account_id != user_values.definition_id { + panic!("Mismatch Token Definition and Token Holding"); + } + + if user_values.balance < balance_to_burn { + panic!("Insufficient balance to burn"); + } + + let mut post_user_holding = user_holding.account.clone(); + let mut post_definition = definition.account.clone(); + + post_user_holding.data = TokenHolding::into_data(TokenHolding { + account_type: user_values.account_type, + definition_id: user_values.definition_id, + balance: user_values + .balance + .checked_sub(balance_to_burn) + .expect("Checked above"), + }); + + post_definition.data = TokenDefinition::into_data(TokenDefinition { + account_type: definition_values.account_type, + name: definition_values.name, + total_supply: definition_values + .total_supply + .checked_sub(balance_to_burn) + .expect("Total supply underflow"), + metadata_id: definition_values.metadata_id, + }); + + vec![ + AccountPostState::new(post_definition), + AccountPostState::new(post_user_holding), + ] +} + +fn is_mintable(account_type: u8) -> bool { + account_type != TOKEN_STANDARD_NONFUNGIBLE +} + +fn mint_additional_supply( + pre_states: &[AccountWithMetadata], + amount_to_mint: u128, +) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let definition = &pre_states[0]; + let token_holding = &pre_states[1]; + + if !definition.is_authorized { + panic!("Definition authorization is missing"); + } + + let definition_values = + TokenDefinition::parse(&definition.account.data).expect("Definition account must be valid"); + + let token_holding_values: TokenHolding = if token_holding.account == Account::default() { + TokenHolding::new(&definition.account_id) + } else { + TokenHolding::parse(&token_holding.account.data).expect("Holding account must be valid") + }; + + if !is_mintable(definition_values.account_type) { + panic!("Token Definition's standard does not permit minting additional supply"); + } + + if definition.account_id != token_holding_values.definition_id { + panic!("Mismatch Token Definition and Token Holding"); + } + + let token_holding_post_data = TokenHolding { + account_type: token_holding_values.account_type, + definition_id: token_holding_values.definition_id, + balance: token_holding_values + .balance + .checked_add(amount_to_mint) + .expect("New balance overflow"), + }; + + let post_total_supply = definition_values + .total_supply + .checked_add(amount_to_mint) + .expect("Total supply overflow"); + + let post_definition_data = TokenDefinition { + account_type: definition_values.account_type, + name: definition_values.name, + total_supply: post_total_supply, + metadata_id: definition_values.metadata_id, + }; + + let post_definition = { + let mut this = definition.account.clone(); + this.data = post_definition_data.into_data(); + AccountPostState::new(this) + }; + + let token_holding_post = { + let mut this = token_holding.account.clone(); + this.data = token_holding_post_data.into_data(); + + // Claim the recipient account if it has default program owner + if this.program_owner == DEFAULT_PROGRAM_ID { + AccountPostState::new_claimed(this) + } else { + AccountPostState::new(this) + } + }; + vec![post_definition, token_holding_post] +} + +fn print_nft(pre_states: &[AccountWithMetadata]) -> Vec { + if pre_states.len() != 2 { + panic!("Invalid number of accounts"); + } + + let master_account = &pre_states[0]; + let printed_account = &pre_states[1]; + + if !master_account.is_authorized { + panic!("Master NFT Account must be authorized"); + } + + if printed_account.account != Account::default() { + panic!("Printed Account must be uninitialized"); + } + + let mut master_account_data = + TokenHolding::parse(&master_account.account.data).expect("Invalid Token Holding data"); + + if master_account_data.account_type != TOKEN_HOLDING_NFT_MASTER { + panic!("Invalid Token Holding provided as NFT Master Account"); + } + + if master_account_data.balance < 2 { + panic!("Insufficient balance to print another NFT copy"); + } + + let definition_id = master_account_data.definition_id; + + let post_master_account = { + let mut this = master_account.account.clone(); + master_account_data.balance -= 1; + this.data = master_account_data.into_data(); + AccountPostState::new(this) + }; + + let post_printed_account = { + let mut this = printed_account.account.clone(); + + let printed_data = TokenHolding { + account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, + definition_id, + balance: 1, + }; + + this.data = TokenHolding::into_data(printed_data); + + AccountPostState::new_claimed(this) + }; + + vec![post_master_account, post_printed_account] +} + +type Instruction = Vec; + +fn main() { + let ( + ProgramInput { + pre_states, + instruction, + }, + instruction_words, + ) = read_nssa_inputs::(); + + let post_states = match instruction[0] { + 0 => { + // Parse instruction + let total_supply = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); + assert_ne!(name, [0; 6]); + + // Execute + new_definition(&pre_states, name, total_supply) + } + 1 => { + // Parse instruction + let balance_to_move = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to move must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); + assert_eq!(name, [0; 6]); + + // Execute + transfer(&pre_states, balance_to_move) + } + 2 => { + // Initialize account + if instruction[1..] != [0; 22] { + panic!("Invalid instruction for initialize account"); + } + initialize_account(&pre_states) + } + 3 => { + let balance_to_burn = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to burn must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); + assert_eq!(name, [0; 6]); + + // Execute + burn(&pre_states, balance_to_burn) + } + 4 => { + let balance_to_mint = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Balance to burn must be 16 bytes little-endian"), + ); + let name: [u8; 6] = instruction[17..] + .try_into() + .expect("Name must be 6 bytes long"); + assert_eq!(name, [0; 6]); + + // Execute + mint_additional_supply(&pre_states, balance_to_mint) + } + 5 => { + if instruction.len() != 474 { + panic!("Invalid instruction length") + } + + // Parse instruction + let total_supply = u128::from_le_bytes( + instruction[1..17] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let name = instruction[17..23] + .try_into() + .expect("Name must be 6 bytes long"); + assert_ne!(name, [0; 6]); + let token_standard = instruction[23]; + let metadata_standard = instruction[24]; + let metadata_values: Data = + Data::try_from(instruction[25..474].to_vec()).expect("Invalid metadata"); + + // Execute + new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ) + } + 6 => { + if instruction.len() != 23 { + panic!("Invalid instruction length"); + } + + // Initialize account + if instruction[1..] != [0; 22] { + panic!("Invalid instruction for initialize account"); + } + + print_nft(&pre_states) + } + _ => panic!("Invalid instruction"), + }; + + write_nssa_outputs(instruction_words, pre_states, post_states); +} + +#[cfg(test)] +mod tests { + use nssa_core::account::{Account, AccountId, AccountWithMetadata, Data}; + + use crate::{ + TOKEN_DEFINITION_DATA_SIZE, TOKEN_HOLDING_DATA_SIZE, TOKEN_HOLDING_NFT_MASTER, + TOKEN_HOLDING_NFT_PRINTED_COPY, TOKEN_HOLDING_STANDARD, TOKEN_STANDARD_FUNGIBLE_TOKEN, + TOKEN_STANDARD_NONFUNGIBLE, TokenDefinition, TokenHolding, burn, mint_additional_supply, + new_definition, new_definition_with_metadata, print_nft, transfer, + }; + + struct BalanceForTests; + struct IdForTests; + + struct AccountForTests; + + impl AccountForTests { + fn definition_account_auth() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: BalanceForTests::init_supply(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn definition_account_without_auth() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: BalanceForTests::init_supply(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: false, + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_different_definition() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id_diff(), + balance: BalanceForTests::holding_balance(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn holding_same_definition_with_authorization() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn holding_same_definition_without_authorization() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance(), + }), + nonce: 0, + }, + is_authorized: false, + account_id: IdForTests::holding_id(), + } + } + + fn holding_same_definition_without_authorization_overflow() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), + }), + nonce: 0, + }, + is_authorized: false, + account_id: IdForTests::holding_id(), + } + } + + fn definition_account_post_burn() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: BalanceForTests::init_supply_burned(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_account_post_burn() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance_burned(), + }), + nonce: 0, + }, + is_authorized: false, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_uninit() -> AccountWithMetadata { + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: IdForTests::holding_id_2(), + } + } + + fn init_mint() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::mint_success(), + }), + nonce: 0, + }, + is_authorized: false, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_same_definition_mint() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::holding_balance_mint(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn definition_account_mint() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: BalanceForTests::init_supply_mint(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_same_definition_with_authorization_and_large_balance() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::mint_overflow(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn definition_account_with_authorization_nonfungible() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_NONFUNGIBLE, + name: [2; 6], + total_supply: 1, + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn definition_account_uninit() -> AccountWithMetadata { + AccountWithMetadata { + account: Account::default(), + is_authorized: false, + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_account_init() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn definition_account_unclaimed() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenDefinition::into_data(TokenDefinition { + account_type: TOKEN_STANDARD_FUNGIBLE_TOKEN, + name: [2; 6], + total_supply: BalanceForTests::init_supply(), + metadata_id: AccountId::new([0; 32]), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::pool_definition_id(), + } + } + + fn holding_account_unclaimed() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account2_init() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::init_supply(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id_2(), + } + } + + fn holding_account2_init_post_transfer() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::recipient_post_transfer(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id_2(), + } + } + + fn holding_account_init_post_transfer() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_STANDARD, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::sender_post_transfer(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_master_nft() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::printable_copies(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_master_nft_insufficient_balance() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: IdForTests::pool_definition_id(), + balance: 1, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_master_nft_after_print() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::printable_copies() - 1, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_printed_nft() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_PRINTED_COPY, + definition_id: IdForTests::pool_definition_id(), + balance: 1, + }), + nonce: 0, + }, + is_authorized: false, + account_id: IdForTests::holding_id(), + } + } + + fn holding_account_with_master_nft_transferred_to() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [0u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: IdForTests::pool_definition_id(), + balance: BalanceForTests::printable_copies(), + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id_2(), + } + } + + fn holding_account_master_nft_post_transfer() -> AccountWithMetadata { + AccountWithMetadata { + account: Account { + program_owner: [5u32; 8], + balance: 0u128, + data: TokenHolding::into_data(TokenHolding { + account_type: TOKEN_HOLDING_NFT_MASTER, + definition_id: IdForTests::pool_definition_id(), + balance: 0, + }), + nonce: 0, + }, + is_authorized: true, + account_id: IdForTests::holding_id(), + } + } + } + + impl BalanceForTests { + fn init_supply() -> u128 { + 100_000 + } + + fn holding_balance() -> u128 { + 1_000 + } + + fn init_supply_burned() -> u128 { + 99_500 + } + + fn holding_balance_burned() -> u128 { + 500 + } + + fn burn_success() -> u128 { + 500 + } + + fn burn_insufficient() -> u128 { + 1_500 + } + + fn mint_success() -> u128 { + 50_000 + } + + fn holding_balance_mint() -> u128 { + 51_000 + } + + fn mint_overflow() -> u128 { + u128::MAX - 40_000 + } + + fn init_supply_mint() -> u128 { + 150_000 + } + + fn sender_post_transfer() -> u128 { + 95_000 + } + + fn recipient_post_transfer() -> u128 { + 105_000 + } + + fn transfer_amount() -> u128 { + 5_000 + } + + fn printable_copies() -> u128 { + 10 + } + } + + impl IdForTests { + fn pool_definition_id() -> AccountId { + AccountId::new([15; 32]) + } + + fn pool_definition_id_diff() -> AccountId { + AccountId::new([16; 32]) + } + + fn holding_id() -> AccountId { + AccountId::new([17; 32]) + } + + fn holding_id_2() -> AccountId { + AccountId::new([42; 32]) + } + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_1() { + let pre_states = vec![AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }]; + let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_with_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); + } + + #[should_panic(expected = "Definition target account must have default values")] + #[test] + fn test_new_definition_non_default_first_account_should_fail() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); + } + + #[should_panic(expected = "Holding target account must have default values")] + #[test] + fn test_new_definition_non_default_second_account_should_fail() { + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = new_definition(&pre_states, [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe], 10); + } + + #[test] + fn test_new_definition_with_valid_inputs_succeeds() { + let pre_states = vec![ + AccountForTests::definition_account_uninit(), + AccountForTests::holding_account_uninit(), + ]; + + let post_states = new_definition(&pre_states, [2u8; 6], BalanceForTests::init_supply()); + + let [definition_account, holding_account] = post_states.try_into().ok().unwrap(); + assert!( + *definition_account.account() + == AccountForTests::definition_account_unclaimed().account + ); + + assert!(*holding_account.account() == AccountForTests::holding_account_unclaimed().account); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_transfer_with_invalid_number_of_accounts_1() { + let pre_states = vec![AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }]; + let _post_states = transfer(&pre_states, 10); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_transfer_with_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 10); + } + + #[should_panic(expected = "Invalid sender data")] + #[test] + fn test_transfer_invalid_instruction_type_should_fail() { + let invalid_type = TOKEN_HOLDING_STANDARD ^ 1; + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // First byte should be `TOKEN_HOLDING_STANDARD` for token holding accounts + data: Data::try_from(vec![invalid_type; TOKEN_HOLDING_DATA_SIZE]) + .expect("Invalid data"), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 10); + } + + #[should_panic(expected = "Invalid sender data")] + #[test] + fn test_transfer_invalid_data_size_should_fail_1() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 10); + } + + #[should_panic(expected = "Invalid sender data")] + #[test] + fn test_transfer_invalid_data_size_should_fail_2() { + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Data must be of exact length `TOKEN_HOLDING_DATA_SIZE` + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 10); + } + + #[should_panic(expected = "Sender and recipient definition id mismatch")] + #[test] + fn test_transfer_with_different_definition_ids_should_fail() { + let pre_states = vec![ + AccountForTests::holding_same_definition_with_authorization(), + AccountForTests::holding_different_definition(), + ]; + let _post_states = transfer(&pre_states, 10); + } + + #[should_panic(expected = "Insufficient balance")] + #[test] + fn test_transfer_with_insufficient_balance_should_fail() { + let pre_states = vec![ + AccountForTests::holding_same_definition_with_authorization(), + AccountForTests::holding_account_same_definition_mint(), + ]; + // Attempt to transfer 38 tokens + let _post_states = transfer(&pre_states, BalanceForTests::burn_insufficient()); + } + + #[should_panic(expected = "Sender authorization is missing")] + #[test] + fn test_transfer_without_sender_authorization_should_fail() { + let mut def_data = Vec::::new(); + def_data.extend_from_slice(&[1; TOKEN_DEFINITION_DATA_SIZE - 16]); + def_data.extend_from_slice(&u128::to_le_bytes(37)); + + let pre_states = vec![ + AccountWithMetadata { + account: Account { + // Account with balance 37 + data: Data::try_from(def_data).unwrap(), + ..Account::default() + }, + is_authorized: false, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account { + data: Data::try_from(vec![1; TOKEN_HOLDING_DATA_SIZE - 1]).unwrap(), + ..Account::default() + }, + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = transfer(&pre_states, 37); + } + + #[test] + fn test_transfer_with_valid_inputs_succeeds() { + let pre_states = vec![ + AccountForTests::holding_account_init(), + AccountForTests::holding_account2_init(), + ]; + let post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() == AccountForTests::holding_account_init_post_transfer().account + ); + assert!( + *recipient_post.account() + == AccountForTests::holding_account2_init_post_transfer().account + ); + } + + #[should_panic(expected = "Invalid balance for NFT Master transfer")] + #[test] + fn test_transfer_with_master_nft_invalid_balance() { + let pre_states = vec![ + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_uninit(), + ]; + let _post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); + } + + #[should_panic(expected = "Invalid balance in recipient account for NFT transfer")] + #[test] + fn test_transfer_with_master_nft_invalid_recipient_balance() { + let pre_states = vec![ + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_with_master_nft_transferred_to(), + ]; + let _post_states = transfer(&pre_states, BalanceForTests::printable_copies()); + } + + #[test] + fn test_transfer_with_master_nft_success() { + let pre_states = vec![ + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_uninit(), + ]; + let post_states = transfer(&pre_states, BalanceForTests::printable_copies()); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() + == AccountForTests::holding_account_master_nft_post_transfer().account + ); + assert!( + *recipient_post.account() + == AccountForTests::holding_account_with_master_nft_transferred_to().account + ); + } + + #[test] + fn test_token_initialize_account_succeeds() { + let pre_states = vec![ + AccountForTests::holding_account_init(), + AccountForTests::holding_account2_init(), + ]; + let post_states = transfer(&pre_states, BalanceForTests::transfer_amount()); + let [sender_post, recipient_post] = post_states.try_into().ok().unwrap(); + + assert!( + *sender_post.account() == AccountForTests::holding_account_init_post_transfer().account + ); + assert!( + *recipient_post.account() + == AccountForTests::holding_account2_init_post_transfer().account + ); + } + + #[test] + #[should_panic(expected = "Invalid number of accounts")] + fn test_burn_invalid_number_of_accounts() { + let pre_states = vec![AccountForTests::definition_account_auth()]; + let _post_states = burn(&pre_states, BalanceForTests::burn_success()); + } + + #[test] + #[should_panic(expected = "Mismatch Token Definition and Token Holding")] + fn test_burn_mismatch_def() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_different_definition(), + ]; + let _post_states = burn(&pre_states, BalanceForTests::burn_success()); + } + + #[test] + #[should_panic(expected = "Authorization is missing")] + fn test_burn_missing_authorization() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization(), + ]; + let _post_states = burn(&pre_states, BalanceForTests::burn_success()); + } + + #[test] + #[should_panic(expected = "Insufficient balance to burn")] + fn test_burn_insufficient_balance() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_with_authorization(), + ]; + let _post_states = burn(&pre_states, BalanceForTests::burn_insufficient()); + } + + #[test] + #[should_panic(expected = "Total supply underflow")] + fn test_burn_total_supply_underflow() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_with_authorization_and_large_balance(), + ]; + let _post_states = burn(&pre_states, BalanceForTests::mint_overflow()); + } + + #[test] + fn test_burn_success() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_with_authorization(), + ]; + let post_states = burn(&pre_states, BalanceForTests::burn_success()); + + let def_post = post_states[0].clone(); + let holding_post = post_states[1].clone(); + + assert!(*def_post.account() == AccountForTests::definition_account_post_burn().account); + assert!(*holding_post.account() == AccountForTests::holding_account_post_burn().account); + } + + #[test] + #[should_panic(expected = "Invalid number of accounts")] + fn test_mint_invalid_number_of_accounts_1() { + let pre_states = vec![AccountForTests::definition_account_auth()]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + } + + #[test] + #[should_panic(expected = "Invalid number of accounts")] + fn test_mint_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_same_definition_mint(), + AccountForTests::holding_same_definition_with_authorization(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + } + + #[test] + #[should_panic(expected = "Holding account must be valid")] + fn test_mint_not_valid_holding_account() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::definition_account_without_auth(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + } + + #[test] + #[should_panic(expected = "Definition account must be valid")] + fn test_mint_not_valid_definition_account() { + let pre_states = vec![ + AccountForTests::holding_same_definition_with_authorization(), + AccountForTests::holding_same_definition_without_authorization(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + } + + #[test] + #[should_panic(expected = "Definition authorization is missing")] + fn test_mint_missing_authorization() { + let pre_states = vec![ + AccountForTests::definition_account_without_auth(), + AccountForTests::holding_same_definition_without_authorization(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + } + + #[test] + #[should_panic(expected = "Mismatch Token Definition and Token Holding")] + fn test_mint_mismatched_token_definition() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_different_definition(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + } + + #[test] + fn test_mint_success() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization(), + ]; + let post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + + let def_post = post_states[0].clone(); + let holding_post = post_states[1].clone(); + + assert!(*def_post.account() == AccountForTests::definition_account_mint().account); + assert!( + *holding_post.account() + == AccountForTests::holding_account_same_definition_mint().account + ); + } + + #[test] + fn test_mint_uninit_holding_success() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_uninit(), + ]; + let post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + + let def_post = post_states[0].clone(); + let holding_post = post_states[1].clone(); + + assert!(*def_post.account() == AccountForTests::definition_account_mint().account); + assert!(*holding_post.account() == AccountForTests::init_mint().account); + assert!(holding_post.requires_claim()); + } + + #[test] + #[should_panic(expected = "Total supply overflow")] + fn test_mint_total_supply_overflow() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_overflow()); + } + + #[test] + #[should_panic(expected = "New balance overflow")] + fn test_mint_holding_account_overflow() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_same_definition_without_authorization_overflow(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_overflow()); + } + + #[test] + #[should_panic( + expected = "Token Definition's standard does not permit minting additional supply" + )] + fn test_mint_cannot_mint_unmintable_tokens() { + let pre_states = vec![ + AccountForTests::definition_account_with_authorization_nonfungible(), + AccountForTests::holding_same_definition_without_authorization(), + ]; + let _post_states = mint_additional_supply(&pre_states, BalanceForTests::mint_success()); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_1() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_2() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid number of input accounts")] + #[test] + fn test_call_new_definition_metadata_with_invalid_number_of_accounts_3() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([4; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Definition target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_definition() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Metadata target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_metadata() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountForTests::holding_account_same_definition_mint(), + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Holding target account must have default values")] + #[test] + fn test_call_new_definition_metadata_with_init_holding() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountForTests::holding_account_same_definition_mint(), + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Metadata values data should be 450 bytes")] + #[test] + fn test_call_new_definition_metadata_with_too_short_metadata_length() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 449].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Metadata values data should be 450 bytes")] + #[test] + fn test_call_new_definition_metadata_with_too_long_metadata_length() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 451].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid Token Standard provided")] + #[test] + fn test_call_new_definition_metadata_with_invalid_token_standard() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 14u8; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid Metadata Standadard provided")] + #[test] + fn test_call_new_definition_metadata_with_invalid_metadata_standard() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = 0u8; + let metadata_standard = 14u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid total supply for the specified token supply")] + #[test] + fn test_call_new_definition_metadata_invalid_supply_for_nonfungible() { + let name = [0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe]; + let total_supply = 15u128; + let token_standard = TOKEN_STANDARD_NONFUNGIBLE; + let metadata_standard = 0u8; + let metadata_values: Data = Data::try_from([1u8; 450].to_vec()).unwrap(); + + let pre_states = vec![ + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([1; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([2; 32]), + }, + AccountWithMetadata { + account: Account::default(), + is_authorized: true, + account_id: AccountId::new([3; 32]), + }, + ]; + let _post_states = new_definition_with_metadata( + &pre_states, + name, + total_supply, + token_standard, + metadata_standard, + &metadata_values, + ); + } + + #[should_panic(expected = "Invalid number of accounts")] + #[test] + fn test_print_nft_invalid_number_of_accounts_1() { + let pre_states = vec![AccountForTests::holding_account_master_nft()]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid number of accounts")] + #[test] + fn test_print_nft_invalid_number_of_accounts_2() { + let pre_states = vec![ + AccountForTests::holding_account_master_nft(), + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_uninit(), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Master NFT Account must be authorized")] + #[test] + fn test_print_nft_master_account_must_be_authorized() { + let pre_states = vec![ + AccountForTests::holding_account_uninit(), + AccountForTests::holding_account_uninit(), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Printed Account must be uninitialized")] + #[test] + fn test_print_nft_print_account_initialized() { + let pre_states = vec![ + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_init(), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid Token Holding data")] + #[test] + fn test_print_nft_master_nft_invalid_token_holding() { + let pre_states = vec![ + AccountForTests::definition_account_auth(), + AccountForTests::holding_account_uninit(), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Invalid Token Holding provided as NFT Master Account")] + #[test] + fn test_print_nft_master_nft_not_nft_master_account() { + let pre_states = vec![ + AccountForTests::holding_account_init(), + AccountForTests::holding_account_uninit(), + ]; + let _post_states = print_nft(&pre_states); + } + + #[should_panic(expected = "Insufficient balance to print another NFT copy")] + #[test] + fn test_print_nft_master_nft_insufficient_balance() { + let pre_states = vec![ + AccountForTests::holding_account_master_nft_insufficient_balance(), + AccountForTests::holding_account_uninit(), + ]; + let _post_states = print_nft(&pre_states); + } + + #[test] + fn test_print_nft_success() { + let pre_states = vec![ + AccountForTests::holding_account_master_nft(), + AccountForTests::holding_account_uninit(), + ]; + let post_states = print_nft(&pre_states); + + let post_master_nft = post_states[0].account(); + let post_printed = post_states[1].account(); + + assert!( + *post_master_nft == AccountForTests::holding_account_master_nft_after_print().account + ); + assert!(*post_printed == AccountForTests::holding_account_printed_nft().account); + } +} diff --git a/nssa/program_methods/src/lib.rs b/program_methods/src/lib.rs similarity index 100% rename from nssa/program_methods/src/lib.rs rename to program_methods/src/lib.rs diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 32c263c7..a844c524 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -4,26 +4,19 @@ version = "0.1.0" edition = "2024" [dependencies] +nssa.workspace = true +nssa_core.workspace = true +common.workspace = true +storage.workspace = true +mempool.workspace = true + base58.workspace = true anyhow.workspace = true serde.workspace = true -rand.workspace = true +serde_json.workspace = true tempfile.workspace = true chrono.workspace = true log.workspace = true -nssa-core = { path = "../nssa/core", features = ["host"] } - -[dependencies.storage] -path = "../storage" - -[dependencies.mempool] -path = "../mempool" - -[dependencies.common] -path = "../common" - -[dependencies.nssa] -path = "../nssa" [features] default = [] diff --git a/sequencer_core/src/config.rs b/sequencer_core/src/config.rs index 2f4ee3be..4ef08803 100644 --- a/sequencer_core/src/config.rs +++ b/sequencer_core/src/config.rs @@ -1,5 +1,10 @@ -use std::path::PathBuf; +use std::{ + fs::File, + io::BufReader, + path::{Path, PathBuf}, +}; +use anyhow::Result; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] @@ -43,3 +48,12 @@ pub struct SequencerConfig { /// Sequencer own signing key pub signing_key: [u8; 32], } + +impl SequencerConfig { + pub fn from_path(config_home: &Path) -> Result { + let file = File::open(config_home)?; + let reader = BufReader::new(file); + + Ok(serde_json::from_reader(reader)?) + } +} diff --git a/sequencer_rpc/Cargo.toml b/sequencer_rpc/Cargo.toml index 395660f1..2abd5400 100644 --- a/sequencer_rpc/Cargo.toml +++ b/sequencer_rpc/Cargo.toml @@ -4,6 +4,11 @@ version = "0.1.0" edition = "2024" [dependencies] +nssa.workspace = true +common.workspace = true +mempool.workspace = true +sequencer_core.workspace = true + anyhow.workspace = true serde_json.workspace = true log.workspace = true @@ -11,25 +16,10 @@ serde.workspace = true actix-cors.workspace = true futures.workspace = true base58.workspace = true -hex = "0.4.3" +hex.workspace = true tempfile.workspace = true base64.workspace = true itertools.workspace = true - actix-web.workspace = true tokio.workspace = true borsh.workspace = true - -# TODO: Move to workspace - -[dependencies.sequencer_core] -path = "../sequencer_core" - -[dependencies.common] -path = "../common" - -[dependencies.nssa] -path = "../nssa" - -[dependencies.mempool] -path = "../mempool" diff --git a/sequencer_rpc/src/net_utils.rs b/sequencer_rpc/src/net_utils.rs index 33eacae7..8b9b7e64 100644 --- a/sequencer_rpc/src/net_utils.rs +++ b/sequencer_rpc/src/net_utils.rs @@ -1,4 +1,4 @@ -use std::{io, sync::Arc}; +use std::{io, net::SocketAddr, sync::Arc}; use actix_cors::Cors; use actix_web::{App, Error as HttpError, HttpResponse, HttpServer, http, middleware, web}; @@ -42,25 +42,24 @@ fn get_cors(cors_allowed_origins: &[String]) -> Cors { .max_age(3600) } -#[allow(clippy::too_many_arguments)] pub fn new_http_server( config: RpcConfig, seuquencer_core: Arc>, mempool_handle: MemPoolHandle, -) -> io::Result { +) -> io::Result<(actix_web::dev::Server, SocketAddr)> { let RpcConfig { addr, cors_allowed_origins, limits_config, } = config; - info!(target:NETWORK, "Starting http server at {addr}"); + info!(target:NETWORK, "Starting HTTP server at {addr}"); let handler = web::Data::new(JsonHandler { sequencer_state: seuquencer_core.clone(), mempool_handle, }); // HTTP server - Ok(HttpServer::new(move || { + let http_server = HttpServer::new(move || { App::new() .wrap(get_cors(&cors_allowed_origins)) .app_data(handler.clone()) @@ -70,6 +69,14 @@ pub fn new_http_server( }) .bind(addr)? .shutdown_timeout(SHUTDOWN_TIMEOUT_SECS) - .disable_signals() - .run()) + .disable_signals(); + + let [addr] = http_server + .addrs() + .try_into() + .expect("Exactly one address bound is expected for sequencer HTTP server"); + + info!(target:NETWORK, "HTTP server started at {addr}"); + + Ok((http_server.run(), addr)) } diff --git a/sequencer_runner/Cargo.toml b/sequencer_runner/Cargo.toml index 2f105eeb..55f56dec 100644 --- a/sequencer_runner/Cargo.toml +++ b/sequencer_runner/Cargo.toml @@ -4,25 +4,14 @@ version = "0.1.0" edition = "2024" [dependencies] +common.workspace = true +sequencer_core = { workspace = true, features = ["testnet"] } +sequencer_rpc.workspace = true + +clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true -serde_json.workspace = true env_logger.workspace = true log.workspace = true actix.workspace = true - actix-web.workspace = true tokio.workspace = true - -[dependencies.clap] -features = ["derive", "env"] -workspace = true - -[dependencies.sequencer_rpc] -path = "../sequencer_rpc" - -[dependencies.sequencer_core] -path = "../sequencer_core" -features = ["testnet"] - -[dependencies.common] -path = "../common" diff --git a/sequencer_runner/Dockerfile b/sequencer_runner/Dockerfile new file mode 100644 index 00000000..84df3f3c --- /dev/null +++ b/sequencer_runner/Dockerfile @@ -0,0 +1,94 @@ +# Chef stage - uses pre-built cargo-chef image +FROM lukemathwalker/cargo-chef:latest-rust-1.91.1-slim-trixie AS chef + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev \ + libclang-dev \ + clang \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /sequencer_runner + +# Planner stage - generates dependency recipe +FROM chef AS planner +COPY . . +RUN cargo chef prepare --bin sequencer_runner --recipe-path recipe.json + +# Builder stage - builds dependencies and application +FROM chef AS builder +COPY --from=planner /sequencer_runner/recipe.json recipe.json +# Build dependencies only (this layer will be cached) +RUN cargo chef cook --bin sequencer_runner --release --recipe-path recipe.json + +# Copy source code +COPY . . + +# Build the actual application +RUN cargo build --release --bin sequencer_runner + +# Strip debug symbols to reduce binary size +RUN strip /sequencer_runner/target/release/sequencer_runner + +# Install r0vm +RUN curl -L https://risczero.com/install | bash +ENV PATH="/root/.cargo/bin:/root/.risc0/bin:${PATH}" +RUN rzup install +RUN cp "$(which r0vm)" /usr/local/bin/r0vm +RUN test -x /usr/local/bin/r0vm +RUN r0vm --version + +# Runtime stage - minimal image +FROM debian:trixie-slim + +# Install runtime dependencies +RUN apt-get update \ + && apt-get install -y gosu jq \ + && rm -rf /var/lib/apt/lists/* + +# Create non-root user for security +RUN useradd -m -u 1000 -s /bin/bash sequencer_user && \ + mkdir -p /sequencer_runner /etc/sequencer_runner && \ + chown -R sequencer_user:sequencer_user /sequencer_runner /etc/sequencer_runner + +# Copy binary from builder +COPY --from=builder --chown=sequencer_user:sequencer_user /sequencer_runner/target/release/sequencer_runner /usr/local/bin/sequencer_runner + +# Copy r0vm binary from builder +COPY --from=builder --chown=sequencer_user:sequencer_user /usr/local/bin/r0vm /usr/local/bin/r0vm + +# Copy entrypoint script +COPY sequencer_runner/docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh + +# Volume for configuration directory +VOLUME ["/etc/sequencer_runner"] + +# Expose default port +EXPOSE 3040 + +# Health check (TODO #244: Replace when a real health endpoint is available) +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl http://localhost:3040 \ + -H "Content-Type: application/json" \ + -d "{ \ + \"jsonrpc\": \"2.0\", \ + \"method\": \"hello\", \ + \"params\": {}, \ + \"id\": 1 \ + }" || exit 1 + +# Run the application +ENV RUST_LOG=info + +# Set explicit location for r0vm binary +ENV RISC0_SERVER_PATH=/usr/local/bin/r0vm + +USER root + +ENTRYPOINT ["/docker-entrypoint.sh"] + +WORKDIR /sequencer_runner +CMD ["sequencer_runner", "/etc/sequencer_runner"] diff --git a/integration_tests/configs/debug/sequencer/sequencer_config.json b/sequencer_runner/configs/docker/sequencer_config.json similarity index 98% rename from integration_tests/configs/debug/sequencer/sequencer_config.json rename to sequencer_runner/configs/docker/sequencer_config.json index db1c7f20..56101f46 100644 --- a/integration_tests/configs/debug/sequencer/sequencer_config.json +++ b/sequencer_runner/configs/docker/sequencer_config.json @@ -1,5 +1,5 @@ { - "home": "./sequencer", + "home": "/var/lib/sequencer_runner", "override_rust_log": null, "genesis_id": 1, "is_genesis_random": true, diff --git a/sequencer_runner/docker-compose.yml b/sequencer_runner/docker-compose.yml new file mode 100644 index 00000000..5301962c --- /dev/null +++ b/sequencer_runner/docker-compose.yml @@ -0,0 +1,14 @@ +services: + sequencer_runner: + image: lssa/sequencer_runner + build: + context: .. + dockerfile: sequencer_runner/Dockerfile + container_name: sequencer_runner + ports: + - "3040:3040" + volumes: + # Mount configuration folder + - ./configs/docker:/etc/sequencer_runner + # Mount data folder + - ./data:/var/lib/sequencer_runner diff --git a/sequencer_runner/docker-entrypoint.sh b/sequencer_runner/docker-entrypoint.sh new file mode 100644 index 00000000..fb117131 --- /dev/null +++ b/sequencer_runner/docker-entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# This is an entrypoint script for the sequencer_runner Docker container, +# it's not meant to be executed outside of the container. + +set -e + +CONFIG="/etc/sequencer_runner/sequencer_config.json" + +# Check config file exists +if [ ! -f "$CONFIG" ]; then + echo "Config file not found: $CONFIG" >&2 + exit 1 +fi + +# Parse home dir +HOME_DIR=$(jq -r '.home' "$CONFIG") + +if [ -z "$HOME_DIR" ] || [ "$HOME_DIR" = "null" ]; then + echo "'home' key missing in config" >&2 + exit 1 +fi + +# Give permissions to the data directory and switch to non-root user +if [ "$(id -u)" = "0" ]; then + mkdir -p "$HOME_DIR" + chown -R sequencer_user:sequencer_user "$HOME_DIR" + exec gosu sequencer_user "$@" +fi diff --git a/sequencer_runner/src/config.rs b/sequencer_runner/src/config.rs deleted file mode 100644 index 58f539bc..00000000 --- a/sequencer_runner/src/config.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::{fs::File, io::BufReader, path::PathBuf}; - -use anyhow::Result; -use sequencer_core::config::SequencerConfig; - -pub fn from_file(config_home: PathBuf) -> Result { - let file = File::open(config_home)?; - let reader = BufReader::new(file); - - Ok(serde_json::from_reader(reader)?) -} diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index e9f4a844..5c1ab920 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, sync::Arc}; +use std::{net::SocketAddr, path::PathBuf, sync::Arc}; use actix_web::dev::ServerHandle; use anyhow::Result; @@ -9,8 +9,6 @@ use sequencer_core::{SequencerCore, config::SequencerConfig}; use sequencer_rpc::new_http_server; use tokio::{sync::Mutex, task::JoinHandle}; -pub mod config; - pub const RUST_LOG: &str = "RUST_LOG"; #[derive(Parser, Debug)] @@ -22,7 +20,7 @@ struct Args { pub async fn startup_sequencer( app_config: SequencerConfig, -) -> Result<(ServerHandle, JoinHandle>)> { +) -> Result<(ServerHandle, SocketAddr, JoinHandle>)> { let block_timeout = app_config.block_create_timeout_millis; let port = app_config.port; @@ -32,7 +30,7 @@ pub async fn startup_sequencer( let seq_core_wrapped = Arc::new(Mutex::new(sequencer_core)); - let http_server = new_http_server( + let (http_server, addr) = new_http_server( RpcConfig::with_port(port), Arc::clone(&seq_core_wrapped), mempool_handle, @@ -61,7 +59,7 @@ pub async fn startup_sequencer( } }); - Ok((http_server_handle, main_loop_handle)) + Ok((http_server_handle, addr, main_loop_handle)) } pub async fn main_runner() -> Result<()> { @@ -70,7 +68,7 @@ pub async fn main_runner() -> Result<()> { let args = Args::parse(); let Args { home_dir } = args; - let app_config = config::from_file(home_dir.join("sequencer_config.json"))?; + let app_config = SequencerConfig::from_path(&home_dir.join("sequencer_config.json"))?; if let Some(ref rust_log) = app_config.override_rust_log { info!("RUST_LOG env var set to {rust_log:?}"); @@ -81,7 +79,7 @@ pub async fn main_runner() -> Result<()> { } // ToDo: Add restart on failures - let (_, main_loop_handle) = startup_sequencer(app_config).await?; + let (_, _, main_loop_handle) = startup_sequencer(app_config).await?; main_loop_handle.await??; diff --git a/storage/Cargo.toml b/storage/Cargo.toml index 2fd4628e..4678560e 100644 --- a/storage/Cargo.toml +++ b/storage/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] +common.workspace = true + thiserror.workspace = true borsh.workspace = true - rocksdb.workspace = true - -[dependencies.common] -path = "../common" diff --git a/nssa/test_program_methods/Cargo.toml b/test_program_methods/Cargo.toml similarity index 63% rename from nssa/test_program_methods/Cargo.toml rename to test_program_methods/Cargo.toml index 0317d2b6..345c479f 100644 --- a/nssa/test_program_methods/Cargo.toml +++ b/test_program_methods/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "test-program-methods" +name = "test_program_methods" version = "0.1.0" edition = "2024" [build-dependencies] -risc0-build = { version = "3.0.3" } +risc0-build.workspace = true [package.metadata.risc0] methods = ["guest"] diff --git a/nssa/test_program_methods/build.rs b/test_program_methods/build.rs similarity index 100% rename from nssa/test_program_methods/build.rs rename to test_program_methods/build.rs diff --git a/test_program_methods/guest/Cargo.toml b/test_program_methods/guest/Cargo.toml new file mode 100644 index 00000000..17613351 --- /dev/null +++ b/test_program_methods/guest/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "test_programs" +version = "0.1.0" +edition = "2024" + +[dependencies] +nssa_core.workspace = true + +risc0-zkvm.workspace = true diff --git a/nssa/test_program_methods/guest/src/bin/burner.rs b/test_program_methods/guest/src/bin/burner.rs similarity index 81% rename from nssa/test_program_methods/guest/src/bin/burner.rs rename to test_program_methods/guest/src/bin/burner.rs index 5deef7cc..3002e39c 100644 --- a/nssa/test_program_methods/guest/src/bin/burner.rs +++ b/test_program_methods/guest/src/bin/burner.rs @@ -20,5 +20,9 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance -= balance_to_burn; - write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ); } diff --git a/nssa/test_program_methods/guest/src/bin/chain_caller.rs b/test_program_methods/guest/src/bin/chain_caller.rs similarity index 91% rename from nssa/test_program_methods/guest/src/bin/chain_caller.rs rename to test_program_methods/guest/src/bin/chain_caller.rs index f2d3cb6f..0cdac8d6 100644 --- a/nssa/test_program_methods/guest/src/bin/chain_caller.rs +++ b/test_program_methods/guest/src/bin/chain_caller.rs @@ -13,9 +13,9 @@ fn main() { let ( ProgramInput { pre_states, - instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed), + instruction: (balance, auth_transfer_id, num_chain_calls, pda_seed), }, - instruction_words + instruction_words, ) = read_nssa_inputs::(); let [recipient_pre, sender_pre] = match pre_states.try_into() { @@ -37,7 +37,7 @@ fn main() { let new_chained_call = ChainedCall { program_id: auth_transfer_id, instruction_data: instruction_data.clone(), - pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], // <- Account order permutation here + pre_states: vec![running_sender_pre.clone(), running_recipient_pre.clone()], /* <- Account order permutation here */ pda_seeds: pda_seed.iter().cloned().collect(), }; chained_calls.push(new_chained_call); diff --git a/test_program_methods/guest/src/bin/changer_claimer.rs b/test_program_methods/guest/src/bin/changer_claimer.rs new file mode 100644 index 00000000..8d28a490 --- /dev/null +++ b/test_program_methods/guest/src/bin/changer_claimer.rs @@ -0,0 +1,38 @@ +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; + +type Instruction = (Option>, bool); + +/// A program that optionally modifies the account data and optionally claims it. +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (data_opt, should_claim), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [pre] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let account_pre = &pre.account; + let mut account_post = account_pre.clone(); + + // Update data if provided + if let Some(data) = data_opt { + account_post.data = data + .try_into() + .expect("provided data should fit into data limit"); + } + + // Claim or not based on the boolean flag + let post_state = if should_claim { + AccountPostState::new_claimed(account_post) + } else { + AccountPostState::new(account_post) + }; + + write_nssa_outputs(instruction_words, vec![pre], vec![post_state]); +} diff --git a/nssa/test_program_methods/guest/src/bin/claimer.rs b/test_program_methods/guest/src/bin/claimer.rs similarity index 100% rename from nssa/test_program_methods/guest/src/bin/claimer.rs rename to test_program_methods/guest/src/bin/claimer.rs diff --git a/nssa/test_program_methods/guest/src/bin/data_changer.rs b/test_program_methods/guest/src/bin/data_changer.rs similarity index 66% rename from nssa/test_program_methods/guest/src/bin/data_changer.rs rename to test_program_methods/guest/src/bin/data_changer.rs index 91544404..cd1cc19d 100644 --- a/nssa/test_program_methods/guest/src/bin/data_changer.rs +++ b/test_program_methods/guest/src/bin/data_changer.rs @@ -4,7 +4,13 @@ type Instruction = Vec; /// A program that modifies the account data by setting bytes sent in instruction. fn main() { - let (ProgramInput { pre_states, instruction: data }, instruction_words) = read_nssa_inputs::(); + let ( + ProgramInput { + pre_states, + instruction: data, + }, + instruction_words, + ) = read_nssa_inputs::(); let [pre] = match pre_states.try_into() { Ok(array) => array, @@ -13,7 +19,9 @@ fn main() { let account_pre = &pre.account; let mut account_post = account_pre.clone(); - account_post.data = data.try_into().expect("provided data should fit into data limit"); + account_post.data = data + .try_into() + .expect("provided data should fit into data limit"); write_nssa_outputs( instruction_words, diff --git a/nssa/test_program_methods/guest/src/bin/extra_output.rs b/test_program_methods/guest/src/bin/extra_output.rs similarity index 100% rename from nssa/test_program_methods/guest/src/bin/extra_output.rs rename to test_program_methods/guest/src/bin/extra_output.rs diff --git a/test_program_methods/guest/src/bin/malicious_authorization_changer.rs b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs new file mode 100644 index 00000000..7dc0ac68 --- /dev/null +++ b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs @@ -0,0 +1,53 @@ +use nssa_core::{ + account::AccountWithMetadata, + program::{ + AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, + }, +}; +use risc0_zkvm::serde::to_vec; + +type Instruction = (u128, ProgramId); + +/// A malicious test program that attempts to change authorization status. +/// It accepts two accounts and executes a native token transfer program via chain call, +/// but sets the `is_authorized` field of the first account to true. +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (balance, transfer_program_id), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [sender, receiver] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + // Maliciously set is_authorized to true for the first account + let authorised_sender = AccountWithMetadata { + is_authorized: true, + ..sender.clone() + }; + + let instruction_data = to_vec(&balance).unwrap(); + + let chained_call = ChainedCall { + program_id: transfer_program_id, + instruction_data, + pre_states: vec![authorised_sender.clone(), receiver.clone()], + pda_seeds: vec![], + }; + + write_nssa_outputs_with_chained_call( + instruction_words, + vec![sender.clone(), receiver.clone()], + vec![ + AccountPostState::new(sender.account), + AccountPostState::new(receiver.account), + ], + vec![chained_call], + ); +} diff --git a/nssa/test_program_methods/guest/src/bin/minter.rs b/test_program_methods/guest/src/bin/minter.rs similarity index 61% rename from nssa/test_program_methods/guest/src/bin/minter.rs rename to test_program_methods/guest/src/bin/minter.rs index 51baa5ec..6bc6855b 100644 --- a/nssa/test_program_methods/guest/src/bin/minter.rs +++ b/test_program_methods/guest/src/bin/minter.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput}; +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; type Instruction = (); @@ -14,5 +14,9 @@ fn main() { let mut account_post = account_pre.clone(); account_post.balance += 1; - write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ); } diff --git a/nssa/test_program_methods/guest/src/bin/missing_output.rs b/test_program_methods/guest/src/bin/missing_output.rs similarity index 100% rename from nssa/test_program_methods/guest/src/bin/missing_output.rs rename to test_program_methods/guest/src/bin/missing_output.rs diff --git a/nssa/test_program_methods/guest/src/bin/modified_transfer.rs b/test_program_methods/guest/src/bin/modified_transfer.rs similarity index 100% rename from nssa/test_program_methods/guest/src/bin/modified_transfer.rs rename to test_program_methods/guest/src/bin/modified_transfer.rs diff --git a/test_program_methods/guest/src/bin/nonce_changer.rs b/test_program_methods/guest/src/bin/nonce_changer.rs new file mode 100644 index 00000000..17aa966a --- /dev/null +++ b/test_program_methods/guest/src/bin/nonce_changer.rs @@ -0,0 +1,22 @@ +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; + +type Instruction = (); + +fn main() { + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); + + let [pre] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let account_pre = &pre.account; + let mut account_post = account_pre.clone(); + account_post.nonce += 1; + + write_nssa_outputs( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ); +} diff --git a/test_program_methods/guest/src/bin/noop.rs b/test_program_methods/guest/src/bin/noop.rs new file mode 100644 index 00000000..79dd1dec --- /dev/null +++ b/test_program_methods/guest/src/bin/noop.rs @@ -0,0 +1,13 @@ +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; + +type Instruction = (); + +fn main() { + let (ProgramInput { pre_states, .. }, instruction_words) = read_nssa_inputs::(); + + let post_states = pre_states + .iter() + .map(|account| AccountPostState::new(account.account.clone())) + .collect(); + write_nssa_outputs(instruction_words, pre_states, post_states); +} diff --git a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs b/test_program_methods/guest/src/bin/program_owner_changer.rs similarity index 63% rename from nssa/test_program_methods/guest/src/bin/program_owner_changer.rs rename to test_program_methods/guest/src/bin/program_owner_changer.rs index 2b212c13..232fa306 100644 --- a/nssa/test_program_methods/guest/src/bin/program_owner_changer.rs +++ b/test_program_methods/guest/src/bin/program_owner_changer.rs @@ -1,4 +1,4 @@ -use nssa_core::program::{read_nssa_inputs, write_nssa_outputs, AccountPostState, ProgramInput}; +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; type Instruction = (); @@ -14,5 +14,9 @@ fn main() { let mut account_post = account_pre.clone(); account_post.program_owner = [0, 1, 2, 3, 4, 5, 6, 7]; - write_nssa_outputs(instruction_words, vec![pre], vec![AccountPostState::new(account_post)]); + write_nssa_outputs( + instruction_words, + vec![pre], + vec![AccountPostState::new(account_post)], + ); } diff --git a/nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs b/test_program_methods/guest/src/bin/simple_balance_transfer.rs similarity index 100% rename from nssa/test_program_methods/guest/src/bin/simple_balance_transfer.rs rename to test_program_methods/guest/src/bin/simple_balance_transfer.rs diff --git a/nssa/test_program_methods/src/lib.rs b/test_program_methods/src/lib.rs similarity index 100% rename from nssa/test_program_methods/src/lib.rs rename to test_program_methods/src/lib.rs diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index c93b357b..bef25007 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -4,32 +4,28 @@ version = "0.1.0" edition = "2024" [dependencies] +nssa_core.workspace = true +nssa.workspace = true +common.workspace = true +key_protocol.workspace = true + anyhow.workspace = true serde_json.workspace = true env_logger.workspace = true log.workspace = true serde.workspace = true -tokio.workspace = true -tempfile.workspace = true +tokio = { workspace = true, features = ["macros"] } clap.workspace = true -nssa-core = { path = "../nssa/core" } base64.workspace = true -bytemuck = "1.23.2" +bytemuck.workspace = true borsh.workspace = true base58.workspace = true -hex = "0.4.3" +hex.workspace = true rand.workspace = true itertools.workspace = true sha2.workspace = true futures.workspace = true +risc0-zkvm.workspace = true async-stream = "0.3.6" indicatif = { version = "0.18.3", features = ["improved_unicode"] } - -[dependencies.key_protocol] -path = "../key_protocol" - -[dependencies.nssa] -path = "../nssa" - -[dependencies.common] -path = "../common" +optfield = "0.4.0" diff --git a/integration_tests/configs/debug/wallet/wallet_config.json b/wallet/configs/debug/wallet_config.json similarity index 100% rename from integration_tests/configs/debug/wallet/wallet_config.json rename to wallet/configs/debug/wallet_config.json diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 480480da..21e59366 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -3,70 +3,15 @@ use base58::ToBase58; use clap::Subcommand; use itertools::Itertools as _; use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use nssa::{Account, AccountId, program::Program}; +use nssa::{Account, PublicKey, program::Program}; use serde::Serialize; use crate::{ - WalletCore, + TokenDefinition, TokenHolding, WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix}, }; -const TOKEN_DEFINITION_TYPE: u8 = 0; -const TOKEN_DEFINITION_DATA_SIZE: usize = 23; - -const TOKEN_HOLDING_TYPE: u8 = 1; -const TOKEN_HOLDING_DATA_SIZE: usize = 49; - -struct TokenDefinition { - #[allow(unused)] - account_type: u8, - name: [u8; 6], - total_supply: u128, -} - -struct TokenHolding { - #[allow(unused)] - account_type: u8, - definition_id: AccountId, - balance: u128, -} - -impl TokenDefinition { - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE { - None - } else { - let account_type = data[0]; - let name = data[1..7].try_into().unwrap(); - let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap()); - - Some(Self { - account_type, - name, - total_supply, - }) - } - } -} - -impl TokenHolding { - fn parse(data: &[u8]) -> Option { - if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { - None - } else { - let account_type = data[0]; - let definition_id = AccountId::new(data[1..33].try_into().unwrap()); - let balance = u128::from_le_bytes(data[33..].try_into().unwrap()); - Some(Self { - definition_id, - balance, - account_type, - }) - } - } -} - /// Represents generic chain CLI subcommand #[derive(Subcommand, Debug, Clone)] pub enum AccountSubcommand { @@ -75,6 +20,9 @@ pub enum AccountSubcommand { /// Flag to get raw account data #[arg(short, long)] raw: bool, + /// Display keys (pk for public accounts, npk/ipk for private accounts) + #[arg(short, long)] + keys: bool, /// Valid 32 byte base58 string with privacy prefix #[arg(short, long)] account_id: String, @@ -86,7 +34,11 @@ pub enum AccountSubcommand { SyncPrivate {}, /// List all accounts owned by the wallet #[command(visible_alias = "ls")] - List {}, + List { + /// Show detailed account information (like `account get`) + #[arg(short, long)] + long: bool, + }, } /// Represents generic register CLI subcommand @@ -115,13 +67,20 @@ impl WalletSubcommand for NewSubcommand { NewSubcommand::Public { cci } => { let (account_id, chain_index) = wallet_core.create_new_account_public(cci); + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .unwrap(); + + let public_key = PublicKey::new_from_private_key(private_key); + println!( "Generated new account with account_id Public/{account_id} at path {chain_index}" ); + println!("With pk {}", hex::encode(public_key.value())); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::RegisterAccount { account_id }) } @@ -144,9 +103,7 @@ impl WalletSubcommand for NewSubcommand { hex::encode(key.incoming_viewing_public_key.to_bytes()) ); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::RegisterAccount { account_id }) } @@ -206,13 +163,61 @@ impl From for TokedHoldingAccountView { } } +/// 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(); + + match &account.program_owner { + _ if account.program_owner == auth_tr_prog_id => { + let acc_view: AuthenticatedTransferAccountView = account.clone().into(); + ( + "Account owned by authenticated transfer program".to_string(), + serde_json::to_string(&acc_view).unwrap(), + ) + } + _ if account.program_owner == token_prog_id => { + if let Some(token_def) = TokenDefinition::parse(&account.data) { + let acc_view: TokedDefinitionAccountView = token_def.into(); + ( + "Definition account owned by token program".to_string(), + serde_json::to_string(&acc_view).unwrap(), + ) + } else if let Some(token_hold) = TokenHolding::parse(&account.data) { + let acc_view: TokedHoldingAccountView = token_hold.into(); + ( + "Holding account owned by token program".to_string(), + serde_json::to_string(&acc_view).unwrap(), + ) + } else { + let account_hr: HumanReadableAccount = account.clone().into(); + ( + "Unknown token program account".to_string(), + serde_json::to_string(&account_hr).unwrap(), + ) + } + } + _ => { + let account_hr: HumanReadableAccount = account.clone().into(); + ( + "Account".to_string(), + serde_json::to_string(&account_hr).unwrap(), + ) + } + } +} + impl WalletSubcommand for AccountSubcommand { async fn handle_subcommand( self, wallet_core: &mut WalletCore, ) -> Result { match self { - AccountSubcommand::Get { raw, account_id } => { + AccountSubcommand::Get { + raw, + keys, + account_id, + } => { let (account_id, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; let account_id = account_id.parse()?; @@ -226,9 +231,43 @@ impl WalletSubcommand for AccountSubcommand { .ok_or(anyhow::anyhow!("Private account not found in storage"))?, }; + // Helper closure to display keys for the account + let display_keys = |wallet_core: &WalletCore| -> Result<()> { + match addr_kind { + AccountPrivacyKind::Public => { + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .ok_or(anyhow::anyhow!("Public account not found in storage"))?; + + let public_key = PublicKey::new_from_private_key(private_key); + println!("pk {}", hex::encode(public_key.value())); + } + AccountPrivacyKind::Private => { + let (key, _) = wallet_core + .storage + .user_data + .get_private_account(&account_id) + .ok_or(anyhow::anyhow!("Private account not found in storage"))?; + + println!("npk {}", hex::encode(key.nullifer_public_key.0)); + println!( + "ipk {}", + hex::encode(key.incoming_viewing_public_key.to_bytes()) + ); + } + } + Ok(()) + }; + if account == Account::default() { println!("Account is Uninitialized"); + if keys { + display_keys(wallet_core)?; + } + return Ok(SubcommandReturnValue::Empty); } @@ -239,43 +278,13 @@ impl WalletSubcommand for AccountSubcommand { return Ok(SubcommandReturnValue::Empty); } - let auth_tr_prog_id = Program::authenticated_transfer_program().id(); - let token_prog_id = Program::token().id(); + let (description, json_view) = format_account_details(&account); + println!("{description}"); + println!("{json_view}"); - let acc_view = match &account.program_owner { - _ if account.program_owner == auth_tr_prog_id => { - let acc_view: AuthenticatedTransferAccountView = account.into(); - - println!("Account owned by authenticated transfer program"); - - serde_json::to_string(&acc_view)? - } - _ if account.program_owner == token_prog_id => { - if let Some(token_def) = TokenDefinition::parse(&account.data) { - let acc_view: TokedDefinitionAccountView = token_def.into(); - - println!("Definition account owned by token program"); - - serde_json::to_string(&acc_view)? - } else if let Some(token_hold) = TokenHolding::parse(&account.data) { - let acc_view: TokedHoldingAccountView = token_hold.into(); - - println!("Holding account owned by token program"); - - serde_json::to_string(&acc_view)? - } else { - anyhow::bail!( - "Invalid data for account {account_id:#?} with token program" - ); - } - } - _ => { - let account_hr: HumanReadableAccount = account.clone().into(); - serde_json::to_string(&account_hr).unwrap() - } - }; - - println!("{}", acc_view); + if keys { + display_keys(wallet_core)?; + } Ok(SubcommandReturnValue::Empty) } @@ -298,44 +307,104 @@ impl WalletSubcommand for AccountSubcommand { { wallet_core.last_synced_block = curr_last_block; - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent data at {path:#?}"); + wallet_core.store_persistent_data().await?; } else { wallet_core.sync_to_block(curr_last_block).await?; } Ok(SubcommandReturnValue::SyncedToBlock(curr_last_block)) } - AccountSubcommand::List {} => { + AccountSubcommand::List { long } => { let user_data = &wallet_core.storage.user_data; - let accounts = user_data - .default_pub_account_signing_keys - .keys() - .map(|id| format!("Preconfigured Public/{id}")) - .chain( - user_data - .default_user_private_accounts - .keys() - .map(|id| format!("Preconfigured Private/{id}")), - ) - .chain( - user_data - .public_key_tree - .account_id_map - .iter() - .map(|(id, chain_index)| format!("{chain_index} Public/{id}")), - ) - .chain( - user_data - .private_key_tree - .account_id_map - .iter() - .map(|(id, chain_index)| format!("{chain_index} Private/{id}")), - ) - .format(",\n"); - println!("{accounts}"); + if !long { + let accounts = user_data + .default_pub_account_signing_keys + .keys() + .map(|id| format!("Preconfigured Public/{id}")) + .chain( + user_data + .default_user_private_accounts + .keys() + .map(|id| format!("Preconfigured Private/{id}")), + ) + .chain( + user_data + .public_key_tree + .account_id_map + .iter() + .map(|(id, chain_index)| format!("{chain_index} Public/{id}")), + ) + .chain( + user_data + .private_key_tree + .account_id_map + .iter() + .map(|(id, chain_index)| format!("{chain_index} Private/{id}")), + ) + .format("\n"); + + println!("{accounts}"); + return Ok(SubcommandReturnValue::Empty); + } + + // Detailed listing with --long flag + // Preconfigured public accounts + for id in user_data.default_pub_account_signing_keys.keys() { + println!("Preconfigured Public/{id}"); + match wallet_core.get_account_public(*id).await { + Ok(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Ok(_) => println!(" Uninitialized"), + Err(e) => println!(" Error fetching account: {e}"), + } + } + + // Preconfigured private accounts + for id in user_data.default_user_private_accounts.keys() { + println!("Preconfigured Private/{id}"); + match wallet_core.get_account_private(id) { + Some(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Some(_) => println!(" Uninitialized"), + None => println!(" Not found in local storage"), + } + } + + // Public key tree accounts + for (id, chain_index) in user_data.public_key_tree.account_id_map.iter() { + println!("{chain_index} Public/{id}"); + match wallet_core.get_account_public(*id).await { + Ok(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Ok(_) => println!(" Uninitialized"), + Err(e) => println!(" Error fetching account: {e}"), + } + } + + // Private key tree accounts + for (id, chain_index) in user_data.private_key_tree.account_id_map.iter() { + println!("{chain_index} Private/{id}"); + match wallet_core.get_account_private(id) { + Some(account) if account != Account::default() => { + let (description, json_view) = format_account_details(&account); + println!(" {description}"); + println!(" {json_view}"); + } + Some(_) => println!(" Uninitialized"), + None => println!(" Not found in local storage"), + } + } + Ok(SubcommandReturnValue::Empty) } } @@ -344,6 +413,8 @@ impl WalletSubcommand for AccountSubcommand { #[cfg(test)] mod tests { + use nssa::AccountId; + use crate::cli::account::{TokedDefinitionAccountView, TokenDefinition}; #[test] @@ -352,6 +423,7 @@ mod tests { account_type: 1, name: [137, 12, 14, 3, 5, 4], total_supply: 100, + metadata_id: AccountId::new([0; 32]), }; let token_def_view: TokedDefinitionAccountView = token_def.into(); @@ -365,6 +437,7 @@ mod tests { account_type: 1, name: [240, 159, 146, 150, 66, 66], total_supply: 100, + metadata_id: AccountId::new([0; 32]), }; let token_def_view: TokedDefinitionAccountView = token_def.into(); @@ -378,6 +451,7 @@ mod tests { account_type: 1, name: [78, 65, 77, 69, 0, 0], total_supply: 100, + metadata_id: AccountId::new([0; 32]), }; let token_def_view: TokedDefinitionAccountView = token_def.into(); diff --git a/wallet/src/cli/config.rs b/wallet/src/cli/config.rs index bfebf9bf..b2d4aa93 100644 --- a/wallet/src/cli/config.rs +++ b/wallet/src/cli/config.rs @@ -10,7 +10,13 @@ use crate::{ #[derive(Subcommand, Debug, Clone)] pub enum ConfigSubcommand { /// Getter of config fields - Get { key: String }, + Get { + /// Print all config fields + #[arg(short, long)] + all: bool, + /// Config field key to get + key: Option, + }, /// Setter of config fields Set { key: String, value: String }, /// Prints description of corresponding field @@ -23,58 +29,66 @@ impl WalletSubcommand for ConfigSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - ConfigSubcommand::Get { key } => match key.as_str() { - "all" => { + ConfigSubcommand::Get { all, key } => { + if all { let config_str = serde_json::to_string_pretty(&wallet_core.storage.wallet_config)?; println!("{config_str}"); - } - "override_rust_log" => { - if let Some(value) = &wallet_core.storage.wallet_config.override_rust_log { - println!("{value}"); - } else { - println!("Not set"); + } else if let Some(key) = key { + match key.as_str() { + "override_rust_log" => { + if let Some(value) = + &wallet_core.storage.wallet_config.override_rust_log + { + println!("{value}"); + } else { + println!("Not set"); + } + } + "sequencer_addr" => { + println!("{}", wallet_core.storage.wallet_config.sequencer_addr); + } + "seq_poll_timeout_millis" => { + println!( + "{}", + wallet_core.storage.wallet_config.seq_poll_timeout_millis + ); + } + "seq_tx_poll_max_blocks" => { + println!( + "{}", + wallet_core.storage.wallet_config.seq_tx_poll_max_blocks + ); + } + "seq_poll_max_retries" => { + println!("{}", wallet_core.storage.wallet_config.seq_poll_max_retries); + } + "seq_block_poll_max_amount" => { + println!( + "{}", + wallet_core.storage.wallet_config.seq_block_poll_max_amount + ); + } + "initial_accounts" => { + println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts); + } + "basic_auth" => { + if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth + { + println!("{basic_auth}"); + } else { + println!("Not set"); + } + } + _ => { + println!("Unknown field"); + } } + } else { + println!("Please provide a key or use --all flag"); } - "sequencer_addr" => { - println!("{}", wallet_core.storage.wallet_config.sequencer_addr); - } - "seq_poll_timeout_millis" => { - println!( - "{}", - wallet_core.storage.wallet_config.seq_poll_timeout_millis - ); - } - "seq_tx_poll_max_blocks" => { - println!( - "{}", - wallet_core.storage.wallet_config.seq_tx_poll_max_blocks - ); - } - "seq_poll_max_retries" => { - println!("{}", wallet_core.storage.wallet_config.seq_poll_max_retries); - } - "seq_block_poll_max_amount" => { - println!( - "{}", - wallet_core.storage.wallet_config.seq_block_poll_max_amount - ); - } - "initial_accounts" => { - println!("{:#?}", wallet_core.storage.wallet_config.initial_accounts); - } - "basic_auth" => { - if let Some(basic_auth) = &wallet_core.storage.wallet_config.basic_auth { - println!("{basic_auth}"); - } else { - println!("Not set"); - } - } - _ => { - println!("Unknown field"); - } - }, + } ConfigSubcommand::Set { key, value } => { match key.as_str() { "override_rust_log" => { @@ -108,9 +122,7 @@ impl WalletSubcommand for ConfigSubcommand { } } - let path = wallet_core.store_config_changes().await?; - - println!("Stored changed config at {path:#?}"); + wallet_core.store_config_changes().await? } ConfigSubcommand::Description { key } => match key.as_str() { "override_rust_log" => { diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index 61b8697e..c742ecbd 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -11,11 +11,10 @@ use crate::{ chain::ChainSubcommand, config::ConfigSubcommand, programs::{ - native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, - token::TokenProgramAgnosticSubcommand, + amm::AmmProgramAgnosticSubcommand, native_token_transfer::AuthTransferSubcommand, + pinata::PinataProgramAgnosticSubcommand, token::TokenProgramAgnosticSubcommand, }, }, - helperfunctions::{fetch_config, fetch_persistent_storage, merge_auth_config}, }; pub mod account; @@ -47,6 +46,9 @@ pub enum Command { /// Token program interaction subcommand #[command(subcommand)] Token(TokenProgramAgnosticSubcommand), + /// AMM program interaction subcommand + #[command(subcommand)] + AMM(AmmProgramAgnosticSubcommand), /// Check the wallet can connect to the node and builtin local programs /// match the remote versions CheckHealth {}, @@ -94,43 +96,22 @@ pub enum SubcommandReturnValue { SyncedToBlock(u64), } -pub async fn execute_subcommand(command: Command) -> Result { - execute_subcommand_with_auth(command, None).await -} - -pub async fn execute_subcommand_with_auth( +pub async fn execute_subcommand( + wallet_core: &mut WalletCore, command: Command, - auth: Option, ) -> Result { - if fetch_persistent_storage().await.is_err() { - println!("Persistent storage not found, need to execute setup"); - - let password = read_password_from_stdin()?; - execute_setup_with_auth(password, auth.clone()).await?; - } - - let wallet_config = fetch_config().await?; - let wallet_config = merge_auth_config(wallet_config, auth.clone())?; - let mut wallet_core = WalletCore::start_from_config_update_chain(wallet_config).await?; - let subcommand_ret = match command { Command::AuthTransfer(transfer_subcommand) => { - transfer_subcommand - .handle_subcommand(&mut wallet_core) - .await? + transfer_subcommand.handle_subcommand(wallet_core).await? } Command::ChainInfo(chain_subcommand) => { - chain_subcommand.handle_subcommand(&mut wallet_core).await? + chain_subcommand.handle_subcommand(wallet_core).await? } Command::Account(account_subcommand) => { - account_subcommand - .handle_subcommand(&mut wallet_core) - .await? + account_subcommand.handle_subcommand(wallet_core).await? } Command::Pinata(pinata_subcommand) => { - pinata_subcommand - .handle_subcommand(&mut wallet_core) - .await? + pinata_subcommand.handle_subcommand(wallet_core).await? } Command::CheckHealth {} => { let remote_program_ids = wallet_core @@ -162,17 +143,15 @@ pub async fn execute_subcommand_with_auth( SubcommandReturnValue::Empty } - Command::Token(token_subcommand) => { - token_subcommand.handle_subcommand(&mut wallet_core).await? - } + Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?, + Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?, Command::Config(config_subcommand) => { - config_subcommand - .handle_subcommand(&mut wallet_core) - .await? + config_subcommand.handle_subcommand(wallet_core).await? } Command::RestoreKeys { depth } => { let password = read_password_from_stdin()?; - execute_keys_restoration_with_auth(password, depth, auth).await?; + wallet_core.reset_storage(password)?; + execute_keys_restoration(wallet_core, depth).await?; SubcommandReturnValue::Empty } @@ -196,14 +175,7 @@ pub async fn execute_subcommand_with_auth( Ok(subcommand_ret) } -pub async fn execute_continuous_run() -> Result<()> { - execute_continuous_run_with_auth(None).await -} -pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let mut wallet_core = WalletCore::start_from_config_update_chain(config.clone()).await?; - +pub async fn execute_continuous_run(wallet_core: &mut WalletCore) -> Result<()> { loop { let latest_block_num = wallet_core .sequencer_client @@ -213,7 +185,7 @@ pub async fn execute_continuous_run_with_auth(auth: Option) -> Result<() wallet_core.sync_to_block(latest_block_num).await?; tokio::time::sleep(std::time::Duration::from_millis( - config.seq_poll_timeout_millis, + wallet_core.config().seq_poll_timeout_millis, )) .await; } @@ -229,34 +201,7 @@ pub fn read_password_from_stdin() -> Result { Ok(password.trim().to_string()) } -pub async fn execute_setup(password: String) -> Result<()> { - execute_setup_with_auth(password, None).await -} - -pub async fn execute_setup_with_auth(password: String, auth: Option) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let wallet_core = WalletCore::start_from_config_new_storage(config.clone(), password).await?; - - wallet_core.store_persistent_data().await?; - - Ok(()) -} - -pub async fn execute_keys_restoration(password: String, depth: u32) -> Result<()> { - execute_keys_restoration_with_auth(password, depth, None).await -} - -pub async fn execute_keys_restoration_with_auth( - password: String, - depth: u32, - auth: Option, -) -> Result<()> { - let config = fetch_config().await?; - let config = merge_auth_config(config, auth)?; - let mut wallet_core = - WalletCore::start_from_config_new_storage(config.clone(), password.clone()).await?; - +pub async fn execute_keys_restoration(wallet_core: &mut WalletCore, depth: u32) -> Result<()> { wallet_core .storage .user_data diff --git a/wallet/src/cli/programs/amm.rs b/wallet/src/cli/programs/amm.rs new file mode 100644 index 00000000..ce919b7c --- /dev/null +++ b/wallet/src/cli/programs/amm.rs @@ -0,0 +1,286 @@ +use anyhow::Result; +use clap::Subcommand; +use nssa::AccountId; + +use crate::{ + WalletCore, + cli::{SubcommandReturnValue, WalletSubcommand}, + helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, + program_facades::amm::Amm, +}; + +/// Represents generic CLI subcommand for a wallet working with amm program +#[derive(Subcommand, Debug, Clone)] +pub enum AmmProgramAgnosticSubcommand { + /// Produce a new pool + /// + /// user_holding_a and user_holding_b must be owned. + /// + /// Only public execution allowed + New { + /// user_holding_a - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_a: String, + /// user_holding_b - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_b: String, + /// user_holding_lp - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_lp: String, + #[arg(long)] + balance_a: u128, + #[arg(long)] + balance_b: u128, + }, + /// Swap + /// + /// The account associated with swapping token must be owned + /// + /// Only public execution allowed + Swap { + /// user_holding_a - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_a: String, + /// user_holding_b - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_b: String, + #[arg(long)] + amount_in: u128, + #[arg(long)] + min_amount_out: u128, + /// token_definition - valid 32 byte base58 string WITHOUT privacy prefix + #[arg(long)] + token_definition: String, + }, + /// Add liquidity + /// + /// user_holding_a and user_holding_b must be owned. + /// + /// Only public execution allowed + AddLiquidity { + /// user_holding_a - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_a: String, + /// user_holding_b - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_b: String, + /// user_holding_lp - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_lp: String, + #[arg(long)] + min_amount_lp: u128, + #[arg(long)] + max_amount_a: u128, + #[arg(long)] + max_amount_b: u128, + }, + /// Remove liquidity + /// + /// user_holding_lp must be owned. + /// + /// Only public execution allowed + RemoveLiquidity { + /// user_holding_a - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_a: String, + /// user_holding_b - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_b: String, + /// user_holding_lp - valid 32 byte base58 string with privacy prefix + #[arg(long)] + user_holding_lp: String, + #[arg(long)] + balance_lp: u128, + #[arg(long)] + min_amount_a: u128, + #[arg(long)] + min_amount_b: u128, + }, +} + +impl WalletSubcommand for AmmProgramAgnosticSubcommand { + async fn handle_subcommand( + self, + wallet_core: &mut WalletCore, + ) -> Result { + match self { + AmmProgramAgnosticSubcommand::New { + user_holding_a, + user_holding_b, + user_holding_lp, + balance_a, + balance_b, + } => { + let (user_holding_a, user_holding_a_privacy) = + parse_addr_with_privacy_prefix(&user_holding_a)?; + let (user_holding_b, user_holding_b_privacy) = + parse_addr_with_privacy_prefix(&user_holding_b)?; + let (user_holding_lp, user_holding_lp_privacy) = + parse_addr_with_privacy_prefix(&user_holding_lp)?; + + let user_holding_a: AccountId = user_holding_a.parse()?; + let user_holding_b: AccountId = user_holding_b.parse()?; + let user_holding_lp: AccountId = user_holding_lp.parse()?; + + match ( + user_holding_a_privacy, + user_holding_b_privacy, + user_holding_lp_privacy, + ) { + ( + AccountPrivacyKind::Public, + AccountPrivacyKind::Public, + AccountPrivacyKind::Public, + ) => { + Amm(wallet_core) + .send_new_definition( + user_holding_a, + user_holding_b, + user_holding_lp, + balance_a, + balance_b, + ) + .await?; + + Ok(SubcommandReturnValue::Empty) + } + _ => { + // ToDo: Implement after private multi-chain calls is available + anyhow::bail!("Only public execution allowed for Amm calls"); + } + } + } + AmmProgramAgnosticSubcommand::Swap { + user_holding_a, + user_holding_b, + amount_in, + min_amount_out, + token_definition, + } => { + let (user_holding_a, user_holding_a_privacy) = + parse_addr_with_privacy_prefix(&user_holding_a)?; + let (user_holding_b, user_holding_b_privacy) = + parse_addr_with_privacy_prefix(&user_holding_b)?; + + let user_holding_a: AccountId = user_holding_a.parse()?; + let user_holding_b: AccountId = user_holding_b.parse()?; + + match (user_holding_a_privacy, user_holding_b_privacy) { + (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { + Amm(wallet_core) + .send_swap( + user_holding_a, + user_holding_b, + amount_in, + min_amount_out, + token_definition.parse()?, + ) + .await?; + + Ok(SubcommandReturnValue::Empty) + } + _ => { + // ToDo: Implement after private multi-chain calls is available + anyhow::bail!("Only public execution allowed for Amm calls"); + } + } + } + AmmProgramAgnosticSubcommand::AddLiquidity { + user_holding_a, + user_holding_b, + user_holding_lp, + min_amount_lp, + max_amount_a, + max_amount_b, + } => { + let (user_holding_a, user_holding_a_privacy) = + parse_addr_with_privacy_prefix(&user_holding_a)?; + let (user_holding_b, user_holding_b_privacy) = + parse_addr_with_privacy_prefix(&user_holding_b)?; + let (user_holding_lp, user_holding_lp_privacy) = + parse_addr_with_privacy_prefix(&user_holding_lp)?; + + let user_holding_a: AccountId = user_holding_a.parse()?; + let user_holding_b: AccountId = user_holding_b.parse()?; + let user_holding_lp: AccountId = user_holding_lp.parse()?; + + match ( + user_holding_a_privacy, + user_holding_b_privacy, + user_holding_lp_privacy, + ) { + ( + AccountPrivacyKind::Public, + AccountPrivacyKind::Public, + AccountPrivacyKind::Public, + ) => { + Amm(wallet_core) + .send_add_liquidity( + user_holding_a, + user_holding_b, + user_holding_lp, + min_amount_lp, + max_amount_a, + max_amount_b, + ) + .await?; + + Ok(SubcommandReturnValue::Empty) + } + _ => { + // ToDo: Implement after private multi-chain calls is available + anyhow::bail!("Only public execution allowed for Amm calls"); + } + } + } + AmmProgramAgnosticSubcommand::RemoveLiquidity { + user_holding_a, + user_holding_b, + user_holding_lp, + balance_lp, + min_amount_a, + min_amount_b, + } => { + let (user_holding_a, user_holding_a_privacy) = + parse_addr_with_privacy_prefix(&user_holding_a)?; + let (user_holding_b, user_holding_b_privacy) = + parse_addr_with_privacy_prefix(&user_holding_b)?; + let (user_holding_lp, user_holding_lp_privacy) = + parse_addr_with_privacy_prefix(&user_holding_lp)?; + + let user_holding_a: AccountId = user_holding_a.parse()?; + let user_holding_b: AccountId = user_holding_b.parse()?; + let user_holding_lp: AccountId = user_holding_lp.parse()?; + + match ( + user_holding_a_privacy, + user_holding_b_privacy, + user_holding_lp_privacy, + ) { + ( + AccountPrivacyKind::Public, + AccountPrivacyKind::Public, + AccountPrivacyKind::Public, + ) => { + Amm(wallet_core) + .send_remove_liquidity( + user_holding_a, + user_holding_b, + user_holding_lp, + balance_lp, + min_amount_a, + min_amount_b, + ) + .await?; + + Ok(SubcommandReturnValue::Empty) + } + _ => { + // ToDo: Implement after private multi-chain calls is available + anyhow::bail!("Only public execution allowed for Amm calls"); + } + } + } + } + } +} diff --git a/wallet/src/cli/programs/mod.rs b/wallet/src/cli/programs/mod.rs index 3ffb7bb2..96a4e766 100644 --- a/wallet/src/cli/programs/mod.rs +++ b/wallet/src/cli/programs/mod.rs @@ -1,3 +1,4 @@ +pub mod amm; pub mod native_token_transfer; pub mod pinata; pub mod token; diff --git a/wallet/src/cli/programs/native_token_transfer.rs b/wallet/src/cli/programs/native_token_transfer.rs index 9dc72ae6..0cfc0fb6 100644 --- a/wallet/src/cli/programs/native_token_transfer.rs +++ b/wallet/src/cli/programs/native_token_transfer.rs @@ -4,6 +4,7 @@ use common::transaction::NSSATransaction; use nssa::AccountId; use crate::{ + AccDecodeData::Decode, WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, @@ -68,9 +69,7 @@ impl WalletSubcommand for AuthTransferSubcommand { println!("Transaction data is {transfer_tx:?}"); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; } AccountPrivacyKind::Private => { let account_id = account_id.parse()?; @@ -87,7 +86,7 @@ impl WalletSubcommand for AuthTransferSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret, account_id)]; + let acc_decode_data = vec![Decode(secret, account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -95,9 +94,7 @@ impl WalletSubcommand for AuthTransferSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; } } @@ -328,7 +325,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_from, from), (secret_to, to)]; + let acc_decode_data = vec![Decode(secret_from, from), Decode(secret_to, to)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -336,9 +333,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -372,7 +367,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_from, from)]; + let acc_decode_data = vec![Decode(secret_from, from)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -380,9 +375,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -412,7 +405,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret, to)]; + let acc_decode_data = vec![Decode(secret, to)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -420,9 +413,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -453,9 +444,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommandShielded { let tx_hash = res.tx_hash; - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -491,7 +480,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret, from)]; + let acc_decode_data = vec![Decode(secret, from)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -499,9 +488,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -519,9 +506,7 @@ impl WalletSubcommand for NativeTokenTransferProgramSubcommand { println!("Transaction data is {transfer_tx:?}"); - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::Empty) } diff --git a/wallet/src/cli/programs/pinata.rs b/wallet/src/cli/programs/pinata.rs index 7712a7c1..192d3f47 100644 --- a/wallet/src/cli/programs/pinata.rs +++ b/wallet/src/cli/programs/pinata.rs @@ -3,6 +3,7 @@ use clap::Subcommand; use common::{PINATA_BASE58, transaction::NSSATransaction}; use crate::{ + AccDecodeData::Decode, WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, @@ -159,7 +160,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { println!("Transaction data is {transfer_tx:?}"); if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_winner, winner_account_id)]; + let acc_decode_data = vec![Decode(secret_winner, winner_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -167,9 +168,7 @@ impl WalletSubcommand for PinataProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } diff --git a/wallet/src/cli/programs/token.rs b/wallet/src/cli/programs/token.rs index 4480a1e5..b5ea1b34 100644 --- a/wallet/src/cli/programs/token.rs +++ b/wallet/src/cli/programs/token.rs @@ -4,6 +4,7 @@ use common::transaction::NSSATransaction; use nssa::AccountId; use crate::{ + AccDecodeData::Decode, WalletCore, cli::{SubcommandReturnValue, WalletSubcommand}, helperfunctions::{AccountPrivacyKind, parse_addr_with_privacy_prefix}, @@ -49,6 +50,48 @@ pub enum TokenProgramAgnosticSubcommand { #[arg(long)] amount: u128, }, + /// Burn tokens on `holder`, modify `definition`. + /// + /// `holder` is owned + /// + /// Also if `definition` is private then it is owned, because + /// we can not modify foreign accounts. + Burn { + /// definition - valid 32 byte base58 string with privacy prefix + #[arg(long)] + definition: String, + /// holder - valid 32 byte base58 string with privacy prefix + #[arg(long)] + holder: String, + /// amount - amount of balance to burn + #[arg(long)] + amount: u128, + }, + /// Mint tokens on `holder`, modify `definition`. + /// + /// `definition` is owned + /// + /// If `holder` is private, then `holder` and (`holder_npk` , `holder_ipk`) is a mutually + /// exclusive patterns. + /// + /// First is used for owned accounts, second otherwise. + Mint { + /// definition - valid 32 byte base58 string with privacy prefix + #[arg(long)] + definition: String, + /// holder - valid 32 byte base58 string with privacy prefix + #[arg(long)] + holder: Option, + /// holder_npk - valid 32 byte hex string + #[arg(long)] + holder_npk: Option, + /// to_ipk - valid 33 byte hex string + #[arg(long)] + holder_ipk: Option, + /// amount - amount of balance to mint + #[arg(long)] + amount: u128, + }, } impl WalletSubcommand for TokenProgramAgnosticSubcommand { @@ -201,6 +244,150 @@ impl WalletSubcommand for TokenProgramAgnosticSubcommand { } }; + underlying_subcommand.handle_subcommand(wallet_core).await + } + TokenProgramAgnosticSubcommand::Burn { + definition, + holder, + amount, + } => { + let underlying_subcommand = { + let (definition, definition_privacy) = + parse_addr_with_privacy_prefix(&definition)?; + let (holder, holder_privacy) = parse_addr_with_privacy_prefix(&holder)?; + + match (definition_privacy, holder_privacy) { + (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Public( + TokenProgramSubcommandPublic::BurnToken { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::BurnTokenPrivateOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Deshielded( + TokenProgramSubcommandDeshielded::BurnTokenDeshieldedOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Public, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::BurnTokenShielded { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + } + }; + + underlying_subcommand.handle_subcommand(wallet_core).await + } + TokenProgramAgnosticSubcommand::Mint { + definition, + holder, + holder_npk, + holder_ipk, + amount, + } => { + let underlying_subcommand = match (holder, holder_npk, holder_ipk) { + (None, None, None) => { + anyhow::bail!( + "Provide either account account_id of holder or their public keys" + ); + } + (Some(_), Some(_), Some(_)) => { + anyhow::bail!( + "Provide only one variant: either account_id of holder or their public keys" + ); + } + (_, Some(_), None) | (_, None, Some(_)) => { + anyhow::bail!("List of public keys is uncomplete"); + } + (Some(holder), None, None) => { + let (definition, definition_privacy) = + parse_addr_with_privacy_prefix(&definition)?; + let (holder, holder_privacy) = parse_addr_with_privacy_prefix(&holder)?; + + match (definition_privacy, holder_privacy) { + (AccountPrivacyKind::Public, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Public( + TokenProgramSubcommandPublic::MintToken { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::MintTokenPrivateOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Private, AccountPrivacyKind::Public) => { + TokenProgramSubcommand::Deshielded( + TokenProgramSubcommandDeshielded::MintTokenDeshielded { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + (AccountPrivacyKind::Public, AccountPrivacyKind::Private) => { + TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::MintTokenShieldedOwned { + definition_account_id: definition, + holder_account_id: holder, + amount, + }, + ) + } + } + } + (None, Some(holder_npk), Some(holder_ipk)) => { + let (definition, definition_privacy) = + parse_addr_with_privacy_prefix(&definition)?; + + match definition_privacy { + AccountPrivacyKind::Private => TokenProgramSubcommand::Private( + TokenProgramSubcommandPrivate::MintTokenPrivateForeign { + definition_account_id: definition, + holder_npk, + holder_ipk, + amount, + }, + ), + AccountPrivacyKind::Public => TokenProgramSubcommand::Shielded( + TokenProgramSubcommandShielded::MintTokenShieldedForeign { + definition_account_id: definition, + holder_npk, + holder_ipk, + amount, + }, + ), + } + } + }; + underlying_subcommand.handle_subcommand(wallet_core).await } } @@ -239,6 +426,24 @@ pub enum TokenProgramSubcommandPublic { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnToken { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintToken { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents generic private CLI subcommand for a wallet working with token_program @@ -266,6 +471,35 @@ pub enum TokenProgramSubcommandPrivate { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnTokenPrivateOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenPrivateOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenPrivateForeign { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_npk: String, + #[arg(short, long)] + holder_ipk: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents deshielded public CLI subcommand for a wallet working with token_program @@ -280,6 +514,24 @@ pub enum TokenProgramSubcommandDeshielded { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnTokenDeshieldedOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenDeshielded { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents generic shielded CLI subcommand for a wallet working with token_program @@ -307,6 +559,35 @@ pub enum TokenProgramSubcommandShielded { #[arg(short, long)] balance_to_move: u128, }, + // Burn tokens using the token program + BurnTokenShielded { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenShieldedOwned { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_account_id: String, + #[arg(short, long)] + amount: u128, + }, + // Transfer tokens using the token program + MintTokenShieldedForeign { + #[arg(short, long)] + definition_account_id: String, + #[arg(short, long)] + holder_npk: String, + #[arg(short, long)] + holder_ipk: String, + #[arg(short, long)] + amount: u128, + }, } /// Represents generic initialization subcommand for a wallet working with token_program @@ -386,6 +667,34 @@ impl WalletSubcommand for TokenProgramSubcommandPublic { .await?; Ok(SubcommandReturnValue::Empty) } + TokenProgramSubcommandPublic::BurnToken { + definition_account_id, + holder_account_id, + amount, + } => { + Token(wallet_core) + .send_burn_transaction( + definition_account_id.parse().unwrap(), + holder_account_id.parse().unwrap(), + amount, + ) + .await?; + Ok(SubcommandReturnValue::Empty) + } + TokenProgramSubcommandPublic::MintToken { + definition_account_id, + holder_account_id, + amount, + } => { + Token(wallet_core) + .send_mint_transaction( + definition_account_id.parse().unwrap(), + holder_account_id.parse().unwrap(), + amount, + ) + .await?; + Ok(SubcommandReturnValue::Empty) + } } } } @@ -421,8 +730,8 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { let acc_decode_data = vec![ - (secret_sender, sender_account_id), - (secret_recipient, recipient_account_id), + Decode(secret_sender, sender_account_id), + Decode(secret_recipient, recipient_account_id), ]; wallet_core.decode_insert_privacy_preserving_transaction_results( @@ -431,9 +740,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -473,7 +780,7 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_sender, sender_account_id)]; + let acc_decode_data = vec![Decode(secret_sender, sender_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -481,9 +788,134 @@ impl WalletSubcommand for TokenProgramSubcommandPrivate { )?; } - let path = wallet_core.store_persistent_data().await?; + wallet_core.store_persistent_data().await?; - println!("Stored persistent accounts at {path:#?}"); + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::BurnTokenPrivateOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, [secret_definition, secret_holder]) = Token(wallet_core) + .send_burn_transaction_private_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![ + Decode(secret_definition, definition_account_id), + Decode(secret_holder, holder_account_id), + ]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + wallet_core.store_persistent_data().await?; + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::MintTokenPrivateOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, [secret_definition, secret_holder]) = Token(wallet_core) + .send_mint_transaction_private_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![ + Decode(secret_definition, definition_account_id), + Decode(secret_holder, holder_account_id), + ]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + wallet_core.store_persistent_data().await?; + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandPrivate::MintTokenPrivateForeign { + definition_account_id, + holder_npk, + holder_ipk, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + + let holder_npk_res = hex::decode(holder_npk)?; + let mut holder_npk = [0; 32]; + holder_npk.copy_from_slice(&holder_npk_res); + let holder_npk = nssa_core::NullifierPublicKey(holder_npk); + + let holder_ipk_res = hex::decode(holder_ipk)?; + let mut holder_ipk = [0u8; 33]; + holder_ipk.copy_from_slice(&holder_ipk_res); + let holder_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( + holder_ipk.to_vec(), + ); + + let (res, [secret_definition, _]) = Token(wallet_core) + .send_mint_transaction_private_foreign_account( + definition_account_id, + holder_npk, + holder_ipk, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -521,7 +953,7 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_sender, sender_account_id)]; + let acc_decode_data = vec![Decode(secret_sender, sender_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -529,9 +961,79 @@ impl WalletSubcommand for TokenProgramSubcommandDeshielded { )?; } - let path = wallet_core.store_persistent_data().await?; + wallet_core.store_persistent_data().await?; - println!("Stored persistent accounts at {path:#?}"); + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandDeshielded::BurnTokenDeshieldedOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_definition) = Token(wallet_core) + .send_burn_transaction_deshielded_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + wallet_core.store_persistent_data().await?; + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandDeshielded::MintTokenDeshielded { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_definition) = Token(wallet_core) + .send_mint_transaction_deshielded( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -584,9 +1086,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { println!("Transaction data is {:?}", tx.message); } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -614,7 +1114,7 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_recipient, recipient_account_id)]; + let acc_decode_data = vec![Decode(secret_recipient, recipient_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -622,9 +1122,123 @@ impl WalletSubcommand for TokenProgramSubcommandShielded { )?; } - let path = wallet_core.store_persistent_data().await?; + wallet_core.store_persistent_data().await?; - println!("Stored persistent accounts at {path:#?}"); + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandShielded::BurnTokenShielded { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_holder) = Token(wallet_core) + .send_burn_transaction_shielded( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![Decode(secret_holder, holder_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + wallet_core.store_persistent_data().await?; + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandShielded::MintTokenShieldedOwned { + definition_account_id, + holder_account_id, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + let holder_account_id: AccountId = holder_account_id.parse().unwrap(); + + let (res, secret_holder) = Token(wallet_core) + .send_mint_transaction_shielded_owned_account( + definition_account_id, + holder_account_id, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + let acc_decode_data = vec![Decode(secret_holder, holder_account_id)]; + + wallet_core.decode_insert_privacy_preserving_transaction_results( + tx, + &acc_decode_data, + )?; + } + + wallet_core.store_persistent_data().await?; + + Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) + } + TokenProgramSubcommandShielded::MintTokenShieldedForeign { + definition_account_id, + holder_npk, + holder_ipk, + amount, + } => { + let definition_account_id: AccountId = definition_account_id.parse().unwrap(); + + let holder_npk_res = hex::decode(holder_npk)?; + let mut holder_npk = [0; 32]; + holder_npk.copy_from_slice(&holder_npk_res); + let holder_npk = nssa_core::NullifierPublicKey(holder_npk); + + let holder_ipk_res = hex::decode(holder_ipk)?; + let mut holder_ipk = [0u8; 33]; + holder_ipk.copy_from_slice(&holder_ipk_res); + let holder_ipk = nssa_core::encryption::shared_key_derivation::Secp256k1Point( + holder_ipk.to_vec(), + ); + + let (res, _) = Token(wallet_core) + .send_mint_transaction_shielded_foreign_account( + definition_account_id, + holder_npk, + holder_ipk, + amount, + ) + .await?; + + println!("Results of tx send are {res:#?}"); + + let tx_hash = res.tx_hash; + let transfer_tx = wallet_core + .poll_native_token_transfer(tx_hash.clone()) + .await?; + + if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { + println!("Transaction data is {:?}", tx.message); + } + + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -673,8 +1287,8 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { let acc_decode_data = vec![ - (secret_definition, definition_account_id), - (secret_supply, supply_account_id), + Decode(secret_definition, definition_account_id), + Decode(secret_supply, supply_account_id), ]; wallet_core.decode_insert_privacy_preserving_transaction_results( @@ -683,9 +1297,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -723,7 +1335,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_definition, definition_account_id)]; + let acc_decode_data = vec![Decode(secret_definition, definition_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -731,9 +1343,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } @@ -771,7 +1381,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { .await?; if let NSSATransaction::PrivacyPreserving(tx) = transfer_tx { - let acc_decode_data = vec![(secret_supply, supply_account_id)]; + let acc_decode_data = vec![Decode(secret_supply, supply_account_id)]; wallet_core.decode_insert_privacy_preserving_transaction_results( tx, @@ -779,9 +1389,7 @@ impl WalletSubcommand for CreateNewTokenProgramSubcommand { )?; } - let path = wallet_core.store_persistent_data().await?; - - println!("Stored persistent accounts at {path:#?}"); + wallet_core.store_persistent_data().await?; Ok(SubcommandReturnValue::PrivacyPreservingTransfer { tx_hash }) } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index c06ccc49..45407b6d 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,11 +1,17 @@ -use std::str::FromStr; +use std::{ + io::{BufReader, Write as _}, + path::Path, + str::FromStr, +}; +use anyhow::{Context as _, Result}; use key_protocol::key_management::{ KeyChain, key_tree::{ chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, }, }; +use log::warn; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -105,6 +111,25 @@ pub struct PersistentStorage { pub last_synced_block: u64, } +impl PersistentStorage { + pub fn from_path(path: &Path) -> Result { + match std::fs::File::open(path) { + Ok(file) => { + let storage_content = BufReader::new(file); + Ok(serde_json::from_reader(storage_content)?) + } + Err(err) => match err.kind() { + std::io::ErrorKind::NotFound => { + anyhow::bail!("Not found, please setup roots from config command beforehand"); + } + _ => { + anyhow::bail!("IO error {err:#?}"); + } + }, + } + } +} + impl InitialAccountData { pub fn account_id(&self) -> nssa::AccountId { match &self { @@ -172,9 +197,11 @@ pub struct GasConfig { pub gas_limit_runtime: u64, } +#[optfield::optfield(pub WalletConfigOverrides, rewrap, attrs = (derive(Debug, Default)))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WalletConfig { /// Override rust log (env var logging level) + #[serde(skip_serializing_if = "Option::is_none")] pub override_rust_log: Option, /// Sequencer URL pub sequencer_addr: String, @@ -189,6 +216,7 @@ pub struct WalletConfig { /// Initial accounts for wallet pub initial_accounts: Vec, /// Basic authentication credentials + #[serde(skip_serializing_if = "Option::is_none")] pub basic_auth: Option, } @@ -748,3 +776,98 @@ impl Default for WalletConfig { } } } + +impl WalletConfig { + pub fn from_path_or_initialize_default(config_path: &Path) -> Result { + match std::fs::File::open(config_path) { + Ok(file) => { + let reader = std::io::BufReader::new(file); + Ok(serde_json::from_reader(reader)?) + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + println!("Config not found, setting up default config"); + + let config_home = config_path.parent().ok_or_else(|| { + anyhow::anyhow!( + "Could not get parent directory of config file at {config_path:#?}" + ) + })?; + std::fs::create_dir_all(config_home)?; + + println!("Created configs dir at path {config_home:#?}"); + + let mut file = std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(config_path)?; + + let config = WalletConfig::default(); + let default_config_serialized = serde_json::to_vec_pretty(&config).unwrap(); + + file.write_all(&default_config_serialized)?; + + println!("Configs set up"); + Ok(config) + } + Err(err) => Err(err).context("IO error"), + } + } + + pub fn apply_overrides(&mut self, overrides: WalletConfigOverrides) { + let WalletConfig { + override_rust_log, + sequencer_addr, + seq_poll_timeout_millis, + seq_tx_poll_max_blocks, + seq_poll_max_retries, + seq_block_poll_max_amount, + initial_accounts, + basic_auth, + } = self; + + let WalletConfigOverrides { + override_rust_log: o_override_rust_log, + sequencer_addr: o_sequencer_addr, + seq_poll_timeout_millis: o_seq_poll_timeout_millis, + seq_tx_poll_max_blocks: o_seq_tx_poll_max_blocks, + seq_poll_max_retries: o_seq_poll_max_retries, + seq_block_poll_max_amount: o_seq_block_poll_max_amount, + initial_accounts: o_initial_accounts, + basic_auth: o_basic_auth, + } = overrides; + + if let Some(v) = o_override_rust_log { + warn!("Overriding wallet config 'override_rust_log' to {v:#?}"); + *override_rust_log = v; + } + if let Some(v) = o_sequencer_addr { + warn!("Overriding wallet config 'sequencer_addr' to {v}"); + *sequencer_addr = v; + } + if let Some(v) = o_seq_poll_timeout_millis { + warn!("Overriding wallet config 'seq_poll_timeout_millis' to {v}"); + *seq_poll_timeout_millis = v; + } + if let Some(v) = o_seq_tx_poll_max_blocks { + warn!("Overriding wallet config 'seq_tx_poll_max_blocks' to {v}"); + *seq_tx_poll_max_blocks = v; + } + if let Some(v) = o_seq_poll_max_retries { + warn!("Overriding wallet config 'seq_poll_max_retries' to {v}"); + *seq_poll_max_retries = v; + } + if let Some(v) = o_seq_block_poll_max_amount { + warn!("Overriding wallet config 'seq_block_poll_max_amount' to {v}"); + *seq_block_poll_max_amount = v; + } + if let Some(v) = o_initial_accounts { + warn!("Overriding wallet config 'initial_accounts' to {v:#?}"); + *initial_accounts = v; + } + if let Some(v) = o_basic_auth { + warn!("Overriding wallet config 'basic_auth' to {v:#?}"); + *basic_auth = v; + } + } +} diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 5f1dcf77..23bf4bb8 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -7,23 +7,22 @@ use nssa::Account; use nssa_core::account::Nonce; use rand::{RngCore, rngs::OsRng}; use serde::Serialize; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ HOME_DIR_ENV_VAR, config::{ - BasicAuth, InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, - PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, WalletConfig, + InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, + PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage, }, }; /// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. -pub fn get_home_nssa_var() -> Result { +fn get_home_nssa_var() -> Result { Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?) } /// Get home dir for wallet. Env var `HOME` must be set before execution to succeed. -pub fn get_home_default_path() -> Result { +fn get_home_default_path() -> Result { std::env::home_dir() .map(|path| path.join(".nssa").join("wallet")) .ok_or(anyhow::anyhow!("Failed to get HOME")) @@ -38,96 +37,20 @@ pub fn get_home() -> Result { } } -/// Fetch config from default home -pub async fn fetch_config() -> Result { - let config_home = get_home()?; - let mut config_needs_setup = false; - - let config = match tokio::fs::OpenOptions::new() - .read(true) - .open(config_home.join("wallet_config.json")) - .await - { - Ok(mut file) => { - let mut config_contents = vec![]; - file.read_to_end(&mut config_contents).await?; - - serde_json::from_slice(&config_contents)? - } - Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => { - config_needs_setup = true; - - println!("Config not found, setting up default config"); - - WalletConfig::default() - } - _ => anyhow::bail!("IO error {err:#?}"), - }, - }; - - if config_needs_setup { - tokio::fs::create_dir_all(&config_home).await?; - - println!("Created configs dir at path {config_home:#?}"); - - let mut file = tokio::fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(config_home.join("wallet_config.json")) - .await?; - - let default_config_serialized = - serde_json::to_vec_pretty(&WalletConfig::default()).unwrap(); - - file.write_all(&default_config_serialized).await?; - - println!("Configs setted up"); - } - - Ok(config) +/// Fetch config path from default home +pub fn fetch_config_path() -> Result { + let home = get_home()?; + let config_path = home.join("wallet_config.json"); + Ok(config_path) } -/// Parse CLI auth string and merge with config auth, prioritizing CLI -pub fn merge_auth_config( - mut config: WalletConfig, - cli_auth: Option, -) -> Result { - if let Some(auth_str) = cli_auth { - let cli_auth_config: BasicAuth = auth_str.parse()?; - - if config.basic_auth.is_some() { - println!("Warning: CLI auth argument takes precedence over config basic-auth"); - } - - config.basic_auth = Some(cli_auth_config); - } - Ok(config) -} - -/// Fetch data stored at home +/// Fetch path to data storage from default home /// /// File must be created through setup beforehand. -pub async fn fetch_persistent_storage() -> Result { +pub fn fetch_persistent_storage_path() -> Result { let home = get_home()?; let accs_path = home.join("storage.json"); - let mut storage_content = vec![]; - - match tokio::fs::File::open(accs_path).await { - Ok(mut file) => { - file.read_to_end(&mut storage_content).await?; - Ok(serde_json::from_slice(&storage_content)?) - } - Err(err) => match err.kind() { - std::io::ErrorKind::NotFound => { - anyhow::bail!("Not found, please setup roots from config command beforehand"); - } - _ => { - anyhow::bail!("IO error {err:#?}"); - } - }, - } + Ok(accs_path) } /// Produces data for storage diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index bc283117..45709d05 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, sync::Arc}; -use anyhow::Result; +use anyhow::{Context, Result}; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; use chain_storage::WalletChainStore; use common::{ @@ -14,17 +14,19 @@ use key_protocol::key_management::key_tree::{chain_index::ChainIndex, traits::Ke use log::info; use nssa::{ Account, AccountId, PrivacyPreservingTransaction, - privacy_preserving_transaction::message::EncryptedAccountData, program::Program, + privacy_preserving_transaction::{ + circuit::ProgramWithDependencies, message::EncryptedAccountData, + }, +}; +use nssa_core::{ + Commitment, MembershipProof, SharedSecretKey, account::Data, program::InstructionData, }; -use nssa_core::{Commitment, MembershipProof, SharedSecretKey, program::InstructionData}; pub use privacy_preserving_tx::PrivacyPreservingAccount; use tokio::io::AsyncWriteExt; use crate::{ - config::PersistentStorage, - helperfunctions::{ - fetch_persistent_storage, get_home, produce_data_for_storage, produce_random_nonces, - }, + config::{PersistentStorage, WalletConfigOverrides}, + helperfunctions::{produce_data_for_storage, produce_random_nonces}, poller::TxPoller, }; @@ -38,92 +40,215 @@ pub mod poller; mod privacy_preserving_tx; pub mod program_facades; +pub enum AccDecodeData { + Skip, + Decode(nssa_core::SharedSecretKey, AccountId), +} + +const TOKEN_DEFINITION_DATA_SIZE: usize = 55; + +const TOKEN_HOLDING_TYPE: u8 = 1; +const TOKEN_HOLDING_DATA_SIZE: usize = 49; +const TOKEN_STANDARD_FUNGIBLE_TOKEN: u8 = 0; +const TOKEN_STANDARD_NONFUNGIBLE: u8 = 2; + +struct TokenDefinition { + #[allow(unused)] + account_type: u8, + name: [u8; 6], + total_supply: u128, + #[allow(unused)] + metadata_id: AccountId, +} + +struct TokenHolding { + #[allow(unused)] + account_type: u8, + definition_id: AccountId, + balance: u128, +} + +impl TokenDefinition { + fn parse(data: &Data) -> Option { + let data = Vec::::from(data.clone()); + + if data.len() != TOKEN_DEFINITION_DATA_SIZE { + None + } else { + let account_type = data[0]; + let name = data[1..7].try_into().expect("Name must be a 6 bytes"); + let total_supply = u128::from_le_bytes( + data[7..23] + .try_into() + .expect("Total supply must be 16 bytes little-endian"), + ); + let metadata_id = AccountId::new( + data[23..TOKEN_DEFINITION_DATA_SIZE] + .try_into() + .expect("Token Program expects valid Account Id for Metadata"), + ); + + let this = Some(Self { + account_type, + name, + total_supply, + metadata_id, + }); + + match account_type { + TOKEN_STANDARD_NONFUNGIBLE if total_supply != 1 => None, + TOKEN_STANDARD_FUNGIBLE_TOKEN if metadata_id != AccountId::new([0; 32]) => None, + _ => this, + } + } + } +} + +impl TokenHolding { + fn parse(data: &[u8]) -> Option { + if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE { + None + } else { + let account_type = data[0]; + let definition_id = AccountId::new(data[1..33].try_into().unwrap()); + let balance = u128::from_le_bytes(data[33..].try_into().unwrap()); + Some(Self { + definition_id, + balance, + account_type, + }) + } + } +} + pub struct WalletCore { - pub storage: WalletChainStore, - pub poller: TxPoller, + config_path: PathBuf, + storage: WalletChainStore, + storage_path: PathBuf, + poller: TxPoller, + // TODO: Make all fields private pub sequencer_client: Arc, pub last_synced_block: u64, } impl WalletCore { - pub async fn start_from_config_update_chain(config: WalletConfig) -> Result { - let basic_auth = config - .basic_auth - .as_ref() - .map(|auth| (auth.username.clone(), auth.password.clone())); - let client = Arc::new(SequencerClient::new_with_auth( - config.sequencer_addr.clone(), - basic_auth, - )?); - let tx_poller = TxPoller::new(config.clone(), client.clone()); + /// Construct wallet using [`HOME_DIR_ENV_VAR`] env var for paths or user home dir if not set. + pub fn from_env() -> Result { + let config_path = helperfunctions::fetch_config_path()?; + let storage_path = helperfunctions::fetch_persistent_storage_path()?; + Self::new_update_chain(config_path, storage_path, None) + } + + pub fn new_update_chain( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, + ) -> Result { let PersistentStorage { accounts: persistent_accounts, last_synced_block, - } = fetch_persistent_storage().await?; + } = PersistentStorage::from_path(&storage_path) + .with_context(|| format!("Failed to read persistent storage at {storage_path:#?}"))?; - let storage = WalletChainStore::new(config, persistent_accounts)?; - - Ok(Self { - storage, - poller: tx_poller, - sequencer_client: client.clone(), + Self::new( + config_path, + storage_path, + config_overrides, + |config| WalletChainStore::new(config, persistent_accounts), last_synced_block, - }) + ) } - pub async fn start_from_config_new_storage( - config: WalletConfig, + pub fn new_init_storage( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, password: String, ) -> Result { + Self::new( + config_path, + storage_path, + config_overrides, + |config| WalletChainStore::new_storage(config, password), + 0, + ) + } + + fn new( + config_path: PathBuf, + storage_path: PathBuf, + config_overrides: Option, + storage_ctor: impl FnOnce(WalletConfig) -> Result, + last_synced_block: u64, + ) -> Result { + let mut config = WalletConfig::from_path_or_initialize_default(&config_path) + .with_context(|| format!("Failed to deserialize wallet config at {config_path:#?}"))?; + if let Some(config_overrides) = config_overrides { + config.apply_overrides(config_overrides); + } + let basic_auth = config .basic_auth .as_ref() .map(|auth| (auth.username.clone(), auth.password.clone())); - let client = Arc::new(SequencerClient::new_with_auth( + let sequencer_client = Arc::new(SequencerClient::new_with_auth( config.sequencer_addr.clone(), basic_auth, )?); - let tx_poller = TxPoller::new(config.clone(), client.clone()); + let tx_poller = TxPoller::new(config.clone(), Arc::clone(&sequencer_client)); - let storage = WalletChainStore::new_storage(config, password)?; + let storage = storage_ctor(config)?; Ok(Self { + config_path, + storage_path, storage, poller: tx_poller, - sequencer_client: client.clone(), - last_synced_block: 0, + sequencer_client, + last_synced_block, }) } - /// Store persistent data at home - pub async fn store_persistent_data(&self) -> Result { - let home = get_home()?; - let storage_path = home.join("storage.json"); + /// Get configuration with applied overrides + pub fn config(&self) -> &WalletConfig { + &self.storage.wallet_config + } - let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block); - let storage = serde_json::to_vec_pretty(&data)?; + /// Get storage + pub fn storage(&self) -> &WalletChainStore { + &self.storage + } - let mut storage_file = tokio::fs::File::create(storage_path.as_path()).await?; - storage_file.write_all(&storage).await?; - - info!("Stored data at {storage_path:#?}"); - - Ok(storage_path) + /// Reset storage + pub fn reset_storage(&mut self, password: String) -> Result<()> { + self.storage = WalletChainStore::new_storage(self.storage.wallet_config.clone(), password)?; + Ok(()) } /// Store persistent data at home - pub async fn store_config_changes(&self) -> Result { - let home = get_home()?; - let config_path = home.join("wallet_config.json"); + pub async fn store_persistent_data(&self) -> Result<()> { + let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block); + let storage = serde_json::to_vec_pretty(&data)?; + + let mut storage_file = tokio::fs::File::create(&self.storage_path).await?; + storage_file.write_all(&storage).await?; + + println!("Stored persistent accounts at {:#?}", self.storage_path); + + Ok(()) + } + + /// Store persistent data at home + pub async fn store_config_changes(&self) -> Result<()> { let config = serde_json::to_vec_pretty(&self.storage.wallet_config)?; - let mut config_file = tokio::fs::File::create(config_path.as_path()).await?; + let mut config_file = tokio::fs::File::create(&self.config_path).await?; config_file.write_all(&config).await?; - info!("Stored data at {config_path:#?}"); + info!("Stored data at {:#?}", self.config_path); - Ok(config_path) + Ok(()) } pub fn create_new_account_public( @@ -218,36 +343,40 @@ impl WalletCore { pub fn decode_insert_privacy_preserving_transaction_results( &mut self, tx: nssa::privacy_preserving_transaction::PrivacyPreservingTransaction, - acc_decode_data: &[(nssa_core::SharedSecretKey, AccountId)], + acc_decode_mask: &[AccDecodeData], ) -> Result<()> { - for (output_index, (secret, acc_account_id)) in acc_decode_data.iter().enumerate() { - let acc_ead = tx.message.encrypted_private_post_states[output_index].clone(); - let acc_comm = tx.message.new_commitments[output_index].clone(); + for (output_index, acc_decode_data) in acc_decode_mask.iter().enumerate() { + match acc_decode_data { + AccDecodeData::Decode(secret, acc_account_id) => { + let acc_ead = tx.message.encrypted_private_post_states[output_index].clone(); + let acc_comm = tx.message.new_commitments[output_index].clone(); - let res_acc = nssa_core::EncryptionScheme::decrypt( - &acc_ead.ciphertext, - secret, - &acc_comm, - output_index as u32, - ) - .unwrap(); + let res_acc = nssa_core::EncryptionScheme::decrypt( + &acc_ead.ciphertext, + secret, + &acc_comm, + output_index as u32, + ) + .unwrap(); - println!("Received new acc {res_acc:#?}"); + println!("Received new acc {res_acc:#?}"); - self.storage - .insert_private_account_data(*acc_account_id, res_acc); + self.storage + .insert_private_account_data(*acc_account_id, res_acc); + } + AccDecodeData::Skip => {} + } } println!("Transaction data is {:?}", tx.message); - Ok(()) } pub async fn send_privacy_preserving_tx( &self, accounts: Vec, - instruction_data: &InstructionData, - program: &Program, + instruction_data: InstructionData, + program: &ProgramWithDependencies, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| { Ok(()) @@ -258,8 +387,8 @@ impl WalletCore { pub async fn send_privacy_preserving_tx_with_pre_check( &self, accounts: Vec, - instruction_data: &InstructionData, - program: &Program, + instruction_data: InstructionData, + program: &ProgramWithDependencies, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { let acc_manager = privacy_preserving_tx::AccountManager::new(self, accounts).await?; @@ -274,16 +403,17 @@ impl WalletCore { let private_account_keys = acc_manager.private_account_keys(); let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( - &pre_states, + pre_states, instruction_data, - acc_manager.visibility_mask(), - &produce_random_nonces(private_account_keys.len()), - &private_account_keys + acc_manager.visibility_mask().to_vec(), + produce_random_nonces(private_account_keys.len()), + private_account_keys .iter() - .map(|keys| (keys.npk.clone(), keys.ssk.clone())) + .map(|keys| (keys.npk.clone(), keys.ssk)) .collect::>(), - &acc_manager.private_account_auth(), - &program.to_owned().into(), + acc_manager.private_account_auth(), + acc_manager.private_account_membership_proofs(), + &program.to_owned(), ) .unwrap(); @@ -303,7 +433,7 @@ impl WalletCore { nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( &message, proof, - &acc_manager.witness_signing_keys(), + &acc_manager.public_account_auth(), ); let tx = PrivacyPreservingTransaction::new(message, witness_set); diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 708b1fcf..045b1b83 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,36 +1,65 @@ -use anyhow::Result; +use anyhow::{Context as _, Result}; use clap::{CommandFactory as _, Parser as _}; -use tokio::runtime::Builder; -use wallet::cli::{Args, execute_continuous_run_with_auth, execute_subcommand_with_auth}; - -pub const NUM_THREADS: usize = 2; +use wallet::{ + WalletCore, + cli::{Args, execute_continuous_run, execute_subcommand, read_password_from_stdin}, + config::WalletConfigOverrides, + helperfunctions::{fetch_config_path, fetch_persistent_storage_path}, +}; // TODO #169: We have sample configs for sequencer, but not for wallet // TODO #168: Why it requires config as a directory? Maybe better to deduce directory from config // file path? // TODO #172: Why it requires config as env var while sequencer_runner accepts as // argument? -fn main() -> Result<()> { - let runtime = Builder::new_multi_thread() - .worker_threads(NUM_THREADS) - .enable_all() - .build() - .unwrap(); - - let args = Args::parse(); +#[tokio::main] +async fn main() -> Result<()> { + let Args { + continuous_run, + auth, + command, + } = Args::parse(); env_logger::init(); - runtime.block_on(async move { - if let Some(command) = args.command { - let _output = execute_subcommand_with_auth(command, args.auth).await?; - Ok(()) - } else if args.continuous_run { - execute_continuous_run_with_auth(args.auth).await + let config_path = fetch_config_path().context("Could not fetch config path")?; + let storage_path = + fetch_persistent_storage_path().context("Could not fetch persistent storage path")?; + + // Override basic auth if provided via CLI + let config_overrides = WalletConfigOverrides { + basic_auth: auth.map(|auth| auth.parse()).transpose()?.map(Some), + ..Default::default() + }; + + if let Some(command) = command { + let mut wallet = if !storage_path.exists() { + // TODO: Maybe move to `WalletCore::from_env()` or similar? + + println!("Persistent storage not found, need to execute setup"); + + let password = read_password_from_stdin()?; + let wallet = WalletCore::new_init_storage( + config_path, + storage_path, + Some(config_overrides), + password, + )?; + + wallet.store_persistent_data().await?; + wallet } else { - let help = Args::command().render_long_help(); - println!("{help}"); - Ok(()) - } - }) + WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))? + }; + let _output = execute_subcommand(&mut wallet, command).await?; + Ok(()) + } else if continuous_run { + let mut wallet = + WalletCore::new_update_chain(config_path, storage_path, Some(config_overrides))?; + execute_continuous_run(&mut wallet).await + } else { + let help = Args::command().render_long_help(); + println!("{help}"); + Ok(()) + } } diff --git a/wallet/src/pinata_interactions.rs b/wallet/src/pinata_interactions.rs index 65a67b78..58047688 100644 --- a/wallet/src/pinata_interactions.rs +++ b/wallet/src/pinata_interactions.rs @@ -58,7 +58,8 @@ impl WalletCore { &[0, 1], &produce_random_nonces(1), &[(winner_npk.clone(), shared_secret_winner.clone())], - &[(winner_nsk.unwrap(), winner_proof)], + &[(winner_nsk.unwrap())], + &[winner_proof], &program.into(), ) .unwrap(); @@ -125,6 +126,7 @@ impl WalletCore { &produce_random_nonces(1), &[(winner_npk.clone(), shared_secret_winner.clone())], &[], + &[] &program.into(), ) .unwrap(); diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs index e79bbac3..df2fc539 100644 --- a/wallet/src/privacy_preserving_tx.rs +++ b/wallet/src/privacy_preserving_tx.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use common::error::ExecutionFailureKind; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; use nssa::{AccountId, PrivateKey}; @@ -9,6 +10,7 @@ use nssa_core::{ use crate::WalletCore; +#[derive(Clone)] pub enum PrivacyPreservingAccount { Public(AccountId), PrivateOwned(AccountId), @@ -18,6 +20,19 @@ pub enum PrivacyPreservingAccount { }, } +impl PrivacyPreservingAccount { + pub fn is_public(&self) -> bool { + matches!(&self, Self::Public(_)) + } + + pub fn is_private(&self) -> bool { + matches!( + &self, + Self::PrivateOwned(_) | Self::PrivateForeign { npk: _, ipk: _ } + ) + } +} + pub struct PrivateAccountKeys { pub npk: NullifierPublicKey, pub ssk: SharedSecretKey, @@ -133,11 +148,21 @@ impl AccountManager { .collect() } - pub fn private_account_auth(&self) -> Vec<(NullifierSecretKey, MembershipProof)> { + pub fn private_account_auth(&self) -> Vec { self.states .iter() .filter_map(|state| match state { - State::Private(pre) => Some((pre.nsk?, pre.proof.clone()?)), + State::Private(pre) => pre.nsk, + _ => None, + }) + .collect() + } + + pub fn private_account_membership_proofs(&self) -> Vec> { + self.states + .iter() + .filter_map(|state| match state { + State::Private(pre) => Some(pre.proof.clone()), _ => None, }) .collect() @@ -153,7 +178,7 @@ impl AccountManager { .collect() } - pub fn witness_signing_keys(&self) -> Vec<&PrivateKey> { + pub fn public_account_auth(&self) -> Vec<&PrivateKey> { self.states .iter() .filter_map(|state| match state { @@ -185,7 +210,7 @@ async fn private_acc_preparation( return Err(ExecutionFailureKind::KeyNotFoundError); }; - let mut nsk = Some(from_keys.private_key_holder.nullifier_secret_key); + let nsk = from_keys.private_key_holder.nullifier_secret_key; let from_npk = from_keys.nullifer_public_key; let from_ipk = from_keys.incoming_viewing_public_key; @@ -196,14 +221,12 @@ async fn private_acc_preparation( .await .unwrap(); - if proof.is_none() { - nsk = None; - } - - let sender_pre = AccountWithMetadata::new(from_acc.clone(), proof.is_some(), &from_npk); + // TODO: Technically we could allow unauthorized owned accounts, but currently we don't have + // support from that in the wallet. + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, &from_npk); Ok(AccountPreparedData { - nsk, + nsk: Some(nsk), npk: from_npk, ipk: from_ipk, pre_state: sender_pre, diff --git a/wallet/src/program_facades/amm.rs b/wallet/src/program_facades/amm.rs new file mode 100644 index 00000000..3beb92cb --- /dev/null +++ b/wallet/src/program_facades/amm.rs @@ -0,0 +1,539 @@ +use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse}; +use nssa::{AccountId, ProgramId, program::Program}; +use nssa_core::program::PdaSeed; + +use crate::{TokenHolding, WalletCore}; + +fn compute_pool_pda( + amm_program_id: ProgramId, + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, +) -> AccountId { + AccountId::from(( + &amm_program_id, + &compute_pool_pda_seed(definition_token_a_id, definition_token_b_id), + )) +} + +fn compute_pool_pda_seed( + definition_token_a_id: AccountId, + definition_token_b_id: AccountId, +) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut i: usize = 0; + let (token_1, token_2) = loop { + if definition_token_a_id.value()[i] > definition_token_b_id.value()[i] { + let token_1 = definition_token_a_id; + let token_2 = definition_token_b_id; + break (token_1, token_2); + } else if definition_token_a_id.value()[i] < definition_token_b_id.value()[i] { + let token_1 = definition_token_b_id; + let token_2 = definition_token_a_id; + break (token_1, token_2); + } + + if i == 32 { + panic!("Definitions match"); + } else { + i += 1; + } + }; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&token_1.to_bytes()); + bytes[32..].copy_from_slice(&token_2.to_bytes()); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) +} + +fn compute_vault_pda( + amm_program_id: ProgramId, + pool_id: AccountId, + definition_token_id: AccountId, +) -> AccountId { + AccountId::from(( + &amm_program_id, + &compute_vault_pda_seed(pool_id, definition_token_id), + )) +} + +fn compute_vault_pda_seed(pool_id: AccountId, definition_token_id: AccountId) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&pool_id.to_bytes()); + bytes[32..].copy_from_slice(&definition_token_id.to_bytes()); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) +} + +fn compute_liquidity_token_pda(amm_program_id: ProgramId, pool_id: AccountId) -> AccountId { + AccountId::from((&amm_program_id, &compute_liquidity_token_pda_seed(pool_id))) +} + +fn compute_liquidity_token_pda_seed(pool_id: AccountId) -> PdaSeed { + use risc0_zkvm::sha::{Impl, Sha256}; + + let mut bytes = [0; 64]; + bytes[0..32].copy_from_slice(&pool_id.to_bytes()); + bytes[32..].copy_from_slice(&[0; 32]); + + PdaSeed::new( + Impl::hash_bytes(&bytes) + .as_bytes() + .try_into() + .expect("Hash output must be exactly 32 bytes long"), + ) +} + +pub struct Amm<'w>(pub &'w WalletCore); + +impl Amm<'_> { + pub async fn send_new_definition( + &self, + user_holding_a: AccountId, + user_holding_b: AccountId, + user_holding_lp: AccountId, + balance_a: u128, + balance_b: u128, + ) -> Result { + let (instruction, program) = amm_program_preparation_definition(balance_a, balance_b); + + let amm_program_id = Program::amm().id(); + + let user_a_acc = self + .0 + .get_account_public(user_holding_a) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + let user_b_acc = self + .0 + .get_account_public(user_holding_b) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let definition_token_a_id = TokenHolding::parse(&user_a_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))? + .definition_id; + let definition_token_b_id = TokenHolding::parse(&user_b_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))? + .definition_id; + + let amm_pool = + compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id); + let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id); + let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id); + let pool_lp = compute_liquidity_token_pda(amm_program_id, amm_pool); + + let account_ids = vec![ + amm_pool, + vault_holding_a, + vault_holding_b, + pool_lp, + user_holding_a, + user_holding_b, + user_holding_lp, + ]; + + let nonces = self + .0 + .get_accounts_nonces(vec![user_holding_a, user_holding_b]) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let signing_key_a = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_a) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + + let signing_key_b = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_b) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + + let message = nssa::public_transaction::Message::try_new( + program.id(), + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message( + &message, + &[signing_key_a, signing_key_b], + ); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_swap( + &self, + user_holding_a: AccountId, + user_holding_b: AccountId, + amount_in: u128, + min_amount_out: u128, + token_definition_id: AccountId, + ) -> Result { + let (instruction, program) = + amm_program_preparation_swap(amount_in, min_amount_out, token_definition_id); + + let amm_program_id = Program::amm().id(); + + let user_a_acc = self + .0 + .get_account_public(user_holding_a) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + let user_b_acc = self + .0 + .get_account_public(user_holding_b) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let definition_token_a_id = TokenHolding::parse(&user_a_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))? + .definition_id; + let definition_token_b_id = TokenHolding::parse(&user_b_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_b))? + .definition_id; + + let amm_pool = + compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id); + let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id); + let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id); + + let account_ids = vec![ + amm_pool, + vault_holding_a, + vault_holding_b, + user_holding_a, + user_holding_b, + ]; + + let account_id_auth; + + // Checking, which account are associated with TokenDefinition + let token_holder_acc_a = self + .0 + .get_account_public(user_holding_a) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + let token_holder_acc_b = self + .0 + .get_account_public(user_holding_b) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let token_holder_a = TokenHolding::parse(&token_holder_acc_a.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))?; + let token_holder_b = TokenHolding::parse(&token_holder_acc_b.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_b))?; + + if token_holder_a.definition_id == token_definition_id { + account_id_auth = user_holding_a; + } else if token_holder_b.definition_id == token_definition_id { + account_id_auth = user_holding_b; + } else { + return Err(ExecutionFailureKind::AccountDataError(token_definition_id)); + } + + let nonces = self + .0 + .get_accounts_nonces(vec![account_id_auth]) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let signing_key = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&account_id_auth) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + + let message = nssa::public_transaction::Message::try_new( + program.id(), + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_add_liquidity( + &self, + user_holding_a: AccountId, + user_holding_b: AccountId, + user_holding_lp: AccountId, + min_amount_lp: u128, + max_amount_a: u128, + max_amount_b: u128, + ) -> Result { + let (instruction, program) = + amm_program_preparation_add_liq(min_amount_lp, max_amount_a, max_amount_b); + + let amm_program_id = Program::amm().id(); + + let user_a_acc = self + .0 + .get_account_public(user_holding_a) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + let user_b_acc = self + .0 + .get_account_public(user_holding_b) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let definition_token_a_id = TokenHolding::parse(&user_a_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))? + .definition_id; + let definition_token_b_id = TokenHolding::parse(&user_b_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))? + .definition_id; + + let amm_pool = + compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id); + let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id); + let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id); + let pool_lp = compute_liquidity_token_pda(amm_program_id, amm_pool); + + let account_ids = vec![ + amm_pool, + vault_holding_a, + vault_holding_b, + pool_lp, + user_holding_a, + user_holding_b, + user_holding_lp, + ]; + + let nonces = self + .0 + .get_accounts_nonces(vec![user_holding_a, user_holding_b]) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let signing_key_a = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_a) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + + let signing_key_b = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_b) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + + let message = nssa::public_transaction::Message::try_new( + program.id(), + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message( + &message, + &[signing_key_a, signing_key_b], + ); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_remove_liquidity( + &self, + user_holding_a: AccountId, + user_holding_b: AccountId, + user_holding_lp: AccountId, + balance_lp: u128, + min_amount_a: u128, + min_amount_b: u128, + ) -> Result { + let (instruction, program) = + amm_program_preparation_remove_liq(balance_lp, min_amount_a, min_amount_b); + + let amm_program_id = Program::amm().id(); + + let user_a_acc = self + .0 + .get_account_public(user_holding_a) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + let user_b_acc = self + .0 + .get_account_public(user_holding_b) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let definition_token_a_id = TokenHolding::parse(&user_a_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))? + .definition_id; + let definition_token_b_id = TokenHolding::parse(&user_b_acc.data) + .ok_or(ExecutionFailureKind::AccountDataError(user_holding_a))? + .definition_id; + + let amm_pool = + compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id); + let vault_holding_a = compute_vault_pda(amm_program_id, amm_pool, definition_token_a_id); + let vault_holding_b = compute_vault_pda(amm_program_id, amm_pool, definition_token_b_id); + let pool_lp = compute_liquidity_token_pda(amm_program_id, amm_pool); + + let account_ids = vec![ + amm_pool, + vault_holding_a, + vault_holding_b, + pool_lp, + user_holding_a, + user_holding_b, + user_holding_lp, + ]; + + let nonces = self + .0 + .get_accounts_nonces(vec![user_holding_lp]) + .await + .map_err(|_| ExecutionFailureKind::SequencerError)?; + + let signing_key_lp = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&user_holding_lp) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + + let message = nssa::public_transaction::Message::try_new( + program.id(), + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key_lp]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } +} + +fn amm_program_preparation_definition(balance_a: u128, balance_b: u128) -> (Vec, Program) { + // An instruction data of 65-bytes, indicating the initial amm reserves' balances and + // token_program_id with the following layout: + // [0x00 || array of balances (little-endian 16 bytes) || AMM_PROGRAM_ID)] + let amm_program_id = Program::amm().id(); + + let mut instruction = [0; 65]; + instruction[1..17].copy_from_slice(&balance_a.to_le_bytes()); + instruction[17..33].copy_from_slice(&balance_b.to_le_bytes()); + + // This can be done less verbose, but it is better to use same way, as in amm program + instruction[33..37].copy_from_slice(&amm_program_id[0].to_le_bytes()); + instruction[37..41].copy_from_slice(&amm_program_id[1].to_le_bytes()); + instruction[41..45].copy_from_slice(&amm_program_id[2].to_le_bytes()); + instruction[45..49].copy_from_slice(&amm_program_id[3].to_le_bytes()); + instruction[49..53].copy_from_slice(&amm_program_id[4].to_le_bytes()); + instruction[53..57].copy_from_slice(&amm_program_id[5].to_le_bytes()); + instruction[57..61].copy_from_slice(&amm_program_id[6].to_le_bytes()); + instruction[61..].copy_from_slice(&amm_program_id[7].to_le_bytes()); + + let instruction_data = instruction.to_vec(); + let program = Program::amm(); + + (instruction_data, program) +} + +fn amm_program_preparation_swap( + amount_in: u128, + min_amount_out: u128, + token_definition_id: AccountId, +) -> (Vec, Program) { + // An instruction data byte string of length 65, indicating which token type to swap, quantity + // of tokens put into the swap (of type TOKEN_DEFINITION_ID) and min_amount_out. + // [0x01 || amount (little-endian 16 bytes) || TOKEN_DEFINITION_ID]. + let mut instruction = [0; 65]; + instruction[0] = 0x01; + instruction[1..17].copy_from_slice(&amount_in.to_le_bytes()); + instruction[17..33].copy_from_slice(&min_amount_out.to_le_bytes()); + + // This can be done less verbose, but it is better to use same way, as in amm program + instruction[33..].copy_from_slice(&token_definition_id.to_bytes()); + + let instruction_data = instruction.to_vec(); + let program = Program::amm(); + + (instruction_data, program) +} + +fn amm_program_preparation_add_liq( + min_amount_lp: u128, + max_amount_a: u128, + max_amount_b: u128, +) -> (Vec, Program) { + // An instruction data byte string of length 49, amounts for minimum amount of liquidity from + // add (min_amount_lp), max amount added for each token (max_amount_a and max_amount_b); + // indicate [0x02 || array of of balances (little-endian 16 bytes)]. + let mut instruction = [0; 49]; + instruction[0] = 0x02; + + instruction[1..17].copy_from_slice(&min_amount_lp.to_le_bytes()); + instruction[17..33].copy_from_slice(&max_amount_a.to_le_bytes()); + instruction[33..49].copy_from_slice(&max_amount_b.to_le_bytes()); + + let instruction_data = instruction.to_vec(); + let program = Program::amm(); + + (instruction_data, program) +} + +fn amm_program_preparation_remove_liq( + balance_lp: u128, + min_amount_a: u128, + min_amount_b: u128, +) -> (Vec, Program) { + // An instruction data byte string of length 49, amounts for minimum amount of liquidity to + // redeem (balance_lp), minimum balance of each token to remove (min_amount_a and + // min_amount_b); indicate [0x03 || array of balances (little-endian 16 bytes)]. + let mut instruction = [0; 49]; + instruction[0] = 0x03; + + instruction[1..17].copy_from_slice(&balance_lp.to_le_bytes()); + instruction[17..33].copy_from_slice(&min_amount_a.to_le_bytes()); + instruction[33..49].copy_from_slice(&min_amount_b.to_le_bytes()); + + let instruction_data = instruction.to_vec(); + let program = Program::amm(); + + (instruction_data, program) +} diff --git a/wallet/src/program_facades/mod.rs b/wallet/src/program_facades/mod.rs index 27d30ce3..5fdcdb39 100644 --- a/wallet/src/program_facades/mod.rs +++ b/wallet/src/program_facades/mod.rs @@ -1,6 +1,7 @@ //! This module contains [`WalletCore`](crate::WalletCore) facades for interacting with various //! on-chain programs. +pub mod amm; pub mod native_token_transfer; pub mod pinata; pub mod token; diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs index 35a13ba1..7b774595 100644 --- a/wallet/src/program_facades/native_token_transfer/deshielded.rs +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -19,8 +19,8 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::Public(to), ], - &instruction_data, - &program, + instruction_data, + &program.into(), tx_pre_check, ) .await diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index 320027bb..eabf1b60 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -15,11 +15,10 @@ impl NativeTokenTransfer<'_> { let instruction: u128 = 0; self.0 - .send_privacy_preserving_tx_with_pre_check( + .send_privacy_preserving_tx( vec![PrivacyPreservingAccount::PrivateOwned(from)], - &Program::serialize_instruction(instruction).unwrap(), - &Program::authenticated_transfer_program(), - |_| Ok(()), + Program::serialize_instruction(instruction).unwrap(), + &Program::authenticated_transfer_program().into(), ) .await .map(|(resp, secrets)| { @@ -47,8 +46,8 @@ impl NativeTokenTransfer<'_> { ipk: to_ipk, }, ], - &instruction_data, - &program, + instruction_data, + &program.into(), tx_pre_check, ) .await @@ -74,8 +73,8 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::PrivateOwned(to), ], - &instruction_data, - &program, + instruction_data, + &program.into(), tx_pre_check, ) .await diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index 0802d6ed..85c8145c 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -20,8 +20,8 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::Public(from), PrivacyPreservingAccount::PrivateOwned(to), ], - &instruction_data, - &program, + instruction_data, + &program.into(), tx_pre_check, ) .await @@ -52,8 +52,8 @@ impl NativeTokenTransfer<'_> { ipk: to_ipk, }, ], - &instruction_data, - &program, + instruction_data, + &program.into(), tx_pre_check, ) .await diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index 41e75101..6036a603 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -37,8 +37,8 @@ impl Pinata<'_> { PrivacyPreservingAccount::Public(pinata_account_id), PrivacyPreservingAccount::PrivateOwned(winner_account_id), ], - &nssa::program::Program::serialize_instruction(solution).unwrap(), - &nssa::program::Program::pinata(), + nssa::program::Program::serialize_instruction(solution).unwrap(), + &nssa::program::Program::pinata().into(), ) .await .map(|(resp, secrets)| { diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index 4ec9c127..0d3f79d7 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -1,9 +1,6 @@ use common::{error::ExecutionFailureKind, rpc_primitives::requests::SendTxResponse}; use nssa::{AccountId, program::Program}; -use nssa_core::{ - NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey, - program::InstructionData, -}; +use nssa_core::{NullifierPublicKey, SharedSecretKey, encryption::IncomingViewingPublicKey}; use crate::{PrivacyPreservingAccount, WalletCore}; @@ -20,7 +17,7 @@ impl Token<'_> { let account_ids = vec![definition_account_id, supply_account_id]; let program_id = nssa::program::Program::token().id(); // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(&name); let message = nssa::public_transaction::Message::try_new( @@ -45,7 +42,9 @@ impl Token<'_> { name: [u8; 6], total_supply: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + let instruction = token_program_preparation_definition(name, total_supply); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -53,8 +52,8 @@ impl Token<'_> { PrivacyPreservingAccount::Public(definition_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -73,7 +72,9 @@ impl Token<'_> { name: [u8; 6], total_supply: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + let instruction = token_program_preparation_definition(name, total_supply); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -81,8 +82,8 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(supply_account_id), ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -101,7 +102,9 @@ impl Token<'_> { name: [u8; 6], total_supply: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_definition(name, total_supply); + let instruction = token_program_preparation_definition(name, total_supply); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -109,8 +112,8 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -131,7 +134,7 @@ impl Token<'_> { let program_id = nssa::program::Program::token().id(); // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // 0x00 || 0x00 || 0x00]. - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[0] = 0x01; instruction[1..17].copy_from_slice(&amount.to_le_bytes()); let Ok(nonces) = self.0.get_accounts_nonces(vec![sender_account_id]).await else { @@ -167,7 +170,9 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -175,8 +180,8 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -194,7 +199,9 @@ impl Token<'_> { recipient_ipk: IncomingViewingPublicKey, amount: u128, ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -205,8 +212,8 @@ impl Token<'_> { ipk: recipient_ipk, }, ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -223,7 +230,9 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -231,8 +240,8 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::Public(recipient_account_id), ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -250,7 +259,9 @@ impl Token<'_> { recipient_account_id: AccountId, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -258,8 +269,8 @@ impl Token<'_> { PrivacyPreservingAccount::Public(sender_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -278,7 +289,9 @@ impl Token<'_> { recipient_ipk: IncomingViewingPublicKey, amount: u128, ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { - let (instruction_data, program) = token_program_preparation_transfer(amount); + let instruction = token_program_preparation_transfer(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); self.0 .send_privacy_preserving_tx( @@ -289,8 +302,8 @@ impl Token<'_> { ipk: recipient_ipk, }, ], - &instruction_data, - &program, + instruction_data, + &Program::token().into(), ) .await .map(|(resp, secrets)| { @@ -301,30 +314,354 @@ impl Token<'_> { (resp, first) }) } + + pub async fn send_burn_transaction( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result { + let account_ids = vec![definition_account_id, holder_account_id]; + let instruction = token_program_preparation_burn(amount); + + let Ok(nonces) = self.0.get_accounts_nonces(vec![holder_account_id]).await else { + return Err(ExecutionFailureKind::SequencerError); + }; + let message = nssa::public_transaction::Message::try_new( + Program::token().id(), + account_ids, + nonces, + instruction, + ) + .expect("Instruction should serialize"); + + let signing_key = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&holder_account_id) + .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_burn_transaction_private_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let instruction = token_program_preparation_burn(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected definition's secret"); + let second = iter.next().expect("expected holder's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_burn_transaction_deshielded_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_burn(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::Public(holder_account_id), + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected definition's secret"); + (resp, first) + }) + } + + pub async fn send_burn_transaction_shielded( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_burn(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected holder's secret"); + (resp, first) + }) + } + + pub async fn send_mint_transaction( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result { + let account_ids = vec![definition_account_id, holder_account_id]; + let instruction = token_program_preparation_mint(amount); + + let Ok(nonces) = self + .0 + .get_accounts_nonces(vec![definition_account_id]) + .await + else { + return Err(ExecutionFailureKind::SequencerError); + }; + let message = nssa::public_transaction::Message::try_new( + Program::token().id(), + account_ids, + nonces, + instruction, + ) + .unwrap(); + + let Some(signing_key) = self + .0 + .storage + .user_data + .get_pub_account_signing_key(&definition_account_id) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + let witness_set = + nssa::public_transaction::WitnessSet::for_message(&message, &[signing_key]); + + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.0.sequencer_client.send_tx_public(tx).await?) + } + + pub async fn send_mint_transaction_private_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected definition's secret"); + let second = iter.next().expect("expected holder's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_mint_transaction_private_foreign_account( + &self, + definition_account_id: AccountId, + holder_npk: NullifierPublicKey, + holder_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result<(SendTxResponse, [SharedSecretKey; 2]), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: holder_npk, + ipk: holder_ipk, + }, + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let mut iter = secrets.into_iter(); + let first = iter.next().expect("expected definition's secret"); + let second = iter.next().expect("expected holder's secret"); + (resp, [first, second]) + }) + } + + pub async fn send_mint_transaction_deshielded( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::Public(holder_account_id), + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected definition's secret"); + (resp, first) + }) + } + + pub async fn send_mint_transaction_shielded_owned_account( + &self, + definition_account_id: AccountId, + holder_account_id: AccountId, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected holder's secret"); + (resp, first) + }) + } + + pub async fn send_mint_transaction_shielded_foreign_account( + &self, + definition_account_id: AccountId, + holder_npk: NullifierPublicKey, + holder_ipk: IncomingViewingPublicKey, + amount: u128, + ) -> Result<(SendTxResponse, SharedSecretKey), ExecutionFailureKind> { + let instruction = token_program_preparation_mint(amount); + let instruction_data = + Program::serialize_instruction(instruction).expect("Instruction should serialize"); + + self.0 + .send_privacy_preserving_tx( + vec![ + PrivacyPreservingAccount::Public(definition_account_id), + PrivacyPreservingAccount::PrivateForeign { + npk: holder_npk, + ipk: holder_ipk, + }, + ], + instruction_data, + &Program::token().into(), + ) + .await + .map(|(resp, secrets)| { + let first = secrets + .into_iter() + .next() + .expect("expected holder's secret"); + (resp, first) + }) + } } -fn token_program_preparation_transfer(amount: u128) -> (InstructionData, Program) { +fn token_program_preparation_transfer(amount: u128) -> Vec { // Instruction must be: [0x01 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || // 0x00 || 0x00 || 0x00]. - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[0] = 0x01; instruction[1..17].copy_from_slice(&amount.to_le_bytes()); - let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let program = Program::token(); - (instruction_data, program) + instruction } -fn token_program_preparation_definition( - name: [u8; 6], - total_supply: u128, -) -> (InstructionData, Program) { +fn token_program_preparation_definition(name: [u8; 6], total_supply: u128) -> Vec { // Instruction must be: [0x00 || total_supply (little-endian 16 bytes) || name (6 bytes)] - let mut instruction = [0; 23]; + let mut instruction = vec![0u8; 23]; instruction[1..17].copy_from_slice(&total_supply.to_le_bytes()); instruction[17..].copy_from_slice(&name); - let instruction_data = Program::serialize_instruction(instruction).unwrap(); - let program = Program::token(); - (instruction_data, program) + instruction +} + +fn token_program_preparation_burn(amount: u128) -> Vec { + // Instruction must be: [0x03 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || + // 0x00 || 0x00 || 0x00]. + let mut instruction = vec![0; 23]; + instruction[0] = 0x03; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + instruction +} + +fn token_program_preparation_mint(amount: u128) -> Vec { + // Instruction must be: [0x04 || amount (little-endian 16 bytes) || 0x00 || 0x00 || 0x00 || + // 0x00 || 0x00 || 0x00]. + let mut instruction = vec![0; 23]; + instruction[0] = 0x04; + instruction[1..17].copy_from_slice(&amount.to_le_bytes()); + + instruction }