Skip to content
Merged
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
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,15 @@ jobs:
run: |
chmod +x scripts/check-wasm-size.sh
./scripts/check-wasm-size.sh

event-shape:
name: Event shape vs schema
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Check revenue_pool event shape matches EVENT_SCHEMA.md
shell: bash
run: |
chmod +x scripts/check-event-shape.sh
./scripts/check-event-shape.sh
130 changes: 129 additions & 1 deletion EVENT_SCHEMA.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Event Schema
# Event Schema

Events emitted by all Callora contracts for indexers, frontends, and auditors.
All topic/data types refer to Soroban/Stellar XDR values.
Expand Down Expand Up @@ -675,6 +675,129 @@ three payments, three `batch_distribute` events are emitted in order.

---



---

### `pause_set`

Emitted by both `pause()` (data = `true`) and `unpause()` (data = `false`) to signal
a change in the pool's pause state. Only the admin may trigger either function.

| Index | Location | Type | Description |
|---------|----------|---------|--------------------------------------------------|
| topic 0 | topics | Symbol | `"pause_set"` |
| topic 1 | topics | Address | `caller` -- the admin who called pause/unpause |
| data | data | bool | `true` = pool is now paused; `false` = unpaused |

```json
{ "topics": ["pause_set", "GADMIN..."], "data": true }
```

> While paused, `distribute` and `batch_distribute` are blocked.
> Admin rotation (`set_admin`, `claim_admin`) remains available.

---

### `admin_cancelled`

Emitted when the current admin cancels a pending two-step admin transfer via
`cancel_admin_transfer()`. Both the current and the pending admin are recorded as topics
so indexers can link the cancellation to the in-flight handover without a data decode.

| Index | Location | Type | Description |
|---------|-----------|---------|----------------------------------------------------|
| topic 0 | topics | Symbol | `"admin_cancelled"` |
| topic 1 | topics | Address | `current_admin` -- admin who issued the cancel |
| topic 2 | topics | Address | `pending_admin` -- nominee whose claim is revoked |
| data | data | () | empty |

```json
{
"topics": ["admin_cancelled", "GCURRENT_ADMIN...", "GPENDING_ADMIN..."],
"data": null
}
```

> After this event `get_pending_admin()` returns `None`. The current admin remains
> unchanged and may initiate a new transfer at any time.

---

### `upgraded`

Emitted when the admin upgrades the contract WASM via `upgrade()`. The new WASM hash
is persisted to instance storage and is queryable via `get_version()`.

| Index | Location | Type | Description |
|---------|----------|------------|---------------------------------------------------|
| topic 0 | topics | Symbol | `"upgraded"` |
| topic 1 | topics | Address | `caller` -- admin who executed the upgrade |
| data | data | BytesN<32> | `new_wasm_hash` -- hash of the deployed WASM blob |

```json
{
"topics": ["upgraded", "GADMIN..."],
"data": "a1b2c3d4e5f6..."
}
```

> `get_version()` returns this hash immediately after the transaction. Only one WASM
> version is stored; calling `upgrade()` again overwrites the previous value.
---

### `yield_deposited`

Emitted when the treasury deposits accumulated protocol yield into the revenue pool
via `deposit_yield()`. The cumulative tracker is updated atomically with the transfer.

| Index | Location | Type | Description |
|---------|----------|---------|--------------------------------------------------------|
| topic 0 | topics | Symbol | `"yield_deposited"` |
| topic 1 | topics | Address | `treasury` -- current admin who called `deposit_yield` |
| data[0] | data | i128 | `amount` -- USDC deposited in this call (stroops) |
| data[1] | data | Symbol | `source` -- short label, e.g. `"fees"` or `"yield"` |
| data[2] | data | i128 | `cumulative_yield_deposited` -- running total after deposit |

```json
{
"topics": ["yield_deposited", "GTREASURY..."],
"data": [5000000, "fees", 42000000]
}
```

> `cumulative_yield_deposited` equals `get_cumulative_yield_deposited()` immediately
> after the emitting transaction. It never decreases and panics on `i128` overflow.

---

### `admin_broadcast`

Emitted when the admin publishes an emergency message via `broadcast()`.
No tokens are moved; this is an out-of-band signaling channel for indexers and frontends.

| Index | Location | Type | Description |
|---------|----------|------------------|------------------------------------------------|
| topic 0 | topics | Symbol | `"admin_broadcast"` |
| topic 1 | topics | Address | `caller` -- must be current admin |
| data | data | `AdminBroadcast` | struct with `severity` and `message` fields |

`AdminBroadcast` struct fields:

| Field | Type | Description |
|------------|----------|--------------------------------------------------|
| `severity` | Severity | One of `Info`, `Warn`, or `Crit` |
| `message` | String | Broadcast text; max 256 characters, never empty |

```json
{
"topics": ["admin_broadcast", "GADMIN..."],
"data": { "severity": "Crit", "message": "Emergency: pausing distribution pending audit." }
}
```

> Indexers SHOULD alert on `severity = Crit`. The `message` field is capped at
> 256 characters; longer strings are rejected before the event is emitted.
## Contract: `callora-settlement` (v0.1.0)

Source: [`contracts/settlement/src/lib.rs`](contracts/settlement/src/lib.rs).
Expand Down Expand Up @@ -928,6 +1051,11 @@ operational edge cases (off-chain payment reconciliation, dispute resolution).
| `receive_payment` | revenue-pool | `receive_payment()` |
| `distribute` | revenue-pool | `distribute()` |
| `batch_distribute` | revenue-pool | each payment in `batch_distribute()` |
| `pause_set` | revenue-pool | `pause()` / `unpause()` |
| `admin_cancelled` | revenue-pool | `cancel_admin_transfer()` |
| `upgraded` | revenue-pool | `upgrade()` |
| `yield_deposited` | revenue-pool | `deposit_yield()` |
| `admin_broadcast` | revenue-pool | `broadcast()` |
| `payment_received` | settlement | `receive_payment()` |
| `balance_credited` | settlement | `receive_payment()` with `to_pool=false` |
| `vault_changed` | settlement | `set_vault()` |
Expand Down
68 changes: 68 additions & 0 deletions scripts/check-event-shape.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# check-event-shape.sh
# Verifies that every env.events().publish() call site in contracts/revenue_pool/src/lib.rs
# has a corresponding entry in EVENT_SCHEMA.md.
#
# Exit 0 = all events are documented. Exit 1 = undocumented event found.
set -euo pipefail

SCHEMA="EVENT_SCHEMA.md"
LIB="contracts/revenue_pool/src/lib.rs"

if [[ ! -f "$SCHEMA" ]]; then
echo "ERROR: $SCHEMA not found (run from repo root)" >&2
exit 1
fi
if [[ ! -f "$LIB" ]]; then
echo "ERROR: $LIB not found (run from repo root)" >&2
exit 1
fi

# Extract event names from publish call sites:
# events::event_FOO(&env) → FOO
# Matches lines like: (events::event_admin_changed(&env), ...)
mapfile -t CODE_EVENTS < <(
grep -oP 'events::event_\K[a-z_]+(?=\(&env\))' "$LIB" | sort -u
)

# Extract event names documented under the revenue-pool section of EVENT_SCHEMA.md.
# We look for ### `foo` headings between the revenue-pool header and the next ## header.
IN_POOL=0
mapfile -t SCHEMA_EVENTS < <(
while IFS= read -r line; do
if [[ "$line" =~ ^##[[:space:]].*callora-revenue-pool ]]; then
IN_POOL=1; continue
fi
if [[ $IN_POOL -eq 1 && "$line" =~ ^##[[:space:]] ]]; then
IN_POOL=0; continue
fi
if [[ $IN_POOL -eq 1 && "$line" =~ ^###[[:space:]]\`([a-z_]+)\` ]]; then
echo "${BASH_REMATCH[1]}"
fi
done < "$SCHEMA" | sort -u
)

echo "=== Revenue Pool Event Shape Check ==="
echo "Events in lib.rs : ${CODE_EVENTS[*]}"
echo "Events in schema : ${SCHEMA_EVENTS[*]}"
echo ""

MISSING=()
for ev in "${CODE_EVENTS[@]}"; do
if ! printf '%s\n' "${SCHEMA_EVENTS[@]}" | grep -qx "$ev"; then
MISSING+=("$ev")
fi
done

if [[ ${#MISSING[@]} -gt 0 ]]; then
echo "FAIL: The following revenue_pool events are emitted in lib.rs but not documented in EVENT_SCHEMA.md:"
for m in "${MISSING[@]}"; do
echo " - $m"
done
echo ""
echo "Add a '### \`$m\`' section under the callora-revenue-pool heading in EVENT_SCHEMA.md."
exit 1
fi

echo "OK: all revenue_pool event publish sites are documented in EVENT_SCHEMA.md."
exit 0
Loading