Skip to content

feat(contracts): emit named events from every state-changing entrypoint - #71

Merged
codemagician1949 merged 1 commit into
IntensifierLab:mainfrom
oluwagbemigaphilip:feat/state-change-events
Aug 25, 2026
Merged

codemagician1949 merged 1 commit into
IntensifierLab:mainfrom
oluwagbemigaphilip:feat/state-change-events

Conversation

@oluwagbemigaphilip

Copy link
Copy Markdown
Contributor

Off-chain indexers had no reliable signal for several state transitions:
one entrypoint emitted nothing at all, and the events that did exist carried
too little to reconstruct what happened.

process_repayment emitted nothing

repayment-waterfall::process_repayment moved reserve capital via a
cross-contract call and emitted no event, so a repayment was invisible to
any index. It now emits ("repaid",) with (amount, timestamp).

The publish is placed after the apply_reserve_delta call, not before: if
that call traps the whole invocation reverts, and an event should never be
claimed for a transfer that did not happen.

verify_invoice carried no verifier

It emitted ("inv_ver",) with just the invoice id. An audit trail is only
ever asked two questions — who, and when — and neither was recoverable. The
id now moves to a topic, so an indexer can subscribe per invoice instead of
decoding every payload, and the payload carries (verifier, timestamp).

assign_invoice lost the previous owner

It emitted the pool as a topic and the id as data. The previous owner was
nowhere, and after the assignment it is gone from storage too — the write
overwrites it — so the event was the only place it could have survived. Now
captured before the write and emitted as (previous_owner, pool, timestamp).

join_pool could not be reconciled

It emitted only shares. Shares are minted at the prevailing NAV, so
without the amount paid and the NAV used there is no way to check the mint
was priced correctly, or to rebuild a lender's cost basis from the event
stream. Now (amount, shares, nav, total_shares), with the running total so
pool size is trackable without a state read.

Not included here: create_invoice. That entrypoint and its event are the
subject of #13, and are left entirely to that PR so the two do not overlap.

Wasm size warning

pool_manager.wasm was already the closest to the 64KB CI limit at 64,262
bytes. The richer join_pool event takes it to 64,848 — still passing, but
headroom is now 688 bytes. The next addition to that contract is likely to
break the size gate, and it is worth deciding deliberately whether to raise
the limit or split the contract before that happens.

Verification

  • cargo test --workspace — all tests passing, 6 of them new
  • release wasm builds clean; every contract under the 64KB limit
    (governance 30,005 · invoice_registry 47,755 · pool_manager 64,848 ·
    repayment_waterfall 44,714)

The new tests drive each entrypoint to its success path and compare the
emitted event whole — topic and payload together — rather than picking out
fields, plus a negative case asserting a rejected repayment emits nothing,
since a phantom credit in an index is worse than a missing one.

Closes #4

Off-chain indexers had no reliable signal for several state transitions:
one entrypoint emitted nothing at all, and the events that did exist carried
too little to reconstruct what happened.

process_repayment emitted nothing
---------------------------------
`repayment-waterfall::process_repayment` moved reserve capital via a
cross-contract call and emitted no event, so a repayment was invisible to
any index. It now emits `("repaid",)` with `(amount, timestamp)`.

The publish is placed *after* the `apply_reserve_delta` call, not before: if
that call traps the whole invocation reverts, and an event should never be
claimed for a transfer that did not happen.

verify_invoice carried no verifier
----------------------------------
It emitted `("inv_ver",)` with just the invoice id. An audit trail is only
ever asked two questions — who, and when — and neither was recoverable. The
id now moves to a topic, so an indexer can subscribe per invoice instead of
decoding every payload, and the payload carries `(verifier, timestamp)`.

assign_invoice lost the previous owner
--------------------------------------
It emitted the pool as a topic and the id as data. The previous owner was
nowhere, and after the assignment it is gone from storage too — the write
overwrites it — so the event was the only place it could have survived. Now
captured before the write and emitted as `(previous_owner, pool, timestamp)`.

join_pool could not be reconciled
---------------------------------
It emitted only `shares`. Shares are minted at the prevailing NAV, so
without the amount paid and the NAV used there is no way to check the mint
was priced correctly, or to rebuild a lender's cost basis from the event
stream. Now `(amount, shares, nav, total_shares)`, with the running total so
pool size is trackable without a state read.

Not included here: `create_invoice`. That entrypoint and its event are the
subject of IntensifierLab#13, and are left entirely to that PR so the two do not overlap.

Wasm size warning
-----------------
`pool_manager.wasm` was already the closest to the 64KB CI limit at 64,262
bytes. The richer `join_pool` event takes it to 64,848 — still passing, but
headroom is now 688 bytes. The next addition to that contract is likely to
break the size gate, and it is worth deciding deliberately whether to raise
the limit or split the contract before that happens.

Verification
------------
- `cargo test --workspace` — all tests passing, 6 of them new
- release wasm builds clean; every contract under the 64KB limit
  (governance 30,005 · invoice_registry 47,755 · pool_manager 64,848 ·
  repayment_waterfall 44,714)

The new tests drive each entrypoint to its success path and compare the
emitted event whole — topic and payload together — rather than picking out
fields, plus a negative case asserting a rejected repayment emits nothing,
since a phantom credit in an index is worse than a missing one.

Closes IntensifierLab#4
@codemagician1949

Copy link
Copy Markdown
Contributor

Nice job @oluwagbemigaphilip

@codemagician1949
codemagician1949 merged commit 7109f98 into IntensifierLab:main Aug 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: add Soroban event emission for all state changes

2 participants