Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"specId": "db8875b2-abc0-44d0-9b9a-cdd038962f9a", "workflowType": "requirements-first", "specType": "feature"}
191 changes: 191 additions & 0 deletions .kiro/specs/issue-597-test-deposit-registered-event/design.md
Original file line number Diff line number Diff line change
@@ -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<Val> = (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`
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"specId": "db8875b2-abc0-44d0-9b9a-cdd038962f9b", "workflowType": "requirements-first", "specType": "feature"}
38 changes: 38 additions & 0 deletions .kiro/specs/issue-601-test-retry-dlq-emits-event/requirements.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"specId": "db8875b2-abc0-44d0-9b9a-cdd038962f9a", "workflowType": "requirements-first", "specType": "feature"}
Original file line number Diff line number Diff line change
@@ -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.