From 0e0abb2aa88e2bcf9554f5ea8e38b974e7d44f0e Mon Sep 17 00:00:00 2001 From: stpatrickghost <244839222+stpatrickghost@users.noreply.github.com> Date: Sat, 29 Aug 2026 15:57:27 +0000 Subject: [PATCH 1/2] chore(perf): auto-update Stellar PERF.md bench table --- stellar/PERF.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stellar/PERF.md b/stellar/PERF.md index 6c35c2a..ca940ca 100644 --- a/stellar/PERF.md +++ b/stellar/PERF.md @@ -68,7 +68,7 @@ ownership checks. ## Current Numbers -These are the harness results auto-updated from `develop` (measured 2026-08-28, commit `ac1d79ff9a35`). +These are the harness results auto-updated from `develop` (measured 2026-08-29, commit `ff49946cbc21`). | Contract | Function | Parameters | Instructions | Mem bytes | Read entries | Write entries | Read bytes | Write bytes | Event bytes | |---|---|---:|---:|---:|---:|---:|---:|---:|---:| From efa596ce2fe9d5d4d4d5603dd8d1965485ca007d Mon Sep 17 00:00:00 2001 From: Mitch5000 Date: Sat, 29 Aug 2026 17:52:49 +0100 Subject: [PATCH 2/2] Batch-sender REFIX --- stellar/Cargo.lock | 1 + stellar/ERRORS.md | 385 ++-- stellar/EVENT_INDEXING_AUDIT.md | 171 +- stellar/bench-crossover/src/crossover.rs | 8 +- stellar/examples/indexer/.env.example | 4 +- stellar/examples/indexer/src/processor.ts | 92 +- ...tch_sender_batch_send_through_chaos.1.json | 185 +- ...announcer_splitter_and_batch_sender.1.json | 1658 +++++++++++++++++ stellar/integration-tests/tests/chaos.rs | 7 +- .../integration-tests/tests/topic_filter.rs | 215 +++ stellar/stealth-batch-sender/Cargo.toml | 1 + stellar/stealth-batch-sender/README.md | 168 +- stellar/stealth-batch-sender/src/lib.rs | 85 +- stellar/stealth-batch-sender/src/test.rs | 103 +- ...test_atomicity_on_mid_batch_failure.1.json | 88 +- ...h_send_emits_v2_four_topic_announce.1.json | 1101 +++++++++++ ...st_batch_send_rejects_uninitialized.1.json | 399 ++++ .../test/test_batch_send_success.1.json | 115 +- .../test/test_batch_size_cap.1.json | 61 +- .../test/test_empty_batch_rejected.1.json | 61 +- .../test_empty_ephemeral_key_rejected.1.json | 458 +++++ .../test/test_init_rejects_second_call.1.json | 135 ++ .../test/test_zero_amount_rejected.1.json | 61 +- .../stealth-batch-sender/tests/production.rs | 23 +- 24 files changed, 5041 insertions(+), 544 deletions(-) create mode 100644 stellar/integration-tests/test_snapshots/topic_3_view_tag_filter_across_announcer_splitter_and_batch_sender.1.json create mode 100644 stellar/integration-tests/tests/topic_filter.rs create mode 100644 stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_emits_v2_four_topic_announce.1.json create mode 100644 stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_rejects_uninitialized.1.json create mode 100644 stellar/stealth-batch-sender/test_snapshots/test/test_empty_ephemeral_key_rejected.1.json create mode 100644 stellar/stealth-batch-sender/test_snapshots/test/test_init_rejects_second_call.1.json diff --git a/stellar/Cargo.lock b/stellar/Cargo.lock index 4157a46..2874029 100644 --- a/stellar/Cargo.lock +++ b/stellar/Cargo.lock @@ -1620,6 +1620,7 @@ name = "stealth-batch-sender" version = "0.1.0" dependencies = [ "soroban-sdk", + "stealth-announcer", "wraith-metrics", ] diff --git a/stellar/ERRORS.md b/stellar/ERRORS.md index bd6b574..e50abba 100644 --- a/stellar/ERRORS.md +++ b/stellar/ERRORS.md @@ -1,217 +1,180 @@ -# Stellar Contract Error Catalog +Stellar Contract Error Catalog +This catalog maps Soroban #[contracterror] codes to their source variants for SDKs, indexers, and integration tests. -This catalog maps Soroban `#[contracterror]` codes to their source variants for SDKs, indexers, and integration tests. - -`Introduced in` is `pre-catalog` for variants that already existed when this catalog was added on 2026-08-25. New variants should name the PR, issue, or release that introduced the code. - -## Code allocation policy +Introduced in is pre-catalog for variants that already existed when this catalog was added on 2026-08-25. New variants should name the PR, issue, or release that introduced the code. +Code allocation policy Error codes are only unique within a contract enum at the Soroban ABI layer, but Wraith keeps disjoint reserved ranges so logs and SDK helpers can map raw codes without re-deriving context from Rust source. -| Contract or enum | Reserved range | Notes | -|---|---:|---| -| `stealth-announcer` | none | Panic-only/stateless event emitter today. Reserve `1000-1099` if it gains `#[contracterror]`. | -| `stealth-registry` / `RegistryError` | `1100-1199` | Existing deployed codes are `1-2`; do not renumber them. Add future variants in the reserved range unless a breaking ABI migration is planned. | -| `stealth-sender` / `SenderError` | `1200-1299` | Existing deployed codes are `1-16`; do not renumber them. | -| `stealth-batch-sender` / `BatchSenderError` | `1300-1399` | Existing deployed codes are `1300-1316`; do not renumber them. | -| `stealth-vault` / `VaultError` | `1400-1499` | Existing codes are `1-7`; do not renumber them. | -| `stealth-splitter` / `SplitterError` | `1500-1599` | Existing codes are `1-8`; do not renumber them. | -| `wraith-names` / `NamesError` | `1600-1699` | Existing codes are `1-32`; do not renumber them. New variants use the reserved range, starting at `1600`. | -| `wraith-names` / `AuctionError` | `1700-1799` | Existing codes are `100-123` and intentionally disjoint from `NamesError`; do not renumber them. | -| `wraith-asset-policy` | `1800-1899` | Panic-only today; use this range if it gains `#[contracterror]`. | -| `governance` / `GovernanceError` | `1900-1999` | Proof-of-concept governance contract; existing codes are `1-14`. | - +Contract or enum Reserved range Notes +stealth-announcer none Panic-only/stateless event emitter today. Reserve 1000-1099 if it gains #[contracterror]. +stealth-registry / RegistryError 1100-1199 Existing deployed codes are 1-2; do not renumber them. Add future variants in the reserved range unless a breaking ABI migration is planned. +stealth-sender / SenderError 1200-1299 Existing deployed codes are 1-16; do not renumber them. +stealth-batch-sender / BatchSenderError 1300-1399 Existing deployed codes are 1300-1316; do not renumber them. +stealth-vault / VaultError 1400-1499 Existing codes are 1-7; do not renumber them. +stealth-splitter / SplitterError 1500-1599 Existing codes are 1-8; do not renumber them. +wraith-names / NamesError 1600-1699 Existing codes are 1-32; do not renumber them. New variants use the reserved range, starting at 1600. +wraith-names / AuctionError 1700-1799 Existing codes are 100-123 and intentionally disjoint from NamesError; do not renumber them. +wraith-asset-policy 1800-1899 Panic-only today; use this range if it gains #[contracterror]. +governance / GovernanceError 1900-1999 Proof-of-concept governance contract; existing codes are 1-14. For existing public enums, numeric codes are part of the contract ABI. Append new variants; never recycle or renumber old codes. -## stealth-announcer - -No `#[contracterror]` enum is defined. Current validation failures are panics/assertions in [`stealth-announcer/src/lib.rs`](stealth-announcer/src/lib.rs). - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| N/A | Panic-only | `announce` rejects non-v2 scheme IDs or missing view-tag metadata by panic/assertion. | pre-catalog | - -## stealth-registry - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1 | [`RegistryError::InvalidMetaAddressLength`](stealth-registry/src/lib.rs#L59) | Supplied stealth meta-address is not exactly 64 bytes. | pre-catalog | -| 2 | [`RegistryError::NotRegistered`](stealth-registry/src/lib.rs#L61) | No meta-address is registered for the requested address and scheme. | pre-catalog | - -## stealth-sender - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1 | [`SenderError::AlreadyInitialized`](stealth-sender/src/lib.rs#L42) | Contract initialization was attempted more than once. | pre-catalog | -| 2 | [`SenderError::NotInitialized`](stealth-sender/src/lib.rs#L44) | Contract state required for the call has not been initialized. | pre-catalog | -| 3 | [`SenderError::LengthMismatch`](stealth-sender/src/lib.rs#L46) | Batch input vectors do not have the same length. | pre-catalog | -| 4 | [`SenderError::TokenNotAllowed`](stealth-sender/src/lib.rs#L48) | Configured asset policy rejected the token. | pre-catalog | -| 5 | [`SenderError::InvalidFeeConfig`](stealth-sender/src/lib.rs#L50) | Fee basis points or fee-recipient configuration is invalid. | pre-catalog | -| 6 | [`SenderError::BatchTooLarge`](stealth-sender/src/lib.rs#L54) | Withdrawal batch exceeds the supported size cap. | pre-catalog | -| 7 | [`SenderError::MultisigNotInitialized`](stealth-sender/src/lib.rs#L56) | Governance multisig has not been initialized. | pre-catalog | -| 8 | [`SenderError::MultisigAlreadyInitialized`](stealth-sender/src/lib.rs#L58) | Governance multisig initialization was attempted more than once. | pre-catalog | -| 9 | [`SenderError::NotSigner`](stealth-sender/src/lib.rs#L60) | Caller is not a current governance signer. | pre-catalog | -| 10 | [`SenderError::InvalidThreshold`](stealth-sender/src/lib.rs#L62) | Requested multisig threshold is zero or exceeds signer count. | pre-catalog | -| 11 | [`SenderError::RotationAlreadyPending`](stealth-sender/src/lib.rs#L64) | A signer-rotation proposal already exists. | pre-catalog | -| 12 | [`SenderError::NoPendingRotation`](stealth-sender/src/lib.rs#L66) | No signer-rotation proposal exists for this action. | pre-catalog | -| 13 | [`SenderError::AlreadyApprovedRotation`](stealth-sender/src/lib.rs#L68) | Caller already approved the pending rotation. | pre-catalog | -| 14 | [`SenderError::QuorumNotMet`](stealth-sender/src/lib.rs#L70) | Pending rotation lacks enough approvals. | pre-catalog | -| 15 | [`SenderError::TimelockNotElapsed`](stealth-sender/src/lib.rs#L72) | Rotation timelock has not elapsed. | pre-catalog | -| 16 | [`SenderError::Paused`](stealth-sender/src/lib.rs#L52) | Contract is paused. | pre-catalog | - -## stealth-batch-sender - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1300 | [`BatchSenderError::AlreadyInitialized`](stealth-batch-sender/src/lib.rs#L50) | Contract initialization was attempted more than once. | issue #155 | -| 1301 | [`BatchSenderError::NotInitialized`](stealth-batch-sender/src/lib.rs#L52) | `batch_send` was called before `init`. | issue #155 | -| 1302 | [`BatchSenderError::EmptyBatch`](stealth-batch-sender/src/lib.rs#L54) | Batch contains no transfers. | issue #155 | -| 1303 | [`BatchSenderError::BatchTooLarge`](stealth-batch-sender/src/lib.rs#L56) | Batch exceeds `MAX_BATCH_SIZE` (100). | issue #155 | -| 1304 | [`BatchSenderError::NonPositiveAmount`](stealth-batch-sender/src/lib.rs#L58) | A transfer amount is zero or negative. | issue #155 | -| 1305 | [`BatchSenderError::EmptyEphemeralKey`](stealth-batch-sender/src/lib.rs#L60) | A transfer's ephemeral public key is empty. | issue #155 | -| 1306 | [`BatchSenderError::Paused`](stealth-batch-sender/src/lib.rs#L62) | Contract is paused. | issue #155 | -| 1307 | [`BatchSenderError::AssetNotAllowed`](stealth-batch-sender/src/lib.rs#L64) | Configured asset policy rejected the token. | issue #155 | -| 1308 | [`BatchSenderError::MultisigNotInitialized`](stealth-batch-sender/src/lib.rs#L66) | Governance multisig has not been initialized. | issue #155 | -| 1309 | [`BatchSenderError::MultisigAlreadyInitialized`](stealth-batch-sender/src/lib.rs#L68) | Governance multisig initialization was attempted more than once. | issue #155 | -| 1310 | [`BatchSenderError::NotSigner`](stealth-batch-sender/src/lib.rs#L70) | Caller is not a current governance signer. | issue #155 | -| 1311 | [`BatchSenderError::InvalidThreshold`](stealth-batch-sender/src/lib.rs#L72) | Requested multisig threshold is zero or exceeds signer count. | issue #155 | -| 1312 | [`BatchSenderError::RotationAlreadyPending`](stealth-batch-sender/src/lib.rs#L74) | A signer-rotation proposal already exists. | issue #155 | -| 1313 | [`BatchSenderError::NoPendingRotation`](stealth-batch-sender/src/lib.rs#L76) | No signer-rotation proposal exists for this action. | issue #155 | -| 1314 | [`BatchSenderError::AlreadyApprovedRotation`](stealth-batch-sender/src/lib.rs#L78) | Caller already approved the pending rotation. | issue #155 | -| 1315 | [`BatchSenderError::QuorumNotMet`](stealth-batch-sender/src/lib.rs#L80) | Pending rotation lacks enough approvals. | issue #155 | -| 1316 | [`BatchSenderError::TimelockNotElapsed`](stealth-batch-sender/src/lib.rs#L82) | Rotation timelock has not elapsed. | issue #155 | - -## stealth-vault - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1 | [`VaultError::AlreadyInitialized`](stealth-vault/src/lib.rs#L33) | Contract initialization was attempted more than once. | pre-catalog | -| 2 | [`VaultError::NotInitialized`](stealth-vault/src/lib.rs#L34) | Announcer address has not been initialized. | pre-catalog | -| 3 | [`VaultError::InvalidWindow`](stealth-vault/src/lib.rs#L35) | Refund window is not strictly after unlock plus grace period. | pre-catalog | -| 4 | [`VaultError::DepositNotFound`](stealth-vault/src/lib.rs#L36) | Deposit ID is unknown or already consumed. | pre-catalog | -| 5 | [`VaultError::NotYetUnlocked`](stealth-vault/src/lib.rs#L37) | Claim attempted before the unlock ledger. | pre-catalog | -| 6 | [`VaultError::NotYetRefundable`](stealth-vault/src/lib.rs#L38) | Refund attempted before the refund ledger. | pre-catalog | -| 7 | [`VaultError::WrongRecipient`](stealth-vault/src/lib.rs#L39) | Claim signer does not match the deposit recipient. | pre-catalog | -| 8 | [`VaultError::Paused`](stealth-vault/src/lib.rs#L98) | Vault operations are paused. | PR #169 | -| 9 | [`VaultError::NotYetPermissionless`](stealth-vault/src/lib.rs#L100) | Operation attempted before the permissionless threshold has elapsed. | PR #169 | -| 10 | [`VaultError::InvalidGracePeriod`](stealth-vault/src/lib.rs#L102) | Specified grace period parameter is invalid. | PR #169 | - -## stealth-splitter - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1 | [`SplitterError::AlreadyInitialized`](stealth-splitter/src/lib.rs#L60) | Contract initialization was attempted more than once. | pre-catalog | -| 2 | [`SplitterError::NotInitialized`](stealth-splitter/src/lib.rs#L62) | Announcer address has not been initialized. | pre-catalog | -| 3 | [`SplitterError::SplitNotFound`](stealth-splitter/src/lib.rs#L64) | Split ID is unknown, or a fund-split vector count did not match the split definition. | pre-catalog | -| 4 | [`SplitterError::TooManyBeneficiaries`](stealth-splitter/src/lib.rs#L66) | Split creation exceeded the 25-beneficiary cap. | pre-catalog | -| 5 | [`SplitterError::WeightOverflow`](stealth-splitter/src/lib.rs#L68) | Total beneficiary weight overflowed. | pre-catalog | -| 6 | [`SplitterError::InvalidMetaAddressLength`](stealth-splitter/src/lib.rs#L70) | Beneficiary stealth meta-address is not exactly 64 bytes. | pre-catalog | -| 7 | [`SplitterError::InvalidAmount`](stealth-splitter/src/lib.rs#L72) | Fund amount is zero or negative. | pre-catalog | -| 8 | [`SplitterError::EmptyBeneficiaries`](stealth-splitter/src/lib.rs#L74) | Split definition has no beneficiaries. | pre-catalog | - -## wraith-names - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1 | [`NamesError::NameTaken`](wraith-names/src/lib.rs#L87) | Name is already registered. | pre-catalog | -| 2 | [`NamesError::NameTooShort`](wraith-names/src/lib.rs#L88) | Name is shorter than the minimum length. | pre-catalog | -| 3 | [`NamesError::NameTooLong`](wraith-names/src/lib.rs#L89) | Name exceeds the maximum length. | pre-catalog | -| 4 | [`NamesError::InvalidNameCharacter`](wraith-names/src/lib.rs#L90) | Name contains a character outside the allowed set. | pre-catalog | -| 5 | [`NamesError::InvalidMetaAddress`](wraith-names/src/lib.rs#L91) | Stealth meta-address is invalid, usually not 64 bytes. | pre-catalog | -| 6 | [`NamesError::NameNotFound`](wraith-names/src/lib.rs#L92) | Name or reverse lookup entry was not found. | pre-catalog | -| 7 | [`NamesError::NotOwner`](wraith-names/src/lib.rs#L93) | Caller is not authorized as owner or parent owner. | pre-catalog | -| 8 | [`NamesError::SignatureExpired`](wraith-names/src/lib.rs#L94) | On-behalf signature expiry ledger has passed. | pre-catalog | -| 9 | [`NamesError::SignatureReplay`](wraith-names/src/lib.rs#L95) | On-behalf signature was already used. | pre-catalog | -| 10 | [`NamesError::InvalidSigner`](wraith-names/src/lib.rs#L96) | Owner address could not be converted to a supported signer key. | pre-catalog | -| 11 | [`NamesError::NotGuardian`](wraith-names/src/lib.rs#L97) | Caller is not one of the configured guardians. | pre-catalog | -| 12 | [`NamesError::NoProposal`](wraith-names/src/lib.rs#L98) | No recovery proposal exists. | pre-catalog | -| 13 | [`NamesError::ProposalAlreadyExists`](wraith-names/src/lib.rs#L99) | A recovery proposal already exists. | pre-catalog | -| 14 | [`NamesError::AlreadyApproved`](wraith-names/src/lib.rs#L100) | Guardian already approved the proposal. | pre-catalog | -| 15 | [`NamesError::DelayNotElapsed`](wraith-names/src/lib.rs#L101) | Recovery delay has not elapsed. | pre-catalog | -| 16 | [`NamesError::ThresholdNotMet`](wraith-names/src/lib.rs#L102) | Recovery guardian threshold has not been met. | pre-catalog | -| 17 | [`NamesError::TooManyGuardians`](wraith-names/src/lib.rs#L103) | Guardian set exceeds the supported size. | pre-catalog | -| 18 | [`NamesError::InvalidThreshold`](wraith-names/src/lib.rs#L104) | Guardian or multisig threshold is invalid. | pre-catalog | -| 19 | [`NamesError::InvalidExtendLedger`](wraith-names/src/lib.rs#L105) | Requested TTL extension ledger is not in the future. | pre-catalog | -| 20 | [`NamesError::ParentNotFound`](wraith-names/src/lib.rs#L106) | Subdomain parent name does not exist. | pre-catalog | -| 21 | [`NamesError::MultisigNotInitialized`](wraith-names/src/lib.rs#L110) | Protocol governance multisig has not been initialized. | pre-catalog | -| 22 | [`NamesError::MultisigAlreadyInitialized`](wraith-names/src/lib.rs#L112) | Protocol governance multisig initialization was attempted more than once. | pre-catalog | -| 23 | [`NamesError::NotSigner`](wraith-names/src/lib.rs#L114) | Caller is not a current protocol governance signer. | pre-catalog | -| 24 | [`NamesError::RotationAlreadyPending`](wraith-names/src/lib.rs#L116) | A signer-rotation proposal already exists. | pre-catalog | -| 25 | [`NamesError::NoPendingRotation`](wraith-names/src/lib.rs#L118) | No signer-rotation proposal exists for this action. | pre-catalog | -| 26 | [`NamesError::AlreadyApprovedRotation`](wraith-names/src/lib.rs#L120) | Caller already approved the pending rotation. | pre-catalog | -| 27 | [`NamesError::QuorumNotMet`](wraith-names/src/lib.rs#L122) | Pending rotation lacks enough approvals. | pre-catalog | -| 28 | [`NamesError::TimelockNotElapsed`](wraith-names/src/lib.rs#L124) | Rotation timelock has not elapsed. | pre-catalog | -| 29 | [`NamesError::NameTooDeep`](wraith-names/src/lib.rs#L125) | Name exceeds the supported subdomain depth. | pre-catalog | -| 30 | [`NamesError::BulkLimitExceeded`](wraith-names/src/lib.rs#L126) | Bulk operation exceeds the supported item cap. | pre-catalog | -| 31 | [`NamesError::PremiumAuctionRequired`](wraith-names/src/lib.rs#L129) | Premium top-level name must be obtained through auction during the launch window. | pre-catalog | -| 32 | [`NamesError::Paused`](wraith-names/src/lib.rs#L108) | Contract is paused. | pre-catalog | -| 1600 | [`NamesError::AuctionsNotInitialized`](wraith-names/src/lib.rs#L135) | Auction subsystem is not initialized, so there is no auction admin to rotate. | [#165](https://github.com/wraith-protocol/contracts/issues/165) | -| 1601 | [`NamesError::AuctionInProgress`](wraith-names/src/lib.rs#L138) | An auction has a revealed winner and has not settled, so the auction admin cannot be rotated. | [#165](https://github.com/wraith-protocol/contracts/issues/165) | - -## wraith-names auctions - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 100 | [`AuctionError::NotInitialized`](wraith-names/src/auction.rs#L107) | Auction subsystem has not been initialized. | pre-catalog | -| 101 | [`AuctionError::AlreadyInitialized`](wraith-names/src/auction.rs#L108) | Auction subsystem initialization was attempted more than once. | pre-catalog | -| 102 | [`AuctionError::InvalidConfig`](wraith-names/src/auction.rs#L109) | Reserve price or phase durations are invalid. | pre-catalog | -| 103 | [`AuctionError::WindowClosed`](wraith-names/src/auction.rs#L110) | Premium-name launch auction window has closed. | pre-catalog | -| 104 | [`AuctionError::NotPremiumName`](wraith-names/src/auction.rs#L111) | Name is not eligible for premium-name auction handling. | pre-catalog | -| 105 | [`AuctionError::NameAlreadyRegistered`](wraith-names/src/auction.rs#L112) | Name was already registered before auction start or claim. | pre-catalog | -| 106 | [`AuctionError::AuctionExists`](wraith-names/src/auction.rs#L113) | Auction already exists for the name. | pre-catalog | -| 107 | [`AuctionError::NoAuction`](wraith-names/src/auction.rs#L114) | No auction exists for the name. | pre-catalog | -| 108 | [`AuctionError::CommitPhaseOver`](wraith-names/src/auction.rs#L115) | Commit attempted after the commit phase ended. | pre-catalog | -| 109 | [`AuctionError::AlreadyCommitted`](wraith-names/src/auction.rs#L116) | Bidder already committed for this auction. | pre-catalog | -| 110 | [`AuctionError::DepositBelowReserve`](wraith-names/src/auction.rs#L117) | Bid deposit is below the configured reserve price. | pre-catalog | -| 111 | [`AuctionError::RevealPhaseNotActive`](wraith-names/src/auction.rs#L118) | Reveal attempted outside the reveal phase. | pre-catalog | -| 112 | [`AuctionError::NoBid`](wraith-names/src/auction.rs#L119) | No bid exists for the bidder and auction. | pre-catalog | -| 113 | [`AuctionError::AlreadyRevealed`](wraith-names/src/auction.rs#L120) | Bid was already revealed. | pre-catalog | -| 114 | [`AuctionError::CommitmentMismatch`](wraith-names/src/auction.rs#L121) | Revealed amount/salt does not match the stored commitment. | pre-catalog | -| 115 | [`AuctionError::BidBelowReserve`](wraith-names/src/auction.rs#L122) | Revealed bid amount is below reserve. | pre-catalog | -| 116 | [`AuctionError::BidExceedsDeposit`](wraith-names/src/auction.rs#L123) | Revealed bid amount exceeds locked deposit. | pre-catalog | -| 117 | [`AuctionError::RevealPhaseNotOver`](wraith-names/src/auction.rs#L124) | Settle or withdraw attempted before reveal phase ended. | pre-catalog | -| 118 | [`AuctionError::AlreadySettled`](wraith-names/src/auction.rs#L125) | Auction has already been settled. | pre-catalog | -| 119 | [`AuctionError::NotSettled`](wraith-names/src/auction.rs#L126) | Winner tried to claim before settlement. | pre-catalog | -| 120 | [`AuctionError::NotWinner`](wraith-names/src/auction.rs#L127) | Caller is not the winning bidder. | pre-catalog | -| 121 | [`AuctionError::WinnerCannotWithdraw`](wraith-names/src/auction.rs#L128) | Highest bidder cannot self-withdraw before settlement. | pre-catalog | -| 122 | [`AuctionError::InvalidMetaAddress`](wraith-names/src/auction.rs#L129) | Winner claim supplied an invalid stealth meta-address. | pre-catalog | -| 123 | [`AuctionError::RegistrationFailed`](wraith-names/src/auction.rs#L130) | Auction claim could not complete name registration for an unmapped reason. | pre-catalog | - -## wraith-asset-policy - -No `#[contracterror]` enum is defined. This contract is currently panic-only. - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| N/A | Panic-only | Double initialization and missing admin state currently abort by panic/host error. | pre-catalog | - -## governance - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1 | [`GovernanceError::AlreadyInitialized`](contracts/governance/src/lib.rs#L109) | Governance initialization was attempted more than once. | pre-catalog | -| 2 | [`GovernanceError::NotInitialized`](contracts/governance/src/lib.rs#L110) | Governance configuration has not been initialized. | pre-catalog | -| 3 | [`GovernanceError::NotAdmin`](contracts/governance/src/lib.rs#L111) | Caller is not the admin. | pre-catalog | -| 4 | [`GovernanceError::ProposalNotFound`](contracts/governance/src/lib.rs#L112) | Proposal ID does not exist. | pre-catalog | -| 5 | [`GovernanceError::AlreadyVoted`](contracts/governance/src/lib.rs#L113) | Voter already voted on the proposal. | pre-catalog | -| 6 | [`GovernanceError::VotingNotActive`](contracts/governance/src/lib.rs#L114) | Vote attempted outside the active voting window. | pre-catalog | -| 7 | [`GovernanceError::VotingStillActive`](contracts/governance/src/lib.rs#L115) | Execute/cancel path requires voting to have ended. | pre-catalog | -| 8 | [`GovernanceError::QuorumNotMet`](contracts/governance/src/lib.rs#L116) | Proposal did not receive enough total voting power. | pre-catalog | -| 9 | [`GovernanceError::ProposalDefeated`](contracts/governance/src/lib.rs#L117) | Proposal received insufficient support to pass. | pre-catalog | -| 10 | [`GovernanceError::TimelockNotElapsed`](contracts/governance/src/lib.rs#L118) | Proposal timelock has not elapsed after voting closed. | pre-catalog | -| 11 | [`GovernanceError::AlreadyExecuted`](contracts/governance/src/lib.rs#L119) | Proposal has already been executed. | pre-catalog | -| 12 | [`GovernanceError::AlreadyCancelled`](contracts/governance/src/lib.rs#L120) | Proposal has already been cancelled. | pre-catalog | -| 13 | [`GovernanceError::ExecutionFailed`](contracts/governance/src/lib.rs#L121) | Target execution failed. | pre-catalog | -| 14 | [`GovernanceError::NoVotingPower`](contracts/governance/src/lib.rs#L122) | Voter has no token balance to vote with. | pre-catalog | - -## test-only mock contracts - -These enums are compiled only for tests or live under test fixtures. They are included so the CI coverage check catches every `#[contracterror]` variant in the repository. - -| Code | Name | Meaning | Introduced in | -|---:|---|---|---| -| 1 | [`MockTokenError::InsufficientBalance`](stealth-sender/src/test_mocks.rs#L26) | Mock token transfer balance is too low. | pre-catalog | -| 2 | [`MockTokenError::InsufficientAllowance`](stealth-sender/src/test_mocks.rs#L27) | Mock token allowance is too low. | pre-catalog | -| 11 | [`TokenError::BalanceDeauthorized`](stealth-sender/tests/mocks/token_auth_required.rs#L12) | Test token recipient is not authorized to receive balance. | pre-catalog | - - \ No newline at end of file +stealth-announcer +No #[contracterror] enum is defined. Current validation failures are panics/assertions in stealth-announcer/src/lib.rs. + +Code Name Meaning Introduced in +N/A Panic-only announce rejects non-v2 scheme IDs or missing view-tag metadata by panic/assertion. pre-catalog +stealth-registry +Code Name Meaning Introduced in +1 RegistryError::InvalidMetaAddressLength Supplied stealth meta-address is not exactly 64 bytes. pre-catalog +2 RegistryError::NotRegistered No meta-address is registered for the requested address and scheme. pre-catalog +stealth-sender +Code Name Meaning Introduced in +1 SenderError::AlreadyInitialized Contract initialization was attempted more than once. pre-catalog +2 SenderError::NotInitialized Contract state required for the call has not been initialized. pre-catalog +3 SenderError::LengthMismatch Batch input vectors do not have the same length. pre-catalog +4 SenderError::TokenNotAllowed Configured asset policy rejected the token. pre-catalog +5 SenderError::InvalidFeeConfig Fee basis points or fee-recipient configuration is invalid. pre-catalog +6 SenderError::BatchTooLarge Withdrawal batch exceeds the supported size cap. pre-catalog +7 SenderError::MultisigNotInitialized Governance multisig has not been initialized. pre-catalog +8 SenderError::MultisigAlreadyInitialized Governance multisig initialization was attempted more than once. pre-catalog +9 SenderError::NotSigner Caller is not a current governance signer. pre-catalog +10 SenderError::InvalidThreshold Requested multisig threshold is zero or exceeds signer count. pre-catalog +11 SenderError::RotationAlreadyPending A signer-rotation proposal already exists. pre-catalog +12 SenderError::NoPendingRotation No signer-rotation proposal exists for this action. pre-catalog +13 SenderError::AlreadyApprovedRotation Caller already approved the pending rotation. pre-catalog +14 SenderError::QuorumNotMet Pending rotation lacks enough approvals. pre-catalog +15 SenderError::TimelockNotElapsed Rotation timelock has not elapsed. pre-catalog +16 SenderError::Paused Contract is paused. pre-catalog +stealth-batch-sender +Code Name Meaning Introduced in +1300 BatchSenderError::AlreadyInitialized Contract initialization was attempted more than once. issue #155 +1301 BatchSenderError::NotInitialized batch_send was called before init. issue #155 +1302 BatchSenderError::EmptyBatch Batch contains no transfers. issue #155 +1303 BatchSenderError::BatchTooLarge Batch exceeds MAX_BATCH_SIZE (100). issue #155 +1304 BatchSenderError::NonPositiveAmount A transfer amount is zero or negative. issue #155 +1305 BatchSenderError::EmptyEphemeralKey A transfer's ephemeral public key is empty or not 32 bytes. issue #155 +1306 BatchSenderError::Paused Contract is paused. issue #155 +1307 BatchSenderError::AssetNotAllowed Configured asset policy rejected the token. issue #155 +1308 BatchSenderError::MultisigNotInitialized Governance multisig has not been initialized. issue #155 +1309 BatchSenderError::MultisigAlreadyInitialized Governance multisig initialization was attempted more than once. issue #155 +1310 BatchSenderError::NotSigner Caller is not a current governance signer. issue #155 +1311 BatchSenderError::InvalidThreshold Requested multisig threshold is zero or exceeds signer count. issue #155 +1312 BatchSenderError::RotationAlreadyPending A signer-rotation proposal already exists. issue #155 +1313 BatchSenderError::NoPendingRotation No signer-rotation proposal exists for this action. issue #155 +1314 BatchSenderError::AlreadyApprovedRotation Caller already approved the pending rotation. issue #155 +1315 BatchSenderError::QuorumNotMet Pending rotation lacks enough approvals. issue #155 +1316 BatchSenderError::TimelockNotElapsed Rotation timelock has not elapsed. issue #155 +stealth-vault +Code Name Meaning Introduced in +1 VaultError::AlreadyInitialized Contract initialization was attempted more than once. pre-catalog +2 VaultError::NotInitialized Announcer address has not been initialized. pre-catalog +3 VaultError::InvalidWindow Refund window is not strictly after unlock plus grace period. pre-catalog +4 VaultError::DepositNotFound Deposit ID is unknown or already consumed. pre-catalog +5 VaultError::NotYetUnlocked Claim attempted before the unlock ledger. pre-catalog +6 VaultError::NotYetRefundable Refund attempted before the refund ledger. pre-catalog +7 VaultError::WrongRecipient Claim signer does not match the deposit recipient. pre-catalog +8 VaultError::Paused Vault operations are paused. PR #169 +9 VaultError::NotYetPermissionless Operation attempted before the permissionless threshold has elapsed. PR #169 +10 VaultError::InvalidGracePeriod Specified grace period parameter is invalid. PR #169 +stealth-splitter +Code Name Meaning Introduced in +1 SplitterError::AlreadyInitialized Contract initialization was attempted more than once. pre-catalog +2 SplitterError::NotInitialized Announcer address has not been initialized. pre-catalog +3 SplitterError::SplitNotFound Split ID is unknown, or a fund-split vector count did not match the split definition. pre-catalog +4 SplitterError::TooManyBeneficiaries Split creation exceeded the 25-beneficiary cap. pre-catalog +5 SplitterError::WeightOverflow Total beneficiary weight overflowed. pre-catalog +6 SplitterError::InvalidMetaAddressLength Beneficiary stealth meta-address is not exactly 64 bytes. pre-catalog +7 SplitterError::InvalidAmount Fund amount is zero or negative. pre-catalog +8 SplitterError::EmptyBeneficiaries Split definition has no beneficiaries. pre-catalog +wraith-names +Code Name Meaning Introduced in +1 NamesError::NameTaken Name is already registered. pre-catalog +2 NamesError::NameTooShort Name is shorter than the minimum length. pre-catalog +3 NamesError::NameTooLong Name exceeds the maximum length. pre-catalog +4 NamesError::InvalidNameCharacter Name contains a character outside the allowed set. pre-catalog +5 NamesError::InvalidMetaAddress Stealth meta-address is invalid, usually not 64 bytes. pre-catalog +6 NamesError::NameNotFound Name or reverse lookup entry was not found. pre-catalog +7 NamesError::NotOwner Caller is not authorized as owner or parent owner. pre-catalog +8 NamesError::SignatureExpired On-behalf signature expiry ledger has passed. pre-catalog +9 NamesError::SignatureReplay On-behalf signature was already used. pre-catalog +10 NamesError::InvalidSigner Owner address could not be converted to a supported signer key. pre-catalog +11 NamesError::NotGuardian Caller is not one of the configured guardians. pre-catalog +12 NamesError::NoProposal No recovery proposal exists. pre-catalog +13 NamesError::ProposalAlreadyExists A recovery proposal already exists. pre-catalog +14 NamesError::AlreadyApproved Guardian already approved the proposal. pre-catalog +15 NamesError::DelayNotElapsed Recovery delay has not elapsed. pre-catalog +16 NamesError::ThresholdNotMet Recovery guardian threshold has not been met. pre-catalog +17 NamesError::TooManyGuardians Guardian set exceeds the supported size. pre-catalog +18 NamesError::InvalidThreshold Guardian or multisig threshold is invalid. pre-catalog +19 NamesError::InvalidExtendLedger Requested TTL extension ledger is not in the future. pre-catalog +20 NamesError::ParentNotFound Subdomain parent name does not exist. pre-catalog +21 NamesError::MultisigNotInitialized Protocol governance multisig has not been initialized. pre-catalog +22 NamesError::MultisigAlreadyInitialized Protocol governance multisig initialization was attempted more than once. pre-catalog +23 NamesError::NotSigner Caller is not a current protocol governance signer. pre-catalog +24 NamesError::RotationAlreadyPending A signer-rotation proposal already exists. pre-catalog +25 NamesError::NoPendingRotation No signer-rotation proposal exists for this action. pre-catalog +26 NamesError::AlreadyApprovedRotation Caller already approved the pending rotation. pre-catalog +27 NamesError::QuorumNotMet Pending rotation lacks enough approvals. pre-catalog +28 NamesError::TimelockNotElapsed Rotation timelock has not elapsed. pre-catalog +29 NamesError::NameTooDeep Name exceeds the supported subdomain depth. pre-catalog +30 NamesError::BulkLimitExceeded Bulk operation exceeds the supported item cap. pre-catalog +31 NamesError::PremiumAuctionRequired Premium top-level name must be obtained through auction during the launch window. pre-catalog +32 NamesError::Paused Contract is paused. pre-catalog +1600 NamesError::AuctionsNotInitialized Auction subsystem is not initialized, so there is no auction admin to rotate. #165 +1601 NamesError::AuctionInProgress An auction has a revealed winner and has not settled, so the auction admin cannot be rotated. #165 +wraith-names auctions +Code Name Meaning Introduced in +100 AuctionError::NotInitialized Auction subsystem has not been initialized. pre-catalog +101 AuctionError::AlreadyInitialized Auction subsystem initialization was attempted more than once. pre-catalog +102 AuctionError::InvalidConfig Reserve price or phase durations are invalid. pre-catalog +103 AuctionError::WindowClosed Premium-name launch auction window has closed. pre-catalog +104 AuctionError::NotPremiumName Name is not eligible for premium-name auction handling. pre-catalog +105 AuctionError::NameAlreadyRegistered Name was already registered before auction start or claim. pre-catalog +106 AuctionError::AuctionExists Auction already exists for the name. pre-catalog +107 AuctionError::NoAuction No auction exists for the name. pre-catalog +108 AuctionError::CommitPhaseOver Commit attempted after the commit phase ended. pre-catalog +109 AuctionError::AlreadyCommitted Bidder already committed for this auction. pre-catalog +110 AuctionError::DepositBelowReserve Bid deposit is below the configured reserve price. pre-catalog +111 AuctionError::RevealPhaseNotActive Reveal attempted outside the reveal phase. pre-catalog +112 AuctionError::NoBid No bid exists for the bidder and auction. pre-catalog +113 AuctionError::AlreadyRevealed Bid was already revealed. pre-catalog +114 AuctionError::CommitmentMismatch Revealed amount/salt does not match the stored commitment. pre-catalog +115 AuctionError::BidBelowReserve Revealed bid amount is below reserve. pre-catalog +116 AuctionError::BidExceedsDeposit Revealed bid amount exceeds locked deposit. pre-catalog +117 AuctionError::RevealPhaseNotOver Settle or withdraw attempted before reveal phase ended. pre-catalog +118 AuctionError::AlreadySettled Auction has already been settled. pre-catalog +119 AuctionError::NotSettled Winner tried to claim before settlement. pre-catalog +120 AuctionError::NotWinner Caller is not the winning bidder. pre-catalog +121 AuctionError::WinnerCannotWithdraw Highest bidder cannot self-withdraw before settlement. pre-catalog +122 AuctionError::InvalidMetaAddress Winner claim supplied an invalid stealth meta-address. pre-catalog +123 AuctionError::RegistrationFailed Auction claim could not complete name registration for an unmapped reason. pre-catalog +wraith-asset-policy +No #[contracterror] enum is defined. This contract is currently panic-only. + +Code Name Meaning Introduced in +N/A Panic-only Double initialization and missing admin state currently abort by panic/host error. pre-catalog +governance +Code Name Meaning Introduced in +1 GovernanceError::AlreadyInitialized Governance initialization was attempted more than once. pre-catalog +2 GovernanceError::NotInitialized Governance configuration has not been initialized. pre-catalog +3 GovernanceError::NotAdmin Caller is not the admin. pre-catalog +4 GovernanceError::ProposalNotFound Proposal ID does not exist. pre-catalog +5 GovernanceError::AlreadyVoted Voter already voted on the proposal. pre-catalog +6 GovernanceError::VotingNotActive Vote attempted outside the active voting window. pre-catalog +7 GovernanceError::VotingStillActive Execute/cancel path requires voting to have ended. pre-catalog +8 GovernanceError::QuorumNotMet Proposal did not receive enough total voting power. pre-catalog +9 GovernanceError::ProposalDefeated Proposal received insufficient support to pass. pre-catalog +10 GovernanceError::TimelockNotElapsed Proposal timelock has not elapsed after voting closed. pre-catalog +11 GovernanceError::AlreadyExecuted Proposal has already been executed. pre-catalog +12 GovernanceError::AlreadyCancelled Proposal has already been cancelled. pre-catalog +13 GovernanceError::ExecutionFailed Target execution failed. pre-catalog +14 GovernanceError::NoVotingPower Voter has no token balance to vote with. pre-catalog +test-only mock contracts +These enums are compiled only for tests or live under test fixtures. They are included so the CI coverage check catches every #[contracterror] variant in the repository. + +Code Name Meaning Introduced in +1 MockTokenError::InsufficientBalance Mock token transfer balance is too low. pre-catalog +2 MockTokenError::InsufficientAllowance Mock token allowance is too low. pre-catalog +11 TokenError::BalanceDeauthorized Test token recipient is not authorized to receive balance. pre-catalog + \ No newline at end of file diff --git a/stellar/EVENT_INDEXING_AUDIT.md b/stellar/EVENT_INDEXING_AUDIT.md index c1bfbcf..17cc63d 100644 --- a/stellar/EVENT_INDEXING_AUDIT.md +++ b/stellar/EVENT_INDEXING_AUDIT.md @@ -1,139 +1,96 @@ -# Stellar Event Indexing Audit (Issue #61) +Stellar Event Indexing Audit (Issue #61) +Date: 2026-06-27 +Scope: All Soroban contracts in stellar/ +Background: Soroban getEvents filters support 1–4 topic segments (hard limit). Unused topic slots waste filtering selectivity and force indexers to download more events than necessary. -**Date:** 2026-06-27 -**Scope:** All Soroban contracts in `stellar/` -**Background:** Soroban `getEvents` filters support 1–4 topic segments (hard limit). Unused topic slots waste filtering selectivity and force indexers to download more events than necessary. +Status +Overall verdict: OPTIMAL. The two core contracts (stealth-announcer, stealth-registry) are correctly shaped. wraith-names is acceptable as-is. stealth-splitter already routes per-transfer announcements through the announcer (Issue #62 was a stale audit-doc verdict). stealth-batch-sender now does the same (Issue #63 resolved). -## Status +Findings Summary +Contract Event Current Topics Assessment Recommendation +stealth-announcer announce ("announce", scheme_id, view_tag_bucket, metadata_kind) ✅ FINAL None — v2 schema is authoritative +stealth-registry register ("register", registrant, scheme_id) ✅ OPTIMAL None +stealth-registry remove ("remove", registrant, scheme_id) ✅ OPTIMAL None +stealth-sender — No user-facing events ✅ N/A None +wraith-names register, update, release, extend (event_type, name_hash) ✅ ACCEPTABLE Optional: add owner as topic 3 (low priority) +stealth-splitter per-transfer announcement ("announce", scheme_id, view_tag_bucket, metadata_kind) via announcer ✅ OPTIMAL None — already routed through announcer (Issue #62 was a stale audit verdict) +stealth-splitter create, fund (event_type, split_id) ✅ OK None +stealth-batch-sender per-transfer announcement ("announce", scheme_id, view_tag_bucket, metadata_kind) via announcer ✅ OPTIMAL None — Issue #63 resolved +stealth-batch-sender batch summary event ("BATCH",) ✅ OK Batch-level observability; not used for recipient scan filtering +Detailed Findings +stealth-announcer +Event: announce +Topics: ("announce", scheme_id, view_tag_bucket, metadata_kind) +Data: (stealth_address, ephemeral_pub_key, metadata) -**Overall verdict: MOSTLY OPTIMAL.** The two core contracts (`stealth-announcer`, `stealth-registry`) are correctly shaped. `wraith-names` is acceptable as-is. `stealth-splitter` has a confirmed indexing deficiency in its `ANNOUNCE` events. `stealth-batch-sender` requires a follow-up review. +All 4 topic slots are used. view_tag_bucket is metadata[0] as u32, partitioning events into 256 buckets — a recipient only needs to scan ~1/256 of all announcements (~99.6% traffic reduction). metadata_kind=1 signals that metadata[0] is the view tag byte. STELLAR_V2_SCHEME_ID=2 is asserted at runtime, preventing accidental v1 data from appearing under this contract. ---- +No changes needed. -## Findings Summary +stealth-registry +Events: register, remove +Topics: ("register"|"remove", registrant, scheme_id) +Data: register → stealth_meta_address; remove → () -| Contract | Event | Current Topics | Assessment | Recommendation | -|---|---|---|---|---| -| stealth-announcer | `announce` | `("announce", scheme_id, view_tag_bucket, metadata_kind)` | ✅ FINAL | None — v2 schema is authoritative | -| stealth-registry | `register` | `("register", registrant, scheme_id)` | ✅ OPTIMAL | None | -| stealth-registry | `remove` | `("remove", registrant, scheme_id)` | ✅ OPTIMAL | None | -| stealth-sender | — | No user-facing events | ✅ N/A | None | -| wraith-names | `register`, `update`, `release`, `extend` | `(event_type, name_hash)` | ✅ ACCEPTABLE | Optional: add owner as topic 3 (low priority) | -| stealth-splitter | `ANNOUNCE` | `("ANNOUNCE",)` | ⚠️ SUBOPTIMAL | Issue #62: adopt v2 topic layout or route through announcer | -| stealth-splitter | `BATCH` | `("BATCH",)` | ⚠️ SUBOPTIMAL | Covered by Issue #62 | -| stealth-splitter | `create`, `fund` | `(event_type, split_id)` | ✅ OK | None | -| stealth-batch-sender | per-transfer event | Unknown (line 83) | ⚠️ NEEDS REVIEW | Issue #63: audit event topic layout | -| stealth-batch-sender | batch summary event | Unknown (line 95) | ⚠️ NEEDS REVIEW | Issue #63: audit event topic layout | +The dominant read pattern is "fetch meta-address for address X under scheme Y." The existing (registrant, scheme_id) topic pair maps directly to that query. A 4th topic (e.g. a hash of the meta-address) would add no useful selectivity for any realistic indexer query. ---- +No changes needed. -## Detailed Findings +stealth-sender +Delegates all announcement work to the stealth-announcer contract via invoke_contract. No user-facing events are emitted directly. Internal observability events from wraith-metrics are out of scope for this audit. -### stealth-announcer +Not applicable. -**Event:** `announce` -**Topics:** `("announce", scheme_id, view_tag_bucket, metadata_kind)` -**Data:** `(stealth_address, ephemeral_pub_key, metadata)` +wraith-names +Events: register, update, release, extend +Topics: (event_type, name_hash) +Data: varies per event -All 4 topic slots are used. `view_tag_bucket` is `metadata[0] as u32`, partitioning events into 256 buckets — a recipient only needs to scan ~1/256 of all announcements (~99.6% traffic reduction). `metadata_kind=1` signals that `metadata[0]` is the view tag byte. `STELLAR_V2_SCHEME_ID=2` is asserted at runtime, preventing accidental v1 data from appearing under this contract. - -**No changes needed.** - ---- - -### stealth-registry - -**Events:** `register`, `remove` -**Topics:** `("register"|"remove", registrant, scheme_id)` -**Data:** `register` → `stealth_meta_address`; `remove` → `()` - -The dominant read pattern is "fetch meta-address for address X under scheme Y." The existing `(registrant, scheme_id)` topic pair maps directly to that query. A 4th topic (e.g. a hash of the meta-address) would add no useful selectivity for any realistic indexer query. - -**No changes needed.** - ---- - -### stealth-sender - -Delegates all announcement work to the `stealth-announcer` contract via `invoke_contract`. No user-facing events are emitted directly. Internal observability events from `wraith-metrics` are out of scope for this audit. - -**Not applicable.** - ---- - -### wraith-names - -**Events:** `register`, `update`, `release`, `extend` -**Topics:** `(event_type, name_hash)` -**Data:** varies per event - -`name_hash` is `SHA-256(name)`, so `(event_type, name_hash)` is a deterministic, exact-match lookup for "history of name X" — the overwhelmingly common query. This is already optimal for the primary use case. +name_hash is SHA-256(name), so (event_type, name_hash) is a deterministic, exact-match lookup for "history of name X" — the overwhelmingly common query. This is already optimal for the primary use case. A potential improvement is adding the owner address as topic 3, enabling queries like "all registrations by owner O." However, the owner address is not currently included in event data, so adding it would require a data-shape change. Given the low event volume of a name registry, the cost/complexity is not justified at this time. -**No changes required.** Owner-indexed queries are a low-priority optional follow-up. - ---- - -### stealth-splitter - -**Events of concern:** `ANNOUNCE`, `BATCH` - -`ANNOUNCE` topics: `("ANNOUNCE",)` — only 1 topic, no filtering possible beyond event name. -`BATCH` topics: `("BATCH",)` — same problem. - -A recipient scanning for stealth payments routed through the splitter must download every `ANNOUNCE` event ever emitted by the contract. This is the exact problem the v2 announcer schema was designed to solve. The correct fix is one of: - -- **(a) Route through announcer:** have `stealth-splitter` call `stealth-announcer` for each transfer instead of emitting its own `ANNOUNCE`. This fixes indexing and eliminates the architectural inconsistency (splitter announcements are not currently discoverable via the same `getEvents` filter as direct announcer events). -- **(b) Adopt v2 topic layout inline:** emit `("ANNOUNCE", scheme_id, view_tag_bucket, metadata_kind)` directly, mirroring the announcer schema without the cross-contract call overhead. +No changes required. Owner-indexed queries are a low-priority optional follow-up. -Option (a) is preferred for consistency. Tracked as **Issue #62**. +stealth-splitter +Per-transfer announcements: routed through stealth-announcer via announcer_client::announce in fund_split (stealth-splitter/src/lib.rs). The resulting events are emitted by the announcer contract with the v2 topic layout ("announce", scheme_id, view_tag_bucket, metadata_kind). Recipients can apply the same topic-3 (view_tag_bucket) getEvents filter used for direct announcer traffic. -**`create` and `fund` events** (`(event_type, split_id)`) are fine — split management queries by `split_id` are the natural access pattern. +The original audit recorded splitter ANNOUNCE / BATCH as single-topic events. That verdict is stale: fund_split no longer publishes ("ANNOUNCE",) itself. Issue #62 is therefore an audit-doc correction, not a code change. ---- +create and fund events ((event_type, split_id)) are fine — split management queries by split_id are the natural access pattern. -### stealth-batch-sender +stealth-batch-sender +Per-transfer announcements are routed through stealth-announcer via announcer_client::announce in batch_send (stealth-batch-sender/src/lib.rs), matching the splitter pattern. Each transfer therefore appears under the announcer with the v2 4-topic layout, including view_tag_bucket = metadata[0] as u32. Indexers no longer need a batch-sender-specific decoder for a single-topic ("ANNOUNCE",) event. -The contract emits at least two events (per-transfer at line 83, batch summary at line 95 of `src/lib.rs`). The full topic layout was not inspected during this audit. If these events follow a pattern similar to `stealth-splitter`'s `ANNOUNCE` — emitting their own announcement-style events with fewer than 4 topics and no `view_tag_bucket` — the same indexing deficiency applies. +The batch summary event ("BATCH",) with (from, count, asset) is operational observability and is not used for recipient scan filtering. -**Tracked as Issue #63** for a dedicated review. +Issue #63 is resolved. ---- +v2 Schema Sign-off +The stealth-announcer v2 event schema is final and authoritative: -## v2 Schema Sign-off +text -The `stealth-announcer` v2 event schema is **final and authoritative**: - -``` topics: ("announce", scheme_id: u32, view_tag_bucket: u32, metadata_kind: u32) data: (stealth_address: Bytes, ephemeral_pub_key: Bytes, metadata: Bytes) -``` - -- `scheme_id = 2` (`STELLAR_V2_SCHEME_ID`) is the canonical Stellar stealth scheme identifier. -- `view_tag_bucket = metadata[0] as u32` when `metadata_kind = 1`. -- v1 events (scheme_id=1, 3-topic layout) remain readable from the old contract during any transition period (Path A migration). - -SDK and off-chain indexers should treat this schema as stable. No further changes to `stealth-announcer` event structure are planned. - ---- - -## Follow-up Issues - -### Issue #62 — stealth-splitter: fix ANNOUNCE event topic layout +scheme_id = 2 (STELLAR_V2_SCHEME_ID) is the canonical Stellar stealth scheme identifier. +view_tag_bucket = metadata[0] as u32 when metadata_kind = 1. +v1 events (scheme_id=1, 3-topic layout) remain readable from the old contract during any transition period (Path A migration). +SDK and off-chain indexers should treat this schema as stable. No further changes to stealth-announcer event structure are planned. -**Problem:** `stealth-splitter` emits `ANNOUNCE` with only 1 topic, making per-recipient scan filtering impossible. Recipients must download all splitter announcements. +The reference indexer (stellar/examples/indexer/src/processor.ts) decodes this layout through a single code path shared by announcer, splitter, and batch-sender. -**Options:** -1. Route splitter per-transfer announcements through `stealth-announcer` (preferred — restores unified indexing). -2. Adopt the v2 topic layout `("ANNOUNCE", scheme_id, view_tag_bucket, metadata_kind)` inline. +Follow-up Issues +Issue #62 — stealth-splitter: fix ANNOUNCE event topic layout +Status: CLOSED (audit-doc correction). No splitter code change. -**Affects:** `ANNOUNCE` and `BATCH` events. `create`/`fund` events are unaffected. +Original problem: the audit recorded stealth-splitter as emitting ANNOUNCE with only 1 topic. ---- +Correction: fund_split already routes per-transfer announcements through stealth-announcer via announcer_client::announce. Splitter output therefore already uses the full 4-topic v2 layout ("announce", scheme_id, view_tag_bucket, metadata_kind). Recipients can filter on topic-3 (view_tag_bucket) without a full scan of splitter traffic. -### Issue #63 — stealth-batch-sender: audit event topic layout +Issue #63 — stealth-batch-sender: audit event topic layout +Status: RESOLVED. Closed by the merging PR that lands this change: stpatrickghost/contracts. -**Problem:** The event topic layout in `stealth-batch-sender` was not fully reviewed in this audit. If per-transfer events lack `view_tag_bucket` and `scheme_id` topics, the same deficiency as Issue #62 applies. +Original problem: stealth-batch-sender emitted per-transfer ("ANNOUNCE",) with a single topic, so server-side topic-3 view-tag filtering could not be applied to batch-sender traffic. -**Action:** Read `stealth-batch-sender/src/lib.rs` lines 83 and 95 in full context, assess topic layout against the v2 schema, and apply the same fix as Issue #62 if needed. +Fix: batch_send now invokes the announcer contract per transfer (same announcer_client::announce pattern as the splitter). Events use ("announce", scheme_id, view_tag_bucket, metadata_kind) with view_tag_bucket = metadata[0] as u32. Covered by unit + snapshot tests and by stellar/integration-tests/tests/topic_filter.rs. \ No newline at end of file diff --git a/stellar/bench-crossover/src/crossover.rs b/stellar/bench-crossover/src/crossover.rs index 49ec879..f8efe56 100644 --- a/stellar/bench-crossover/src/crossover.rs +++ b/stellar/bench-crossover/src/crossover.rs @@ -105,9 +105,10 @@ fn measure_batch(n: u32) -> Measured { let contract_id = env.register(StealthBatchSender, ()); let client = StealthBatchSenderClient::new(&env, &contract_id); - // batch_send now requires init(admin, announcer, asset_policy) (issue #155). + // batch_send now requires init(admin, announcer, asset_policy) (issue #155) + // and routes announcements through the real announcer (issue #63). let admin = Address::generate(&env); - let announcer = Address::generate(&env); + let announcer = env.register(StealthAnnouncerContract, ()); client.init(&admin, &announcer, &None); let (token, sender) = funded_token(&env); @@ -115,8 +116,9 @@ fn measure_batch(n: u32) -> Measured { for i in 0..n { transfers.push_back(Transfer { stealth_address: Address::generate(&env), - ephemeral_pub_key: bytes(&env, 33, 0x02u8.wrapping_add(i as u8)), + ephemeral_pub_key: bytes(&env, 32, 0x02u8.wrapping_add(i as u8)), amount: 100, + metadata: bytes(&env, 1, (i as u8).wrapping_add(1)), }); } diff --git a/stellar/examples/indexer/.env.example b/stellar/examples/indexer/.env.example index b67010c..8738114 100644 --- a/stellar/examples/indexer/.env.example +++ b/stellar/examples/indexer/.env.example @@ -7,8 +7,10 @@ NETWORK_PASSPHRASE=Test SDF Future Network ; October 2022 STEALTH_ANNOUNCER_ADDRESS=CBXYZANN0UNCERXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0001 STEALTH_REGISTRY_ADDRESS=CBREG1STRYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0002 STEALTH_SENDER_ADDRESS=CBSEND3RXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0003 +STEALTH_SPLITTER_ADDRESS=CBSPL1TTERXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0005 +STEALTH_BATCH_SENDER_ADDRESS=CBBATCHS3NDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX0006 WRAITH_NAMES_ADDRESS=CBNAM3SWRA1THXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX004 # Indexer POLL_INTERVAL_MS=5000 -PORT=3000 +PORT=3000 \ No newline at end of file diff --git a/stellar/examples/indexer/src/processor.ts b/stellar/examples/indexer/src/processor.ts index 244b4d3..22817d0 100644 --- a/stellar/examples/indexer/src/processor.ts +++ b/stellar/examples/indexer/src/processor.ts @@ -16,11 +16,32 @@ dotenv.config(); const RPC_URL = process.env.RPC_URL || 'https://futurenet.sorobanrpc.com'; const server = new Server(RPC_URL); +/** + * Contract roles the indexer watches. + * + * Announcements from `announcer`, `splitter`, and `batch-sender` share one + * code path (`processAnnouncement`). Splitter and batch-sender route each + * transfer through the announcer contract, so they emit the same v2 4-topic + * layout: + * + * topics: ("announce", scheme_id, view_tag_bucket, metadata_kind) + * data: (stealth_address, ephemeral_pub_key, metadata) + * + * There is no batch-sender-specific decoder for the legacy single-topic + * `("ANNOUNCE",)` emission — that path is gone. + */ interface ContractConfig { address: string; - type: 'announcer' | 'registry' | 'sender' | 'names'; + type: 'announcer' | 'registry' | 'sender' | 'names' | 'splitter' | 'batch-sender'; } +const ANNOUNCEMENT_SOURCES: ReadonlySet = new Set([ + 'announcer', + 'splitter', + 'batch-sender', + 'sender', +]); + const CONTRACTS: Record = { stealthAnnouncer: { address: process.env.STEALTH_ANNOUNCER_ADDRESS || '', @@ -34,6 +55,14 @@ const CONTRACTS: Record = { address: process.env.STEALTH_SENDER_ADDRESS || '', type: 'sender', }, + stealthSplitter: { + address: process.env.STEALTH_SPLITTER_ADDRESS || '', + type: 'splitter', + }, + stealthBatchSender: { + address: process.env.STEALTH_BATCH_SENDER_ADDRESS || '', + type: 'batch-sender', + }, wraithNames: { address: process.env.WRAITH_NAMES_ADDRESS || '', type: 'names', @@ -108,24 +137,12 @@ async function processEvent(config: ContractConfig, event: any, client: any) { return; } + if (ANNOUNCEMENT_SOURCES.has(config.type)) { + await processAnnouncement(config, event, client, ledger, txHash); + return; + } + switch (config.type) { - case 'announcer': - case 'sender': - // Both emit the same announcement event - if (Array.isArray(event.topic) && event.topic[0] === 'announce') { - const [schemeId, stealthAddress, ephemeralPubKey, metadata] = event.value; - await insertAnnouncement( - ledger, - txHash, - config.address, - schemeId, - stealthAddress, - Buffer.from(ephemeralPubKey, 'base64'), - metadata ? Buffer.from(metadata, 'base64') : null, - client - ); - } - break; case 'registry': if (Array.isArray(event.topic) && event.topic[0] === 'register_keys') { const [registrant, schemeId, stealthMetaAddress] = event.value; @@ -173,6 +190,45 @@ async function processEvent(config: ContractConfig, event: any, client: any) { } } +/** + * Single decoder for v2 announcements from announcer, splitter, and + * batch-sender (and sender, which also routes through the announcer). + * + * v2 topics: ("announce", scheme_id, view_tag_bucket, metadata_kind) + * v2 data: (stealth_address, ephemeral_pub_key, metadata) + * + * Splitter/batch-sender `create`/`fund`/`BATCH` events are ignored here; + * they are not announcement-shaped. + */ +async function processAnnouncement( + config: ContractConfig, + event: any, + client: any, + ledger: number, + txHash: string +) { + if (!Array.isArray(event.topic) || event.topic[0] !== 'announce') { + return; + } + + const schemeId = event.topic.length >= 2 ? event.topic[1] : event.value[0]; + const [stealthAddress, ephemeralPubKey, metadata] = + event.topic.length >= 4 + ? event.value + : event.value.slice(1); + + await insertAnnouncement( + ledger, + txHash, + config.address, + schemeId, + stealthAddress, + Buffer.from(ephemeralPubKey, 'base64'), + metadata ? Buffer.from(metadata, 'base64') : null, + client + ); +} + export async function startProcessing() { const POLL_INTERVAL_MS = parseInt(process.env.POLL_INTERVAL_MS || '5000', 10); diff --git a/stellar/integration-tests/test_snapshots/batch_sender_batch_send_through_chaos.1.json b/stellar/integration-tests/test_snapshots/batch_sender_batch_send_through_chaos.1.json index 40bb142..e640b86 100644 --- a/stellar/integration-tests/test_snapshots/batch_sender_batch_send_through_chaos.1.json +++ b/stellar/integration-tests/test_snapshots/batch_sender_batch_send_through_chaos.1.json @@ -1,21 +1,23 @@ { "generators": { - "address": 7, + "address": 9, "nonce": 0 }, "auth": [ + [], + [], [], [ [ - "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", { "function": { "contract_fn": { - "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "function_name": "set_admin", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" } ] } @@ -26,15 +28,15 @@ ], [ [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", { "function": { "contract_fn": { - "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "function_name": "mint", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" }, { "i128": { @@ -51,7 +53,7 @@ ], [ [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", { "function": { "contract_fn": { @@ -59,7 +61,7 @@ "function_name": "batch_send", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" }, { "vec": [ @@ -84,12 +86,20 @@ "bytes": "0101010101010101010101010101010101010101010101010101010101010101" } }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "01" + } + }, { "key": { "symbol": "stealth_address" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" } } ] @@ -115,12 +125,20 @@ "bytes": "0202020202020202020202020202020202020202020202020202020202020202" } }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "02" + } + }, { "key": { "symbol": "stealth_address" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" } } ] @@ -146,12 +164,20 @@ "bytes": "0303030303030303030303030303030303030303030303030303030303030303" } }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "03" + } + }, { "key": { "symbol": "stealth_address" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" } } ] @@ -159,7 +185,7 @@ ] }, { - "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" } ] } @@ -168,14 +194,14 @@ { "function": { "contract_fn": { - "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "function_name": "transfer", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" }, { "i128": { @@ -191,14 +217,14 @@ { "function": { "contract_fn": { - "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "function_name": "transfer", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" }, { "i128": { @@ -214,14 +240,14 @@ { "function": { "contract_fn": { - "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "function_name": "transfer", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" }, { "i128": { @@ -255,7 +281,7 @@ [ { "account": { - "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF" + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" } }, [ @@ -263,7 +289,7 @@ "last_modified_ledger_seq": 0, "data": { "account": { - "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", "balance": 0, "seq_num": 0, "num_sub_entries": 0, @@ -283,7 +309,7 @@ [ { "contract_data": { - "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", "key": { "ledger_key_nonce": { "nonce": 801925984706572462 @@ -298,7 +324,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", "key": { "ledger_key_nonce": { "nonce": 801925984706572462 @@ -330,6 +356,63 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "key": "ledger_key_contract_instance", "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", "val": { "contract_instance": { "executable": { @@ -348,7 +431,7 @@ [ { "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { "nonce": 5541220902715666415 @@ -363,7 +446,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { "nonce": 5541220902715666415 @@ -381,7 +464,7 @@ [ { "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { "nonce": 1033654523790656264 @@ -396,7 +479,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { "nonce": 1033654523790656264 @@ -414,14 +497,14 @@ [ { "contract_data": { - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" } ] }, @@ -434,14 +517,14 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" } ] }, @@ -487,14 +570,14 @@ [ { "contract_data": { - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" } ] }, @@ -507,14 +590,14 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" } ] }, @@ -560,14 +643,14 @@ [ { "contract_data": { - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" } ] }, @@ -580,14 +663,14 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" } ] }, @@ -633,14 +716,14 @@ [ { "contract_data": { - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" } ] }, @@ -653,14 +736,14 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": { "vec": [ { "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" } ] }, @@ -706,7 +789,7 @@ [ { "contract_data": { - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": "ledger_key_contract_instance", "durability": "persistent" } @@ -717,7 +800,7 @@ "data": { "contract_data": { "ext": "v0", - "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", "key": "ledger_key_contract_instance", "durability": "persistent", "val": { @@ -743,7 +826,7 @@ "symbol": "name" }, "val": { - "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF" + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" } }, { @@ -766,7 +849,7 @@ ] }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" } }, { @@ -797,7 +880,7 @@ "symbol": "issuer" }, "val": { - "bytes": "0000000000000000000000000000000000000000000000000000000000000004" + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" } } ] diff --git a/stellar/integration-tests/test_snapshots/topic_3_view_tag_filter_across_announcer_splitter_and_batch_sender.1.json b/stellar/integration-tests/test_snapshots/topic_3_view_tag_filter_across_announcer_splitter_and_batch_sender.1.json new file mode 100644 index 0000000..c4e4f85 --- /dev/null +++ b/stellar/integration-tests/test_snapshots/topic_3_view_tag_filter_across_announcer_splitter_and_batch_sender.1.json @@ -0,0 +1,1658 @@ +{ + "generators": { + "address": 13, + "nonce": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + { + "function": { + "contract_fn": { + "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "function_name": "create_split", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "meta_address" + }, + "val": { + "bytes": "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + } + }, + { + "key": { + "symbol": "weight" + }, + "val": { + "u128": { + "hi": 0, + "lo": 1 + } + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "meta_address" + }, + "val": { + "bytes": "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" + } + }, + { + "key": { + "symbol": "weight" + }, + "val": { + "u128": { + "hi": 0, + "lo": 1 + } + } + } + ] + } + ] + }, + { + "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + }, + { + "bytes": "746f7069632d66696c7465722d73616c74" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "function_name": "fund_split", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "bytes": "652da6b2a1f956ba57d1885012514a7121f03b96372263ebf30c9f6d71a1ed3b" + }, + { + "i128": { + "hi": 0, + "lo": 2000 + } + }, + { + "u32": 2 + }, + { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + ] + }, + { + "vec": [ + { + "bytes": "2121212121212121212121212121212121212121212121212121212121212121" + }, + { + "bytes": "2222222222222222222222222222222222222222222222222222222222222222" + } + ] + }, + { + "vec": [ + { + "bytes": "07" + }, + { + "bytes": "0b" + } + ] + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "i128": { + "hi": 0, + "lo": 2000 + } + } + ] + } + }, + "sub_invocations": [] + }, + { + "function": { + "contract_fn": { + "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5", + "function_name": "batch_send", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, + { + "key": { + "symbol": "ephemeral_pub_key" + }, + "val": { + "bytes": "3131313131313131313131313131313131313131313131313131313131313131" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "07" + } + }, + { + "key": { + "symbol": "stealth_address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 200 + } + } + }, + { + "key": { + "symbol": "ephemeral_pub_key" + }, + "val": { + "bytes": "3232323232323232323232323232323232323232323232323232323232323232" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "c8" + } + }, + { + "key": { + "symbol": "stealth_address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + } + ] + } + ] + }, + { + "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [] + }, + { + "function": { + "contract_fn": { + "contract_address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "i128": { + "hi": 0, + "lo": 200 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Split" + }, + { + "bytes": "652da6b2a1f956ba57d1885012514a7121f03b96372263ebf30c9f6d71a1ed3b" + } + ] + }, + "val": { + "map": [ + { + "key": { + "symbol": "asset" + }, + "val": { + "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + } + }, + { + "key": { + "symbol": "beneficiaries" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "meta_address" + }, + "val": { + "bytes": "11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + } + }, + { + "key": { + "symbol": "weight" + }, + "val": { + "u128": { + "hi": 0, + "lo": 1 + } + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "meta_address" + }, + "val": { + "bytes": "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" + } + }, + { + "key": { + "symbol": "weight" + }, + "val": { + "u128": { + "hi": 0, + "lo": 1 + } + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "creator" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "salt" + }, + "val": { + "bytes": "746f7069632d66696c7465722d73616c74" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "SplitFunded" + }, + { + "bytes": "652da6b2a1f956ba57d1885012514a7121f03b96372263ebf30c9f6d71a1ed3b" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 2000 + } + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXI7N" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 996700 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 2000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 200 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000004" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "8011bbf4cdf04e5bc6ac886935b99aa4b2c0cabde133f9d7fb3e656799f0a896", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 100 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "announce" + }, + { + "u32": 2 + }, + { + "u32": 7 + }, + { + "u32": 1 + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYRE5" + }, + { + "bytes": "3131313131313131313131313131313131313131313131313131313131313131" + }, + { + "bytes": "07" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "8011bbf4cdf04e5bc6ac886935b99aa4b2c0cabde133f9d7fb3e656799f0a896", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJXFF" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 200 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "announce" + }, + { + "u32": 2 + }, + { + "u32": 200 + }, + { + "u32": 1 + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2ZMN" + }, + { + "bytes": "3232323232323232323232323232323232323232323232323232323232323232" + }, + { + "bytes": "c8" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "000000000000000000000000000000000000000000000000000000000000000a", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "BATCH" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 2 + }, + { + "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "000000000000000000000000000000000000000000000000000000000000000a", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "metric" + }, + { + "symbol": "st_bat_sd" + }, + { + "symbol": "bat_send" + } + ], + "data": { + "vec": [ + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "vec": [ + { + "vec": [ + { + "symbol": "ast_addr" + }, + { + "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "000000000000000000000000000000000000000000000000000000000000000a", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "metric" + }, + { + "symbol": "st_bat_sd" + }, + { + "symbol": "bat_vol" + } + ], + "data": { + "vec": [ + { + "i128": { + "hi": 0, + "lo": 300 + } + }, + { + "vec": [ + { + "vec": [ + { + "symbol": "ast_addr" + }, + { + "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "000000000000000000000000000000000000000000000000000000000000000a", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "metric" + }, + { + "symbol": "st_bat_sd" + }, + { + "symbol": "bat_size" + } + ], + "data": { + "vec": [ + { + "i128": { + "hi": 0, + "lo": 2 + } + }, + { + "vec": [ + { + "vec": [ + { + "symbol": "ast_addr" + }, + { + "address": "CCABDO7UZXYE4W6GVSEGSNNZTKSLFQGKXXQTH6OX7M7GKZ4Z6CUJNGZN" + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/stellar/integration-tests/tests/chaos.rs b/stellar/integration-tests/tests/chaos.rs index 6a314e1..4a7d8c4 100644 --- a/stellar/integration-tests/tests/chaos.rs +++ b/stellar/integration-tests/tests/chaos.rs @@ -710,8 +710,8 @@ fn batch_sender_batch_send_through_chaos() { // The production-hardening pass (issue #155) added a one-time init // flow — batch_send now returns NotInitialized until init() is called. let admin = Address::generate(&env); - let announcer = Address::generate(&env); - client.init(&admin, &announcer, &None); + let announcer_id = env.register(StealthAnnouncerContract, ()); + client.init(&admin, &announcer_id, &None); let (token, from) = funded_token(&env); @@ -724,16 +724,19 @@ fn batch_sender_batch_send_through_chaos() { stealth_address: stealth1.clone(), ephemeral_pub_key: bytes(&env, &[0x01u8; 32]), amount: 100, + metadata: bytes(&env, &[0x01]), }); transfers.push_back(Transfer { stealth_address: stealth2.clone(), ephemeral_pub_key: bytes(&env, &[0x02u8; 32]), amount: 200, + metadata: bytes(&env, &[0x02]), }); transfers.push_back(Transfer { stealth_address: stealth3.clone(), ephemeral_pub_key: bytes(&env, &[0x03u8; 32]), amount: 300, + metadata: bytes(&env, &[0x03]), }); client.batch_send(&from, &transfers, &token); diff --git a/stellar/integration-tests/tests/topic_filter.rs b/stellar/integration-tests/tests/topic_filter.rs new file mode 100644 index 0000000..c29a6de --- /dev/null +++ b/stellar/integration-tests/tests/topic_filter.rs @@ -0,0 +1,215 @@ +//! Integration test: topic-3 (`view_tag_bucket`) filtering across all three +//! announcement sources — announcer, splitter, and batch-sender. +//! +//! After Issues #62/#63, every source emits (or routes through) the v2 layout: +//! +//! ```text +//! topics = ("announce", scheme_id, view_tag_bucket, metadata_kind) +//! ``` +//! +//! Topic 3 (1-indexed; index 2 in the topic vector) is `view_tag_bucket = +//! metadata[0] as u32`. A server-side filter on that slot must return only the +//! matching subset, regardless of which contract originated the payment. +//! +//! Soroban testutils reset the event buffer after each top-level invocation, so +//! this test harvests announce events after every source call and then applies +//! the topic-3 filter to the combined stream. + +use soroban_sdk::testutils::{Address as _, Events}; +use soroban_sdk::{symbol_short, token, Address, Bytes, BytesN, Env, FromVal, IntoVal, Val}; +use stealth_announcer::{ + StealthAnnouncerContract, StealthAnnouncerContractClient, METADATA_KIND_VIEW_TAG, + STELLAR_V2_SCHEME_ID, +}; +use stealth_batch_sender::{StealthBatchSender, StealthBatchSenderClient, Transfer}; +use stealth_splitter::{Beneficiary, StealthSplitterContract, StealthSplitterContractClient}; + +const MATCH_TAG: u8 = 7; +const OTHER_TAGS: [u8; 3] = [99, 11, 200]; + +struct HarvestedAnnounce { + contract: Address, + bucket: u32, + stealth: Address, + metadata: Bytes, +} + +fn bytes(env: &Env, data: &[u8]) -> Bytes { + Bytes::from_slice(env, data) +} + +fn bytes32(env: &Env, fill: u8) -> BytesN<32> { + BytesN::from_array(env, &[fill; 32]) +} + +fn meta(env: &Env, tag: u8) -> Bytes { + Bytes::from_slice(env, &[tag]) +} + +fn funded_token(env: &Env, holder: &Address, amount: i128) -> Address { + let admin = Address::generate(env); + let token = env.register_stellar_asset_contract_v2(admin).address(); + token::StellarAssetClient::new(env, &token).mint(holder, &amount); + token +} + +/// Pull v2 announce events out of the current invocation's event buffer. +fn harvest_announces(env: &Env, out: &mut std::vec::Vec) { + let announce_sym: Val = symbol_short!("announce").into_val(env); + for event in env.events().all().iter() { + if event.1.len() != 4 { + continue; + } + let first: Option = event.1.first(); + if first.map(|t| t.shallow_eq(&announce_sym)) != Some(true) { + continue; + } + let bucket: u32 = FromVal::from_val(env, &event.1.get(2).unwrap()); + let kind: u32 = FromVal::from_val(env, &event.1.get(3).unwrap()); + let scheme: u32 = FromVal::from_val(env, &event.1.get(1).unwrap()); + assert_eq!(scheme, STELLAR_V2_SCHEME_ID); + assert_eq!(kind, METADATA_KIND_VIEW_TAG); + let (stealth, _epk, metadata): (Address, BytesN<32>, Bytes) = + FromVal::from_val(env, &event.2); + out.push(HarvestedAnnounce { + contract: event.0, + bucket, + stealth, + metadata, + }); + } +} + +fn filter_by_view_tag<'a>( + events: &'a [HarvestedAnnounce], + bucket: u32, +) -> std::vec::Vec<&'a HarvestedAnnounce> { + events.iter().filter(|e| e.bucket == bucket).collect() +} + +#[test] +fn topic_3_view_tag_filter_across_announcer_splitter_and_batch_sender() { + let env = Env::default(); + env.mock_all_auths(); + + // Shared announcer — splitter and batch-sender route through it, so every + // announcement (all three sources) uses the same 4-topic v2 layout. + let announcer_id = env.register(StealthAnnouncerContract, ()); + let announcer = StealthAnnouncerContractClient::new(&env, &announcer_id); + + let funder = Address::generate(&env); + let token = funded_token(&env, &funder, 1_000_000); + + let mut harvested: std::vec::Vec = std::vec::Vec::new(); + + // ── Source 1: direct announcer ────────────────────────────────────────── + let direct_match = Address::generate(&env); + let direct_other = Address::generate(&env); + announcer.announce( + &STELLAR_V2_SCHEME_ID, + &direct_match, + &bytes32(&env, 0x11), + &meta(&env, MATCH_TAG), + ); + harvest_announces(&env, &mut harvested); + announcer.announce( + &STELLAR_V2_SCHEME_ID, + &direct_other, + &bytes32(&env, 0x12), + &meta(&env, OTHER_TAGS[0]), + ); + harvest_announces(&env, &mut harvested); + + // ── Source 2: splitter (already routes through announcer) ──────────────── + let splitter_id = env.register(StealthSplitterContract, ()); + let splitter = StealthSplitterContractClient::new(&env, &splitter_id); + splitter.init(&announcer_id); + + let mut beneficiaries = soroban_sdk::vec![&env]; + beneficiaries.push_back(Beneficiary { + meta_address: bytes(&env, &[0x11u8; 64]), + weight: 1, + }); + beneficiaries.push_back(Beneficiary { + meta_address: bytes(&env, &[0x22u8; 64]), + weight: 1, + }); + let split_id = splitter.create_split( + &funder, + &beneficiaries, + &token, + &bytes(&env, b"topic-filter-salt"), + ); + + let split_match = Address::generate(&env); + let split_other = Address::generate(&env); + splitter.fund_split( + &funder, + &split_id, + &2_000, + &STELLAR_V2_SCHEME_ID, + &soroban_sdk::vec![&env, split_match.clone(), split_other.clone()], + &soroban_sdk::vec![&env, bytes32(&env, 0x21), bytes32(&env, 0x22)], + &soroban_sdk::vec![&env, meta(&env, MATCH_TAG), meta(&env, OTHER_TAGS[1])], + ); + harvest_announces(&env, &mut harvested); + + // ── Source 3: batch-sender (now routes through announcer) ──────────────── + let batch_id = env.register(StealthBatchSender, ()); + let batch = StealthBatchSenderClient::new(&env, &batch_id); + let admin = Address::generate(&env); + batch.init(&admin, &announcer_id, &None); + + let batch_match = Address::generate(&env); + let batch_other = Address::generate(&env); + let transfers = soroban_sdk::vec![ + &env, + Transfer { + stealth_address: batch_match.clone(), + ephemeral_pub_key: bytes(&env, &[0x31u8; 32]), + amount: 100, + metadata: meta(&env, MATCH_TAG), + }, + Transfer { + stealth_address: batch_other.clone(), + ephemeral_pub_key: bytes(&env, &[0x32u8; 32]), + amount: 200, + metadata: meta(&env, OTHER_TAGS[2]), + }, + ]; + batch.batch_send(&funder, &transfers, &token); + harvest_announces(&env, &mut harvested); + + // ── Assertions ────────────────────────────────────────────────────────── + assert_eq!( + harvested.len(), + 6, + "two announcements from each of the three sources" + ); + assert!(harvested.iter().all(|e| e.contract == announcer_id)); + + let matched = filter_by_view_tag(&harvested, MATCH_TAG as u32); + assert_eq!( + matched.len(), + 3, + "topic-3 filter must keep exactly one event per source" + ); + + let seen_match_addrs: std::vec::Vec
= matched + .iter() + .map(|e| { + assert_eq!(e.metadata, meta(&env, MATCH_TAG)); + e.stealth.clone() + }) + .collect(); + + assert!(seen_match_addrs.contains(&direct_match)); + assert!(seen_match_addrs.contains(&split_match)); + assert!(seen_match_addrs.contains(&batch_match)); + assert!(!seen_match_addrs.contains(&direct_other)); + assert!(!seen_match_addrs.contains(&split_other)); + assert!(!seen_match_addrs.contains(&batch_other)); + + // Negative: a tag that none of the sources used yields an empty subset. + assert_eq!(filter_by_view_tag(&harvested, 255).len(), 0); +} diff --git a/stellar/stealth-batch-sender/Cargo.toml b/stellar/stealth-batch-sender/Cargo.toml index 7d77672..5b9a1d7 100644 --- a/stellar/stealth-batch-sender/Cargo.toml +++ b/stellar/stealth-batch-sender/Cargo.toml @@ -16,4 +16,5 @@ wraith-metrics = { path = "../wraith-metrics" } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } +stealth-announcer = { path = "../stealth-announcer" } diff --git a/stellar/stealth-batch-sender/README.md b/stellar/stealth-batch-sender/README.md index 9598436..e568c22 100644 --- a/stellar/stealth-batch-sender/README.md +++ b/stellar/stealth-batch-sender/README.md @@ -1,116 +1,92 @@ -# Stealth Batch Sender Contract (`stealth-batch-sender`) +Stealth Batch Sender Contract (stealth-batch-sender) +The stealth-batch-sender contract atomically sends tokens from a single sender to multiple pre-computed stealth addresses in a single transaction. This provides ~100x efficiency over N individual stealth-sender::send calls by requiring only one authorization and one ledger round-trip. -The `stealth-batch-sender` contract atomically sends tokens from a single sender to multiple pre-computed stealth addresses in a single transaction. This provides ~100x efficiency over N individual `stealth-sender::send` calls by requiring only one authorization and one ledger round-trip. +Purpose +Batch stealth transfers for efficiency. Instead of calling stealth-sender::send N times (N transactions, N auth signatures, N ledger round-trips), a single batch_send handles all transfers atomically in one transaction. -## Purpose +Entrypoints +Function Description Authorization +batch_send(env, from, transfers, asset) Atomically send asset tokens to N stealth addresses from must authorize +max_batch_size(env) Query the maximum allowed batch size None (read-only) +batch_send +Atomically send asset tokens from from to N pre-computed stealth addresses in a single transaction. -Batch stealth transfers for efficiency. Instead of calling `stealth-sender::send` N times (N transactions, N auth signatures, N ledger round-trips), a single `batch_send` handles all transfers atomically in one transaction. +All-or-nothing semantics: Soroban's transaction model guarantees atomicity. If any individual transfer panics (e.g., insufficient balance mid-batch), the entire transaction is rolled back. No partial sends are possible. -## Entrypoints +Parameters: -| Function | Description | Authorization | -|----------|-------------|----------------| -| `batch_send(env, from, transfers, asset)` | Atomically send `asset` tokens to N stealth addresses | `from` must authorize | -| `max_batch_size(env)` | Query the maximum allowed batch size | None (read-only) | +from: Address — Sender address (must authorize) +transfers: Vec — Array of stealth transfers +asset: Address — Token contract address +Returns: None -### `batch_send` +Transfer struct: -Atomically send `asset` tokens from `from` to N pre-computed stealth addresses in a single transaction. +Rust -**All-or-nothing semantics:** Soroban's transaction model guarantees atomicity. If any individual transfer panics (e.g., insufficient balance mid-batch), the entire transaction is rolled back. No partial sends are possible. - -**Parameters:** -- `from: Address` — Sender address (must authorize) -- `transfers: Vec` — Array of stealth transfers -- `asset: Address` — Token contract address - -**Returns:** None - -**Transfer struct:** -```rust pub struct Transfer { pub stealth_address: Address, // Pre-computed stealth address (recipient) - pub ephemeral_pub_key: Bytes, // Ephemeral public key for recipient scanning + pub ephemeral_pub_key: Bytes, // 32-byte ephemeral public key for recipient scanning pub amount: i128, // Token amount (in asset's base unit) + pub metadata: Bytes, // First byte is the view tag (metadata_kind = 1) } -``` - -**Validation:** -- Batch must contain at least 1 transfer -- Batch size cannot exceed `MAX_BATCH_SIZE` (100) -- Each transfer amount must be positive -- Each `ephemeral_pub_key` must not be empty - -**Events emitted:** -- Per-transfer: `("ANNOUNCE",)` with `(stealth_address, ephemeral_pub_key, amount, asset)` -- Batch summary: `("BATCH",)` with `(from, count, asset)` -- Metric events: `batch_send_count`, `batch_send_volume`, `batch_size` (see [METRICS.md](../METRICS.md)) - -### `max_batch_size` - +Validation: + +Batch must contain at least 1 transfer +Batch size cannot exceed MAX_BATCH_SIZE (100) +Each transfer amount must be positive +Each ephemeral_pub_key must be exactly 32 bytes (non-empty) +Events emitted: + +Per-transfer: routed through stealth-announcer as ("announce", scheme_id, view_tag_bucket, metadata_kind) with data (stealth_address, ephemeral_pub_key, metadata). view_tag_bucket = metadata[0] as u32. +Batch summary: ("BATCH",) with (from, count, asset) +Metric events: batch_send_count, batch_send_volume, batch_size (see METRICS.md) +max_batch_size Query the maximum allowed batch size. -**Returns:** `u32` — Current `MAX_BATCH_SIZE` constant (100) - -## Error Variants +Returns: u32 — Current MAX_BATCH_SIZE constant (100) +Error Variants The contract uses panics for validation (no custom error enum): -| Condition | Panic Reason | -|-----------|--------------| -| Empty `transfers` array | "batch must contain at least one transfer" | -| `transfers.len() > MAX_BATCH_SIZE` | "batch exceeds MAX_BATCH_SIZE" | -| `transfer.amount <= 0` | "transfer amount must be positive" | -| `transfer.ephemeral_pub_key.is_empty()` | "ephemeral_pub_key must not be empty" | - -## Event Topics - -| Topic | Data | Description | -|-------|------|-------------| -| `("ANNOUNCE",)` | `(stealth_address, ephemeral_pub_key, amount, asset)` | Per-transfer stealth payment announcement | -| `("BATCH",)` | `(from, count, asset)` | Batch-level summary event | -| `("metric", contract_id, metric_name)` | `(value, dimensions)` | Metric events (see [METRICS.md](../METRICS.md)) | - -## Storage Layout - -**None** — This contract is stateless and uses no persistent storage. +Condition Panic Reason +Empty transfers array "batch must contain at least one transfer" +transfers.len() > MAX_BATCH_SIZE "batch exceeds MAX_BATCH_SIZE" +transfer.amount <= 0 "transfer amount must be positive" +transfer.ephemeral_pub_key.is_empty() "ephemeral_pub_key must not be empty" +Event Topics +Topic Data Description +("announce", scheme_id, view_tag_bucket, metadata_kind) (stealth_address, ephemeral_pub_key, metadata) Per-transfer stealth payment announcement, emitted by the announcer contract (v2 layout) +("BATCH",) (from, count, asset) Batch-level summary event +("metric", contract_id, metric_name) (value, dimensions) Metric events (see METRICS.md) +Storage Layout +None — This contract is stateless and uses no persistent storage. + +Pause / Admin / Metrics Posture +Feature Status +Pausable No — stateless, nothing to pause +Admin No — no admin controls +Metrics Yes — emits batch_send_count, batch_send_volume, batch_size metrics +Related Docs +PAUSE.md — Pause posture documentation +MULTISIG.md — Multisig setup documentation +METRICS.md — Metrics standard documentation +Constants +MAX_BATCH_SIZE: u32 = 100 — Maximum transfers per batch, justified against Soroban's ~100M instruction budget. Each transfer costs ~500K instructions (token transfer + event emit). 100 transfers = ~50M instructions, leaving headroom for overhead. +Resource Budget +Instruction usage: ~500K instructions per transfer (token transfer + event emit) +Max batch: 100 transfers = ~50M instructions (under Soroban's ~100M limit) +Efficiency gain: ~100x vs N individual stealth-sender calls (1 auth vs N auths, 1 ledger round-trip vs N) +Testing +Bash -## Pause / Admin / Metrics Posture - -| Feature | Status | -|---------|--------| -| Pausable | No — stateless, nothing to pause | -| Admin | No — no admin controls | -| Metrics | Yes — emits `batch_send_count`, `batch_send_volume`, `batch_size` metrics | - -## Related Docs - -- [PAUSE.md](../PAUSE.md) — Pause posture documentation -- [MULTISIG.md](../MULTISIG.md) — Multisig setup documentation -- [METRICS.md](../METRICS.md) — Metrics standard documentation - -## Constants - -- `MAX_BATCH_SIZE: u32 = 100` — Maximum transfers per batch, justified against Soroban's ~100M instruction budget. Each transfer costs ~500K instructions (token transfer + event emit). 100 transfers = ~50M instructions, leaving headroom for overhead. - -## Resource Budget - -- **Instruction usage:** ~500K instructions per transfer (token transfer + event emit) -- **Max batch:** 100 transfers = ~50M instructions (under Soroban's ~100M limit) -- **Efficiency gain:** ~100x vs N individual stealth-sender calls (1 auth vs N auths, 1 ledger round-trip vs N) - -## Testing - -```bash cargo test -p stealth-batch-sender -``` - Tests cover: -- Batch send with multiple transfers -- Empty batch rejection -- Batch size limit enforcement -- Positive amount validation -- Non-empty ephemeral_pub_key validation -- Metric event emission - -Error codes and panic-only coverage status are tracked in the [Stellar error catalog](../ERRORS.md#stealth-batch-sender). \ No newline at end of file + +Batch send with multiple transfers +Empty batch rejection +Batch size limit enforcement +Positive amount validation +Non-empty ephemeral_pub_key validation +Metric event emission +Error codes and panic-only coverage status are tracked in the Stellar error catalog. \ No newline at end of file diff --git a/stellar/stealth-batch-sender/src/lib.rs b/stellar/stealth-batch-sender/src/lib.rs index 1995de5..d956847 100644 --- a/stellar/stealth-batch-sender/src/lib.rs +++ b/stellar/stealth-batch-sender/src/lib.rs @@ -2,7 +2,7 @@ use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, symbol_short, - token::Client as TokenClient, Address, Env, IntoVal, Vec, + token::Client as TokenClient, Address, Bytes, BytesN, Env, IntoVal, Vec, }; use wraith_metrics::{contract_ids, dimension_names, emit_metric, metric_names}; @@ -17,6 +17,10 @@ mod test; /// 100 transfers = ~50M instructions, leaving headroom for overhead. pub const MAX_BATCH_SIZE: u32 = 100; +/// Stellar v2 stealth scheme id. Per-transfer announcements are routed through +/// the announcer contract, which only accepts this scheme. +pub const STELLAR_V2_SCHEME_ID: u32 = 2; + /// Storage keys. #[contracttype] #[derive(Clone)] @@ -24,7 +28,8 @@ pub enum DataKey { /// Pause admin address. Admin, /// Address of the deployed StealthAnnouncer contract, recorded at init - /// time. Not yet invoked by `batch_send` — see IMPLEMENTATION_NOTES.md. + /// time. Invoked by `batch_send` for each transfer so announcements use + /// the v2 4-topic layout (`announce`, scheme_id, view_tag_bucket, metadata_kind). Announcer, /// Optional address of the asset policy contract. AssetPolicy, @@ -56,7 +61,7 @@ pub enum BatchSenderError { BatchTooLarge = 1303, /// A transfer amount was zero or negative. NonPositiveAmount = 1304, - /// A transfer's ephemeral public key was empty. + /// A transfer's ephemeral public key was empty or not 32 bytes. EmptyEphemeralKey = 1305, /// The contract is paused. Paused = 1306, @@ -89,10 +94,52 @@ pub enum BatchSenderError { pub struct Transfer { /// Pre-computed stealth address (recipient) pub stealth_address: Address, - /// Ephemeral public key for the recipient to scan with - pub ephemeral_pub_key: soroban_sdk::Bytes, + /// Ephemeral public key for the recipient to scan with. + /// Must be exactly 32 bytes so it can be forwarded to the announcer. + pub ephemeral_pub_key: Bytes, /// Token amount (in the asset's base unit) pub amount: i128, + /// Announcement metadata whose first byte is the view tag + /// (`view_tag_bucket = metadata[0] as u32` under metadata_kind = 1). + pub metadata: Bytes, +} + +/// Lightweight client wrapper that invokes the StealthAnnouncer contract via +/// `env.invoke_contract`. Same pattern as `stealth-splitter` / `stealth-sender`. +mod announcer_client { + use soroban_sdk::{Address, Bytes, BytesN, Env, IntoVal}; + + pub fn announce( + env: &Env, + announcer: &Address, + scheme_id: u32, + stealth_address: &Address, + ephemeral_pub_key: &BytesN<32>, + metadata: &Bytes, + ) { + let _: () = env.invoke_contract( + announcer, + &soroban_sdk::symbol_short!("announce"), + soroban_sdk::vec![ + env, + scheme_id.into_val(env), + stealth_address.into_val(env), + ephemeral_pub_key.into_val(env), + metadata.into_val(env), + ], + ); + } +} + +/// Convert a 32-byte `Bytes` ephemeral key into the `BytesN<32>` the announcer +/// expects. Empty or wrong-length keys map to `EmptyEphemeralKey`. +fn ephemeral_key_to_bytesn(env: &Env, key: &Bytes) -> Result, BatchSenderError> { + if key.is_empty() || key.len() != 32 { + return Err(BatchSenderError::EmptyEphemeralKey); + } + let mut buf = [0u8; 32]; + key.copy_into_slice(&mut buf); + Ok(BytesN::from_array(env, &buf)) } /// Wrapper for calling check_asset on the optional asset policy contract. @@ -241,12 +288,18 @@ impl StealthBatchSender { return Err(BatchSenderError::BatchTooLarge); } + let announcer: Address = env + .storage() + .instance() + .get(&DataKey::Announcer) + .ok_or(BatchSenderError::NotInitialized)?; + // Validate every entry before executing any transfer. for transfer in transfers.iter() { if transfer.amount <= 0 { return Err(BatchSenderError::NonPositiveAmount); } - if transfer.ephemeral_pub_key.is_empty() { + if transfer.ephemeral_pub_key.is_empty() || transfer.ephemeral_pub_key.len() != 32 { return Err(BatchSenderError::EmptyEphemeralKey); } } @@ -261,15 +314,17 @@ impl StealthBatchSender { // Execute transfer — any failure here aborts the whole tx (atomicity) token.transfer(&from, &transfer.stealth_address, &transfer.amount); - // Per-transfer announcement (mirrors stealth-sender pattern) - env.events().publish( - (symbol_short!("ANNOUNCE"),), - ( - transfer.stealth_address.clone(), - transfer.ephemeral_pub_key.clone(), - transfer.amount, - asset.clone(), - ), + // Per-transfer announcement via the announcer contract so indexers + // see the v2 4-topic layout (`announce`, scheme_id, view_tag_bucket, + // metadata_kind) and can filter on topic-3 (view_tag_bucket). + let ephemeral_pub_key = ephemeral_key_to_bytesn(&env, &transfer.ephemeral_pub_key)?; + announcer_client::announce( + &env, + &announcer, + STELLAR_V2_SCHEME_ID, + &transfer.stealth_address, + &ephemeral_pub_key, + &transfer.metadata, ); } diff --git a/stellar/stealth-batch-sender/src/test.rs b/stellar/stealth-batch-sender/src/test.rs index 166d30f..bdbaaaa 100644 --- a/stellar/stealth-batch-sender/src/test.rs +++ b/stellar/stealth-batch-sender/src/test.rs @@ -2,9 +2,12 @@ use super::*; use soroban_sdk::{ - testutils::Address as _, + testutils::{Address as _, Events}, token::{Client as TokenClient, StellarAssetClient}, - vec, Address, Bytes, Env, + vec, Address, Bytes, BytesN, Env, FromVal, IntoVal, Val, +}; +use stealth_announcer::{ + StealthAnnouncerContract, METADATA_KIND_VIEW_TAG, STELLAR_V2_SCHEME_ID as ANNOUNCER_SCHEME_ID, }; fn create_token<'a>(env: &Env, admin: &Address) -> (TokenClient<'a>, StellarAssetClient<'a>) { @@ -16,17 +19,25 @@ fn create_token<'a>(env: &Env, admin: &Address) -> (TokenClient<'a>, StellarAsse } fn dummy_pub_key(env: &Env) -> Bytes { - Bytes::from_slice(env, &[0x02u8; 33]) // compressed secp256k1 pubkey + Bytes::from_slice(env, &[0x02u8; 32]) +} + +fn dummy_metadata(env: &Env) -> Bytes { + Bytes::from_slice(env, &[0x2Au8]) +} + +fn metadata_with_tag(env: &Env, tag: u8) -> Bytes { + Bytes::from_slice(env, &[tag]) } /// Registers the contract and initialises it with a fresh admin + -/// announcer, no asset policy. Returns the client plus the admin address +/// a real announcer, no asset policy. Returns the client plus the admin address /// (tests that need to pause/unpause use it). fn setup(env: &Env) -> (StealthBatchSenderClient<'static>, Address) { let contract_id = env.register(StealthBatchSender, ()); let client = StealthBatchSenderClient::new(env, &contract_id); let admin = Address::generate(env); - let announcer = Address::generate(env); + let announcer = env.register(StealthAnnouncerContract, ()); client.init(&admin, &announcer, &None); (client, admin) } @@ -54,16 +65,19 @@ fn test_batch_send_success() { stealth_address: stealth1.clone(), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, Transfer { stealth_address: stealth2.clone(), ephemeral_pub_key: dummy_pub_key(&env), amount: 200, + metadata: dummy_metadata(&env), }, Transfer { stealth_address: stealth3.clone(), ephemeral_pub_key: dummy_pub_key(&env), amount: 300, + metadata: dummy_metadata(&env), }, ]; @@ -75,6 +89,78 @@ fn test_batch_send_success() { assert_eq!(token.balance(&stealth3), 300); } +#[test] +fn test_batch_send_emits_v2_four_topic_announce() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let sender = Address::generate(&env); + let (token, token_admin) = create_token(&env, &admin); + token_admin.mint(&sender, &1000); + + let (client, _) = setup(&env); + + let stealth_a = Address::generate(&env); + let stealth_b = Address::generate(&env); + + let transfers = vec![ + &env, + Transfer { + stealth_address: stealth_a.clone(), + ephemeral_pub_key: dummy_pub_key(&env), + amount: 100, + metadata: metadata_with_tag(&env, 7), + }, + Transfer { + stealth_address: stealth_b.clone(), + ephemeral_pub_key: dummy_pub_key(&env), + amount: 200, + metadata: metadata_with_tag(&env, 99), + }, + ]; + + client.batch_send(&sender, &transfers, &token.address); + + let announce_sym: Val = symbol_short!("announce").into_val(&env); + let mut announce_events = vec![&env]; + for event in env.events().all().iter() { + let first: Option = event.1.first(); + if first.map(|t| t.shallow_eq(&announce_sym)) == Some(true) { + announce_events.push_back(event); + } + } + + assert_eq!( + announce_events.len(), + 2, + "batch-sender must emit one v2 announce per transfer" + ); + + let expected_buckets = [7u32, 99u32]; + let expected_addrs = [stealth_a, stealth_b]; + for i in 0..2 { + let event = announce_events.get(i).unwrap(); + assert_eq!(event.1.len(), 4, "v2 announce uses all 4 topic slots"); + + let expected_topics: soroban_sdk::Vec = vec![ + &env, + symbol_short!("announce").into_val(&env), + ANNOUNCER_SCHEME_ID.into_val(&env), + expected_buckets[i as usize].into_val(&env), + METADATA_KIND_VIEW_TAG.into_val(&env), + ]; + assert_eq!(event.1, expected_topics); + + let (addr, _epk, meta): (Address, BytesN<32>, Bytes) = FromVal::from_val(&env, &event.2); + assert_eq!(addr, expected_addrs[i as usize]); + assert_eq!( + meta, + metadata_with_tag(&env, expected_buckets[i as usize] as u8) + ); + } +} + #[test] fn test_batch_send_rejects_uninitialized() { let env = Env::default(); @@ -94,6 +180,7 @@ fn test_batch_send_rejects_uninitialized() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; @@ -131,6 +218,7 @@ fn test_batch_size_cap() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 1, + metadata: dummy_metadata(&env), }); } @@ -161,16 +249,19 @@ fn test_atomicity_on_mid_batch_failure() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, Transfer { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, Transfer { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; @@ -211,6 +302,7 @@ fn test_zero_amount_rejected() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 0, + metadata: dummy_metadata(&env), }, ]; @@ -236,6 +328,7 @@ fn test_empty_ephemeral_key_rejected() { stealth_address: Address::generate(&env), ephemeral_pub_key: Bytes::new(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_atomicity_on_mid_batch_failure.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_atomicity_on_mid_batch_failure.1.json index 72e2623..52520c4 100644 --- a/stellar/stealth-batch-sender/test_snapshots/test/test_atomicity_on_mid_batch_failure.1.json +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_atomicity_on_mid_batch_failure.1.json @@ -1,6 +1,6 @@ { "generators": { - "address": 7, + "address": 9, "nonce": 0 }, "auth": [ @@ -49,6 +49,8 @@ ] ], [], + [], + [], [] ], "ledger": { @@ -172,6 +174,63 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": "ledger_key_contract_instance", "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", "val": { "contract_instance": { "executable": { @@ -411,7 +470,7 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" }, { "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" @@ -431,31 +490,34 @@ { "event": { "ext": "v0", - "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", "type_": "contract", "body": { "v0": { "topics": [ { - "symbol": "ANNOUNCE" + "symbol": "announce" + }, + { + "u32": 2 + }, + { + "u32": 42 + }, + { + "u32": 1 } ], "data": { "vec": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" - }, - { - "bytes": "020202020202020202020202020202020202020202020202020202020202020202" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" }, { - "i128": { - "hi": 0, - "lo": 100 - } + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" }, { - "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + "bytes": "2a" } ] } diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_emits_v2_four_topic_announce.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_emits_v2_four_topic_announce.1.json new file mode 100644 index 0000000..a427ed9 --- /dev/null +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_emits_v2_four_topic_announce.1.json @@ -0,0 +1,1101 @@ +{ + "generators": { + "address": 8, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "batch_send", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, + { + "key": { + "symbol": "ephemeral_pub_key" + }, + "val": { + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "07" + } + }, + { + "key": { + "symbol": "stealth_address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 200 + } + } + }, + { + "key": { + "symbol": "ephemeral_pub_key" + }, + "val": { + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "63" + } + }, + { + "key": { + "symbol": "stealth_address" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + } + ] + } + ] + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [] + }, + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "i128": { + "hi": 0, + "lo": 200 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 700 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 200 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "692c360a04a982db02db346a106cbf008ad9e058c384bdaaf77bc0c48799b3a4", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 100 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "announce" + }, + { + "u32": 2 + }, + { + "u32": 7 + }, + { + "u32": 1 + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "bytes": "07" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "692c360a04a982db02db346a106cbf008ad9e058c384bdaaf77bc0c48799b3a4", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "transfer" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 200 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000006", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "announce" + }, + { + "u32": 2 + }, + { + "u32": 99 + }, + { + "u32": 1 + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "bytes": "63" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "BATCH" + } + ], + "data": { + "vec": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "u32": 2 + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "metric" + }, + { + "symbol": "st_bat_sd" + }, + { + "symbol": "bat_send" + } + ], + "data": { + "vec": [ + { + "i128": { + "hi": 0, + "lo": 1 + } + }, + { + "vec": [ + { + "vec": [ + { + "symbol": "ast_addr" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "metric" + }, + { + "symbol": "st_bat_sd" + }, + { + "symbol": "bat_vol" + } + ], + "data": { + "vec": [ + { + "i128": { + "hi": 0, + "lo": 300 + } + }, + { + "vec": [ + { + "vec": [ + { + "symbol": "ast_addr" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000004", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "metric" + }, + { + "symbol": "st_bat_sd" + }, + { + "symbol": "bat_size" + } + ], + "data": { + "vec": [ + { + "i128": { + "hi": 0, + "lo": 2 + } + }, + { + "vec": [ + { + "vec": [ + { + "symbol": "ast_addr" + }, + { + "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF" + } + ] + } + ] + } + ] + } + } + } + }, + "failed_call": false + } + ] +} \ No newline at end of file diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_rejects_uninitialized.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_rejects_uninitialized.1.json new file mode 100644 index 0000000..6d4e683 --- /dev/null +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_rejects_uninitialized.1.json @@ -0,0 +1,399 @@ +{ + "generators": { + "address": 5, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_success.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_success.1.json index 25705ab..a0adb1e 100644 --- a/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_success.1.json +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_send_success.1.json @@ -1,6 +1,6 @@ { "generators": { - "address": 7, + "address": 9, "nonce": 0 }, "auth": [ @@ -49,6 +49,8 @@ ] ], [], + [], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -81,7 +83,15 @@ "symbol": "ephemeral_pub_key" }, "val": { - "bytes": "020202020202020202020202020202020202020202020202020202020202020202" + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "2a" } }, { @@ -89,7 +99,7 @@ "symbol": "stealth_address" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" } } ] @@ -112,7 +122,15 @@ "symbol": "ephemeral_pub_key" }, "val": { - "bytes": "020202020202020202020202020202020202020202020202020202020202020202" + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "2a" } }, { @@ -120,7 +138,7 @@ "symbol": "stealth_address" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" } } ] @@ -143,7 +161,15 @@ "symbol": "ephemeral_pub_key" }, "val": { - "bytes": "020202020202020202020202020202020202020202020202020202020202020202" + "bytes": "0202020202020202020202020202020202020202020202020202020202020202" + } + }, + { + "key": { + "symbol": "metadata" + }, + "val": { + "bytes": "2a" } }, { @@ -151,7 +177,7 @@ "symbol": "stealth_address" }, "val": { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" } } ] @@ -175,7 +201,7 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" }, { "i128": { @@ -198,7 +224,7 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" }, { "i128": { @@ -221,7 +247,7 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" }, { "i128": { @@ -397,6 +423,63 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": "ledger_key_contract_instance", "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", "val": { "contract_instance": { "executable": { @@ -495,7 +578,7 @@ "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" } ] }, @@ -515,7 +598,7 @@ "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" } ] }, @@ -568,7 +651,7 @@ "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" } ] }, @@ -588,7 +671,7 @@ "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" } ] }, @@ -641,7 +724,7 @@ "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" } ] }, @@ -661,7 +744,7 @@ "symbol": "Balance" }, { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATYON" } ] }, diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_batch_size_cap.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_size_cap.1.json index 9c4d4d0..5006a56 100644 --- a/stellar/stealth-batch-sender/test_snapshots/test/test_batch_size_cap.1.json +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_batch_size_cap.1.json @@ -1,6 +1,6 @@ { "generators": { - "address": 105, + "address": 107, "nonce": 0 }, "auth": [ @@ -49,6 +49,8 @@ ] ], [], + [], + [], [] ], "ledger": { @@ -172,6 +174,63 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": "ledger_key_contract_instance", "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", "val": { "contract_instance": { "executable": { diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_empty_batch_rejected.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_empty_batch_rejected.1.json index a8bd45b..d303433 100644 --- a/stellar/stealth-batch-sender/test_snapshots/test/test_empty_batch_rejected.1.json +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_empty_batch_rejected.1.json @@ -1,6 +1,6 @@ { "generators": { - "address": 4, + "address": 6, "nonce": 0 }, "auth": [ @@ -24,6 +24,8 @@ ] ], [], + [], + [], [] ], "ledger": { @@ -114,6 +116,63 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": "ledger_key_contract_instance", "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", "val": { "contract_instance": { "executable": { diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_empty_ephemeral_key_rejected.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_empty_ephemeral_key_rejected.1.json new file mode 100644 index 0000000..3989166 --- /dev/null +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_empty_ephemeral_key_rejected.1.json @@ -0,0 +1,458 @@ +{ + "generators": { + "address": 7, + "nonce": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": 0, + "seq_num": 0, + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_init_rejects_second_call.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_init_rejects_second_call.1.json new file mode 100644 index 0000000..f658ea8 --- /dev/null +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_init_rejects_second_call.1.json @@ -0,0 +1,135 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/stellar/stealth-batch-sender/test_snapshots/test/test_zero_amount_rejected.1.json b/stellar/stealth-batch-sender/test_snapshots/test/test_zero_amount_rejected.1.json index 6d4e683..3989166 100644 --- a/stellar/stealth-batch-sender/test_snapshots/test/test_zero_amount_rejected.1.json +++ b/stellar/stealth-batch-sender/test_snapshots/test/test_zero_amount_rejected.1.json @@ -1,6 +1,6 @@ { "generators": { - "address": 5, + "address": 7, "nonce": 0 }, "auth": [ @@ -49,6 +49,8 @@ ] ], [], + [], + [], [] ], "ledger": { @@ -172,6 +174,63 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": "ledger_key_contract_instance", "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Announcer" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4", + "key": "ledger_key_contract_instance", + "durability": "persistent", "val": { "contract_instance": { "executable": { diff --git a/stellar/stealth-batch-sender/tests/production.rs b/stellar/stealth-batch-sender/tests/production.rs index 373c71c..bca0857 100644 --- a/stellar/stealth-batch-sender/tests/production.rs +++ b/stellar/stealth-batch-sender/tests/production.rs @@ -9,12 +9,21 @@ use soroban_sdk::{ token::StellarAssetClient, vec, Address, Bytes, Env, }; +use stealth_announcer::StealthAnnouncerContract; use stealth_batch_sender::{ BatchSenderError, StealthBatchSender, StealthBatchSenderClient, Transfer, MAX_BATCH_SIZE, }; fn dummy_pub_key(env: &Env) -> Bytes { - Bytes::from_slice(env, &[0x02u8; 33]) + Bytes::from_slice(env, &[0x02u8; 32]) +} + +fn dummy_metadata(env: &Env) -> Bytes { + Bytes::from_slice(env, &[0x2Au8]) +} + +fn real_announcer(env: &Env) -> Address { + env.register(StealthAnnouncerContract, ()) } fn create_token(env: &Env, admin: &Address) -> (Address, StellarAssetClient<'static>) { @@ -99,6 +108,7 @@ fn batch_send_requires_init() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; @@ -160,6 +170,7 @@ fn batch_send_rejected_when_paused() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; @@ -179,7 +190,7 @@ fn batch_send_allowed_after_unpause() { let client = deploy(&env); let admin = Address::generate(&env); - let announcer = Address::generate(&env); + let announcer = real_announcer(&env); client.init(&admin, &announcer, &None); let sender = Address::generate(&env); @@ -193,6 +204,7 @@ fn batch_send_allowed_after_unpause() { stealth_address: stealth.clone(), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; @@ -245,6 +257,7 @@ fn oversized_batch_returns_typed_error() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 1, + metadata: dummy_metadata(&env), }); } @@ -272,6 +285,7 @@ fn non_positive_amount_returns_typed_error() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 0, + metadata: dummy_metadata(&env), }, ]; @@ -299,6 +313,7 @@ fn empty_ephemeral_key_returns_typed_error() { stealth_address: Address::generate(&env), ephemeral_pub_key: Bytes::new(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; @@ -327,6 +342,7 @@ fn asset_not_allowed_by_policy_returns_typed_error() { stealth_address: Address::generate(&env), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, ]; @@ -341,7 +357,7 @@ fn asset_allowed_by_policy_succeeds() { let client = deploy(&env); let admin = Address::generate(&env); - let announcer = Address::generate(&env); + let announcer = real_announcer(&env); let policy_id = env.register(AllowAllPolicy, ()); client.init(&admin, &announcer, &Some(policy_id)); @@ -356,6 +372,7 @@ fn asset_allowed_by_policy_succeeds() { stealth_address: stealth.clone(), ephemeral_pub_key: dummy_pub_key(&env), amount: 100, + metadata: dummy_metadata(&env), }, ];