From 05b80cacffe36d1a7f08d3e9997a79e8b975813d Mon Sep 17 00:00:00 2001 From: Marvin Nkut Date: Mon, 30 Mar 2026 11:22:06 +0100 Subject: [PATCH 1/4] feat: add spec for issue #597 - test register_deposit emits DepositRegistered event --- .../.config.kiro | 1 + .../requirements.md | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 .kiro/specs/issue-597-test-deposit-registered-event/.config.kiro create mode 100644 .kiro/specs/issue-597-test-deposit-registered-event/requirements.md diff --git a/.kiro/specs/issue-597-test-deposit-registered-event/.config.kiro b/.kiro/specs/issue-597-test-deposit-registered-event/.config.kiro new file mode 100644 index 0000000..239f0fe --- /dev/null +++ b/.kiro/specs/issue-597-test-deposit-registered-event/.config.kiro @@ -0,0 +1 @@ +{"specId": "db8875b2-abc0-44d0-9b9a-cdd038962f9a", "workflowType": "requirements-first", "specType": "feature"} diff --git a/.kiro/specs/issue-597-test-deposit-registered-event/requirements.md b/.kiro/specs/issue-597-test-deposit-registered-event/requirements.md new file mode 100644 index 0000000..0eea9b5 --- /dev/null +++ b/.kiro/specs/issue-597-test-deposit-registered-event/requirements.md @@ -0,0 +1,39 @@ +# Requirements Document + +## Introduction + +Add a test in `tests/contract_test.rs` that asserts `register_deposit` emits an `Event::DepositRegistered(tx_id, anchor_transaction_id)` event containing the correct IDs. Currently no test in `contract_test.rs` verifies the payload of the `DepositRegistered` event, leaving a gap in coverage for this critical registration signal. + +## Glossary + +- **SynapseContract**: The Soroban smart contract under test, defined in `src/lib.rs`. +- **register_deposit**: The contract method that registers a new deposit and returns a `tx_id`. +- **DepositRegistered**: The `Event` variant emitted by `register_deposit`, carrying `(tx_id: SorobanString, anchor_transaction_id: SorobanString)`. +- **tx_id**: The unique transaction identifier returned by `register_deposit` and generated internally via `next_id`. +- **anchor_transaction_id**: The caller-supplied external anchor transaction identifier passed to `register_deposit`. +- **Event**: The `#[contracttype]` enum in `src/types/mod.rs` representing all contract events. +- **TestEnv**: The Soroban `Env::default()` test environment used in integration tests. + +## Requirements + +### Requirement 1: Emit DepositRegistered Event with Correct Payload + +**User Story:** As a contract integrator, I want `register_deposit` to emit a `DepositRegistered` event containing the returned `tx_id` and the supplied `anchor_transaction_id`, so that I can reliably index and audit deposit registrations off-chain. + +#### Acceptance Criteria + +1. WHEN `register_deposit` is called with a valid `anchor_transaction_id`, THE `SynapseContract` SHALL emit exactly one `Event::DepositRegistered` event. +2. WHEN `register_deposit` emits `Event::DepositRegistered(tx_id, anchor_id)`, THE first field SHALL equal the `SorobanString` returned by `register_deposit`. +3. WHEN `register_deposit` emits `Event::DepositRegistered(tx_id, anchor_id)`, THE second field SHALL equal the `anchor_transaction_id` argument supplied by the caller. +4. THE `test_register_deposit_emits_event` test SHALL be added to `tests/contract_test.rs` and SHALL pass under `cargo test`. + +### Requirement 2: Test Isolation and Setup + +**User Story:** As a developer maintaining the test suite, I want the new test to use the existing `setup` helper and follow the conventions in `contract_test.rs`, so that the test is consistent and easy to maintain. + +#### Acceptance Criteria + +1. THE new test SHALL use the `setup` helper defined in `tests/contract_test.rs` to initialise the contract, admin, and client. +2. THE new test SHALL grant a relayer, add an asset, and call `register_deposit` with a unique `anchor_transaction_id` string before asserting on events. +3. WHEN the test inspects emitted events, THE test SHALL use `env.events().all()` and `TryFromVal` to decode the `(Event, u32)` tuple, consistent with existing event tests in the file. +4. IF the `DepositRegistered` event is not found in the event log, THEN the test SHALL fail with a descriptive `assert!` or `assert_eq!` message. From edfdfc5a6d11e304b6462f6f38aa528195938204 Mon Sep 17 00:00:00 2001 From: Marvin Nkut Date: Mon, 30 Mar 2026 11:30:10 +0100 Subject: [PATCH 2/4] feat: add design doc for issue #597 - DepositRegistered event test --- .../design.md | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 .kiro/specs/issue-597-test-deposit-registered-event/design.md diff --git a/.kiro/specs/issue-597-test-deposit-registered-event/design.md b/.kiro/specs/issue-597-test-deposit-registered-event/design.md new file mode 100644 index 0000000..c132c8b --- /dev/null +++ b/.kiro/specs/issue-597-test-deposit-registered-event/design.md @@ -0,0 +1,191 @@ +# Design Document + +## Overview + +Add `test_register_deposit_emits_event` to `tests/contract_test.rs`. The test verifies that calling `register_deposit` emits exactly one `Event::DepositRegistered(tx_id, anchor_transaction_id)` event whose fields match the return value and the input argument respectively. + +A secondary finding during design: `src/lib.rs` currently emits `Event::DepositRegistered(id.clone(), caller)` where `caller` is an `Address`, but the `Event::DepositRegistered` variant is typed `(SorobanString, SorobanString)` and the requirements specify the second field must be `anchor_transaction_id`. The emit call must be corrected to `Event::DepositRegistered(id.clone(), anchor_transaction_id.clone())` for the test to pass. + +## Architecture + +This is a pure test addition with one accompanying bug-fix in the contract emit call. No new modules, traits, or abstractions are introduced. + +``` +tests/contract_test.rs ← new test function added here +src/lib.rs (register_deposit) ← emit call corrected +``` + +## Components and Interfaces + +### Existing: `setup` helper (`tests/contract_test.rs`) + +```rust +fn setup(env: &Env) -> (Address, Address, SynapseContractClient<'_>) +``` + +Initialises the contract, creates an admin, and returns `(admin, contract_id, client)`. The new test uses this directly. + +### Existing: `emit` (`src/events/mod.rs`) + +```rust +pub fn emit(env: &Env, event: Event) +``` + +Publishes `(event, ledger_sequence)` under the `"synapse"` topic. Consumers decode with `TryFromVal::<_, (Event, u32)>`. + +### Existing: `Event::DepositRegistered` (`src/types/mod.rs`) + +```rust +DepositRegistered(SorobanString, SorobanString), +// ^tx_id ^anchor_transaction_id +``` + +### Fix required: `register_deposit` emit call (`src/lib.rs`) + +Current (incorrect): +```rust +emit(&env, Event::DepositRegistered(id.clone(), caller)); +``` + +Corrected: +```rust +emit(&env, Event::DepositRegistered(id.clone(), anchor_transaction_id.clone())); +``` + +## Data Models + +No new data models. The relevant existing type: + +```rust +// src/types/mod.rs +pub enum Event { + DepositRegistered(SorobanString, SorobanString), // (tx_id, anchor_transaction_id) + // ... +} +``` + +Events are published as `(Event, u32)` tuples where `u32` is the ledger sequence number. + +## Correctness Properties + +*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* + +### Property 1: DepositRegistered event payload correctness + +*For any* valid `register_deposit` call (with a non-empty `anchor_transaction_id`, a registered relayer, and an allowed asset), exactly one `Event::DepositRegistered` event SHALL be present in the event log, its first field SHALL equal the `SorobanString` returned by `register_deposit`, and its second field SHALL equal the `anchor_transaction_id` argument passed by the caller. + +**Validates: Requirements 1.1, 1.2, 1.3** + +## Error Handling + +| Scenario | Behaviour | +|---|---| +| `DepositRegistered` event absent from log | `assert!(found, "DepositRegistered event not found")` fails the test | +| Event payload fields do not match | `assert_eq!` on each field fails with a descriptive message | +| `TryFromVal` decode fails | `.unwrap()` panics, failing the test with a clear Rust panic message | + +## Testing Strategy + +### Unit / integration test (primary deliverable) + +Add `test_register_deposit_emits_event` to `tests/contract_test.rs`: + +```rust +#[test] +fn test_register_deposit_emits_event() { + let env = Env::default(); + let (admin, contract_id, client) = setup(&env); + let relayer = Address::generate(&env); + client.grant_relayer(&admin, &relayer); + client.add_asset(&admin, &SorobanString::from_str(&env, "USD")); + + let anchor_id = SorobanString::from_str(&env, "anchor-597"); + let tx_id = client.register_deposit( + &relayer, + &anchor_id, + &Address::generate(&env), + &100_000_000, + &SorobanString::from_str(&env, "USD"), + &None, + &None, + &None, + ); + + let all_events = env.events().all(); + let topics: soroban_sdk::Vec = (symbol_short!("synapse"),).into_val(&env); + + // Collect all DepositRegistered events emitted by this contract + let deposit_events: Vec<_> = all_events + .iter() + .filter_map(|(contract, event_topics, raw)| { + if contract != contract_id || event_topics != topics { + return None; + } + match <(Event, u32)>::try_from_val(&env, &raw) { + Ok((Event::DepositRegistered(id, anchor), _)) => Some((id, anchor)), + _ => None, + } + }) + .collect(); + + // Property 1: exactly one DepositRegistered event + assert_eq!( + deposit_events.len(), + 1, + "expected exactly one DepositRegistered event, got {}", + deposit_events.len() + ); + + let (emitted_tx_id, emitted_anchor_id) = &deposit_events[0]; + + // Property 1: first field matches return value + assert_eq!( + *emitted_tx_id, tx_id, + "DepositRegistered tx_id does not match register_deposit return value" + ); + + // Property 1: second field matches input anchor_transaction_id + assert_eq!( + *emitted_anchor_id, anchor_id, + "DepositRegistered anchor_transaction_id does not match input" + ); +} +``` + +### Property-based testing + +The property identified above (payload correctness across all valid inputs) is well-suited to property-based testing using [`proptest`](https://github.com/proptest-rs/proptest) or [`quickcheck`](https://github.com/BurntSushi/quickcheck). However, Soroban's `Env::default()` is not `Send + Sync` and does not integrate cleanly with proptest's runner. The recommended approach is to use a table-driven / parameterised test with multiple distinct `anchor_transaction_id` values to approximate coverage: + +```rust +// Feature: issue-597-test-deposit-registered-event, Property 1: DepositRegistered event payload correctness +#[test] +fn test_register_deposit_emits_event_various_anchor_ids() { + let cases = ["anchor-a", "anchor-b", "ANCHOR-999", "x"]; + for anchor_str in cases { + let env = Env::default(); + let (admin, contract_id, client) = setup(&env); + let relayer = Address::generate(&env); + client.grant_relayer(&admin, &relayer); + client.add_asset(&admin, &SorobanString::from_str(&env, "USD")); + let anchor_id = SorobanString::from_str(&env, anchor_str); + let tx_id = client.register_deposit( + &relayer, &anchor_id, &Address::generate(&env), + &100_000_000, &SorobanString::from_str(&env, "USD"), + &None, &None, &None, + ); + let all_events = env.events().all(); + let found = all_events.iter().any(|(_, _, raw)| { + matches!( + <(Event, u32)>::try_from_val(&env, &raw), + Ok((Event::DepositRegistered(ref id, ref anchor), _)) + if *id == tx_id && *anchor == anchor_id + ) + }); + assert!(found, "DepositRegistered not found for anchor_id={anchor_str}"); + } +} +``` + +Each iteration covers a distinct input, approximating the "for any valid input" quantifier. Minimum coverage: 4 distinct anchor IDs exercising different lengths and character sets. + +**Tag format:** `Feature: issue-597-test-deposit-registered-event, Property 1: DepositRegistered event payload correctness` From 120897198ef6e218cb70210ad1e05a1d2960bc52 Mon Sep 17 00:00:00 2001 From: Marvin Nkut Date: Mon, 30 Mar 2026 11:39:43 +0100 Subject: [PATCH 3/4] feat: add spec for issue #601 - test retry_dlq emits StatusUpdated event --- .../.config.kiro | 1 + .../requirements.md | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 .kiro/specs/issue-601-test-retry-dlq-emits-event/.config.kiro create mode 100644 .kiro/specs/issue-601-test-retry-dlq-emits-event/requirements.md diff --git a/.kiro/specs/issue-601-test-retry-dlq-emits-event/.config.kiro b/.kiro/specs/issue-601-test-retry-dlq-emits-event/.config.kiro new file mode 100644 index 0000000..d753e7c --- /dev/null +++ b/.kiro/specs/issue-601-test-retry-dlq-emits-event/.config.kiro @@ -0,0 +1 @@ +{"specId": "db8875b2-abc0-44d0-9b9a-cdd038962f9b", "workflowType": "requirements-first", "specType": "feature"} diff --git a/.kiro/specs/issue-601-test-retry-dlq-emits-event/requirements.md b/.kiro/specs/issue-601-test-retry-dlq-emits-event/requirements.md new file mode 100644 index 0000000..e9937cf --- /dev/null +++ b/.kiro/specs/issue-601-test-retry-dlq-emits-event/requirements.md @@ -0,0 +1,38 @@ +# Requirements Document + +## Introduction + +Add a test in `tests/contract_test.rs` that asserts `retry_dlq` emits an `Event::StatusUpdated(tx_id, TransactionStatus::Pending)` event as its last emitted event. The existing test `retry_dlq_resets_transaction_status_to_pending` verifies the stored state but does not assert the emitted event, leaving a gap in event-emission coverage for the DLQ retry path. + +## Glossary + +- **SynapseContract**: The Soroban smart contract under test, defined in `src/lib.rs`. +- **retry_dlq**: The contract method that retries a failed transaction from the dead-letter queue, restoring its status to `Pending`. +- **StatusUpdated**: The `Event` variant emitted when a transaction's status changes, carrying `(tx_id: SorobanString, new_status: TransactionStatus)`. +- **TransactionStatus**: The `#[contracttype]` enum in `src/types/mod.rs` representing transaction lifecycle states (`Pending`, `Processing`, `Completed`, `Failed`, `Cancelled`). +- **DLQ**: Dead-letter queue — the storage structure holding failed transactions awaiting retry. +- **Event**: The `#[contracttype]` enum in `src/types/mod.rs` representing all contract events. +- **TestEnv**: The Soroban `Env::default()` test environment used in integration tests. + +## Requirements + +### Requirement 1: Emit StatusUpdated(Pending) Event on retry_dlq + +**User Story:** As a contract integrator, I want `retry_dlq` to emit a `StatusUpdated` event with `TransactionStatus::Pending` as the new status, so that off-chain observers can reliably detect when a failed transaction has been re-queued for processing. + +#### Acceptance Criteria + +1. WHEN `retry_dlq` is called on a transaction in the DLQ, THE `SynapseContract` SHALL emit an `Event::StatusUpdated(tx_id, TransactionStatus::Pending)` event. +2. WHEN `retry_dlq` completes successfully, THE last event emitted by `SynapseContract` SHALL be `Event::StatusUpdated(tx_id, TransactionStatus::Pending)`. +3. THE `test_retry_dlq_emits_status_updated` test SHALL be added to `tests/contract_test.rs` and SHALL pass under `cargo test`. + +### Requirement 2: Test Isolation and Setup + +**User Story:** As a developer maintaining the test suite, I want the new test to use the existing `setup` helper and follow the conventions in `contract_test.rs`, so that the test is consistent and easy to maintain. + +#### Acceptance Criteria + +1. THE new test SHALL use the `setup` helper defined in `tests/contract_test.rs` to initialise the contract, admin, and client. +2. THE new test SHALL grant a relayer, add an asset, call `register_deposit`, call `mark_failed`, and then call `retry_dlq` before asserting on events. +3. WHEN the test inspects emitted events, THE test SHALL use `env.events().all()` and `TryFromVal` to decode the `(Event, u32)` tuple, consistent with existing event tests in the file. +4. IF the last event is not `Event::StatusUpdated(tx_id, TransactionStatus::Pending)`, THEN the test SHALL fail with a descriptive `assert_eq!` or `assert!` message. From 9fddd770d7856ac879d8ef69420d14fccf171cfd Mon Sep 17 00:00:00 2001 From: Marvin Nkut Date: Mon, 30 Mar 2026 11:46:12 +0100 Subject: [PATCH 4/4] feat: add spec for issue #608 - test register_deposit stores stellar_account --- .../.config.kiro | 1 + .../requirements.md | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 .kiro/specs/issue-608-test-register-deposit-stores-stellar-account/.config.kiro create mode 100644 .kiro/specs/issue-608-test-register-deposit-stores-stellar-account/requirements.md diff --git a/.kiro/specs/issue-608-test-register-deposit-stores-stellar-account/.config.kiro b/.kiro/specs/issue-608-test-register-deposit-stores-stellar-account/.config.kiro new file mode 100644 index 0000000..239f0fe --- /dev/null +++ b/.kiro/specs/issue-608-test-register-deposit-stores-stellar-account/.config.kiro @@ -0,0 +1 @@ +{"specId": "db8875b2-abc0-44d0-9b9a-cdd038962f9a", "workflowType": "requirements-first", "specType": "feature"} diff --git a/.kiro/specs/issue-608-test-register-deposit-stores-stellar-account/requirements.md b/.kiro/specs/issue-608-test-register-deposit-stores-stellar-account/requirements.md new file mode 100644 index 0000000..784bb7f --- /dev/null +++ b/.kiro/specs/issue-608-test-register-deposit-stores-stellar-account/requirements.md @@ -0,0 +1,26 @@ +# Requirements Document + +## Introduction + +Add a test asserting that the `stellar_account` field on the stored `Transaction` matches the address passed to `register_deposit`. The existing test suite covers `amount`, `memo`, `memo_type`, and `relayer` fields but has no dedicated assertion for `stellar_account` storage correctness. + +## Glossary + +- **Test_Suite**: The integration test file at `tests/contract_test.rs` +- **SynapseContract**: The Soroban smart contract under test +- **register_deposit**: The contract function that creates and stores a `Transaction` +- **Transaction**: The contract type stored by `register_deposit`, containing a `stellar_account` field of type `Address` +- **stellar_account**: The `Address` argument passed to `register_deposit` that identifies the depositor's Stellar account + +## Requirements + +### Requirement 1: Test stellar_account field storage + +**User Story:** As a developer, I want a test that asserts `tx.stellar_account` equals the address passed to `register_deposit`, so that regressions in stellar_account storage are caught automatically. + +#### Acceptance Criteria + +1. WHEN `register_deposit` is called with a given `stellar_account` address, THE Test_Suite SHALL retrieve the stored `Transaction` via `get_transaction` and assert that `tx.stellar_account` equals the address that was passed in. +2. THE Test_Suite SHALL use a freshly generated `Address` as the `stellar_account` argument so the assertion is non-trivial. +3. THE Test_Suite SHALL follow the naming convention `test_register_deposit_stores_stellar_account` for the new test function. +4. THE Test_Suite SHALL compile and pass without modifying any contract source files.