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
6 changes: 3 additions & 3 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ Legend: `[ ]` open, `[~]` in flight, `[x]` done.
## P0

- [ ] Third-party security audit (events + profile). Cost ~$30-80k for a Soroban-experienced firm. Scope it to the 2.0.0 / 1.2.1 tree.
- [ ] **Upgrade mainnet to events 2.0.0 + profile 1.2.1** (`docs/upgrade-runbook.md`, Section 8). Carries every threat-model fix (`docs/threat-model.md` v1.1, Section 4.1.2), restores the 17,280-ledger upgrade timelock (EoP.10: the deployed 1.7.0 / 1.2.0 builds have none), and finishes the 1.7.0 migration that never ran: events still reports a 1.6.0 marker, and events 1 and 2 (`…609`, `…610`, both Completed with zero escrow) are still in the old layout and fail to decode. Rehearsed on testnet 2026-10-02 (events `CBEODVJG…` at 2.0.0, 185 events through `migrate_events`, `migrate` stamped). Backend first: read pages of 90, refund cranks of 15, hackathon selections split at 20 per call, `forfeit_milestone` on milestone rejection, `claim_refund` for held refunds, and no calls to the removed participation entry points. Then profile propose / apply / migrate, and events pause / propose / apply / one `migrate_events` page / `migrate` / unpause, all under the deployed zero timelock. `deploy_mainnet.sh` now refuses to unpause events until `migrate` has stamped, the gate the August upgrade lacked.
- [ ] **Upgrade mainnet to events 1.8.0 + profile 1.2.1** (`docs/upgrade-runbook.md`, Section 8). Events 1.8.0 is the 2.0.0 code under a 1.x label (tag `events-v1.8.0`), so the backend keeps grants and held-refund claims off on mainnet until a 2.x upgrade. Carries every threat-model fix (`docs/threat-model.md` v1.1, Section 4.1.2), restores the 17,280-ledger upgrade timelock (EoP.10: the deployed 1.7.0 / 1.2.0 builds have none), and finishes the 1.7.0 migration that never ran: events still reports a 1.6.0 marker, and events 1 and 2 (`…609`, `…610`, both Completed with zero escrow) are still in the old layout and fail to decode. Rehearsed on testnet 2026-10-02 (events `CBEODVJG…` at 2.0.0, 185 events through `migrate_events`, `migrate` stamped). Backend first: read pages of 90, refund cranks of 15, hackathon selections split at 20 per call, `forfeit_milestone` on milestone rejection, `claim_refund` for held refunds, and no calls to the removed participation entry points. Then profile propose / apply / migrate, and events pause / propose / apply / one `migrate_events` page / `migrate` / unpause, all under the deployed zero timelock. `deploy_mainnet.sh` now refuses to unpause events until `migrate` has stamped, the gate the August upgrade lacked.

## P1

- [ ] **Events 2.1.0 (grant milestone splits) to mainnet**, as its own upgrade after 2.0.0, waiting the 17,280-ledger timelock. Before it goes: rerun Scout on the 2.1.0 code and add it to `docs/threat-model.md` Section 6 (the scan covers 2.0.0; the testnet grant run already passed on 2.1.0, `docs/audit/testnet-grant-run-2.1.0.md`). Refresh the storage replay from 2.0.0 once mainnet runs it (`docs/storage-compatibility.md`).
- [ ] **Events 2.1.0 (grant milestone splits) to mainnet**, as its own upgrade after 1.8.0, waiting the 17,280-ledger timelock. Before it goes: rerun Scout on the 2.1.0 code and add it to `docs/threat-model.md` Section 6 (the scan covers 2.0.0; the testnet grant run already passed on 2.1.0, `docs/audit/testnet-grant-run-2.1.0.md`). Refresh the storage replay from 1.8.0 once mainnet runs it (`docs/storage-compatibility.md`).
- [ ] Per-event prize claim window: `PRIZE_CLAIM_WINDOW_SECS` (90 days) is a module constant because adding a field to `EventRecord` requires a storage migration. Move it onto `EventRecord` at the next real migration window, per the per-event-config rule in `CLAUDE.md`.
- [ ] `Error` enum headroom: events uses 42 of the 50-case `contracterror` cap after 2.0.0, and retired codes are never reused. Plan a consolidation before the cap, not when the next variant fails to compile.
- [ ] Grant committee multi-sig primitive (dedicated signer set + quorum at the contract level vs the current address-level multi-sig).
- [ ] Multi-token support audit: verify the whitelist mechanism handles tokens with non-Stellar 7-decimal scales (currently assumed uniform).
- [ ] Decide whether to appoint a crowdfunding release validator on mainnet once 2.0.0 is live, and who holds the key (`docs/admin-custody-policy.md` §4.1). Until one accepts, the admin multisig co-signs every crowdfunding release.
- [ ] Decide whether to appoint a crowdfunding release validator on mainnet once 1.8.0 is live, and who holds the key (`docs/admin-custody-policy.md` §4.1). Until one accepts, the admin multisig co-signs every crowdfunding release.

## P2 (roadmap)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ boundless-contract/
|-----|----------------|
| `docs/ARCHITECTURE.md` | What lives on chain and why, roles, cross-contract calls |
| `docs/DEPLOYMENT.md` | Fresh deploy on testnet or mainnet, token registration, admin rotation |
| `docs/upgrade-runbook.md` | Upgrading live contracts, including mainnet 1.7.0 to 2.0.0 |
| `docs/upgrade-runbook.md` | Upgrading live contracts, including mainnet 1.7.0 to 1.8.0 |
| `docs/contract-ops-runbook.md` | Building, simulating, signing and submitting admin transactions |
| `docs/multisig-guide.md` | Plain-English guide for signers; provisioning the multisig |
| `docs/admin-custody-policy.md` | Custody policy: signers, thresholds, rotation, release validator |
Expand Down
2 changes: 1 addition & 1 deletion docs/admin-custody-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The admin authority on `boundless-events` gates:
- `set_admin(new_admin)`: rotate the admin authority (two-step with `accept_admin`).
- `pause()`: emergency stop on every event-lifecycle call. Admin calls keep working.
- `unpause()`: resume them.
- `propose_upgrade(wasm_hash, version)`, `apply_upgrade()`, `cancel_pending_upgrade()`: replace contract logic. Mainnet builds from events 2.0.0 and profile 1.2.1 make `apply_upgrade` wait 17,280 ledgers (about a day) after the proposal. `migrate_events(max_events)` and `migrate()` finish an upgrade (`docs/upgrade-runbook.md`).
- `propose_upgrade(wasm_hash, version)`, `apply_upgrade()`, `cancel_pending_upgrade()`: replace contract logic. Mainnet builds from events 1.8.0 and profile 1.2.1 make `apply_upgrade` wait 17,280 ledgers (about a day) after the proposal. `migrate_events(max_events)` and `migrate()` finish an upgrade (`docs/upgrade-runbook.md`).
- `register_supported_token(token)` / `deregister_supported_token(token)`: token whitelist.
- `propose_release_validator(target)`, `cancel_pending_release_validator()`, `clear_release_validator()`: appoint, withdraw a proposal for, or remove the co-signer for crowdfunding milestone releases (from events 2.0.0; see §4.1). The proposed key must accept with its own signature. While none is appointed, the admin co-signs those releases itself.

Expand Down
2 changes: 1 addition & 1 deletion docs/multisig-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ On the events contract the admin **can:**
- Change the default fee rate (`set_fee_bps`).
- Change the fee account address (`set_fee_account`). This one needs all three signatures.
- Pause the contract in an emergency (`pause`) and resume after a fix (`unpause`).
- Propose a contract upgrade (`propose_upgrade`). On current mainnet builds the upgrade cannot apply until about a day (17,280 ledgers) after the proposal, so users can see it coming. The deployed events 1.7.0 and profile 1.2.0 builds have no such wait; the 2.0.0 upgrade restores it.
- Propose a contract upgrade (`propose_upgrade`). On current mainnet builds the upgrade cannot apply until about a day (17,280 ledgers) after the proposal, so users can see it coming. The deployed events 1.7.0 and profile 1.2.0 builds have no such wait; the 1.8.0 upgrade restores it.
- Apply a proposed upgrade after that wait (`apply_upgrade`), or cancel it (`cancel_pending_upgrade`).
- Run the post-upgrade migration (`migrate_events`, then `migrate`).
- Add or remove tokens from the whitelist (`register_supported_token`, `deregister_supported_token`).
Expand Down
2 changes: 1 addition & 1 deletion docs/storage-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ To check that the fixture still reproduces from the history archive (Stellar CLI

## Refreshing for the next upgrade

Once mainnet runs 2.0.0, the replay should start from 2.0.0 instead:
Once mainnet runs 1.8.0 (tag `events-v1.8.0`), the replay should start from 1.8.0 instead:

1. Fetch the deployed wasm for both contracts by hash into the fixtures
directory and replace the two `mainnet-*.wasm` files.
Expand Down
10 changes: 5 additions & 5 deletions docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
**Date:** 8 October 2026
**Prepared for:** Stellar Development Foundation, Soroban Security Audit Bank
**Prepared by:** Boundless Engineering
**Contracts under review:** `boundless-events` 2.0.0 and `boundless-profile` 1.2.1, plus the grant milestone split that `boundless-events` 2.1.0 adds (Tamp.20), which reaches mainnet in a later upgrade. Mainnet runs 1.7.0 and 1.2.0 until the upgrade in Section 4.4.
**Contracts under review:** `boundless-events` 2.0.0 and `boundless-profile` 1.2.1, plus the grant milestone split that `boundless-events` 2.1.0 adds (Tamp.20), which reaches mainnet in a later upgrade. Mainnet runs 1.7.0 and 1.2.0 until the upgrade in Section 4.4, which installs the 2.0.0 code as events 1.8.0.
**Repository:** github.com/boundlessfi/boundless-contract
**Changes in 1.3:** events 2.1.0 lets a grant pay its milestones fixed, unequal shares of each award (`ReleaseKind::Split`), adding Tamp.20. The testnet grant run was repeated on 2.1.0 with a split grant; the static analysis covers 2.0.0 and is due again for 2.1.0.
**Changes in 1.2:** reworded for clarity, retired threats moved to their own table, flow steps renumbered after the participation removal, statuses limited to four defined values, and Section 4 reorganized.
Expand Down Expand Up @@ -163,7 +163,7 @@ The model relies on these assumptions. A threat that needs one of them to fail i
| Parameter | Value |
|---|---|
| Maximum fee | 1,000 bps (10%) |
| Upgrade timelock | 17,280 ledgers (about one day) on mainnet builds from 2.0.0; 0 on testnet builds. The deployed 1.7.0 build has 0 (Section 4.4). |
| Upgrade timelock | 17,280 ledgers (about one day) on mainnet builds from 2.0.0, including its 1.8.0 mainnet label; 0 on testnet builds. The deployed 1.7.0 build has 0 (Section 4.4). |
| Upgrade proposal expiry | 518,400 ledgers (about 30 days) |
| Admin rotation and release validator acceptance windows | 120,960 ledgers (about 7 days) each |
| Manager proposal expiry | 17,280 ledgers (about one day) |
Expand Down Expand Up @@ -388,7 +388,7 @@ Of the 73 active threats, 10 are P1, 23 are P2, 23 are P3 and 17 are P4.
| **EoP.7** | The owner must sign the same transaction as the co-signer, the money goes only to the owner, and the amount is calculated from the pool. The backend prepares the transaction only for the signed-in owner. | Mitigated |
| **EoP.8** | The manager can select, cancel and rotate. It cannot change the owner (no function exists), receive refunds, pay milestones, or change floors. It can award any address, including its own: judging is off chain by design, and the owner chooses whom to delegate to. `ManagerChanged` is on chain before any selection. | Accepted |
| **EoP.9** | The recipient must hold an award in the grant roster, and the amount comes from that award (grants) or from `remaining_escrow / milestones_left` (crowdfunding), never from the caller. Each milestone is settled once and must be in range. | Mitigated |
| **EoP.10** | Mainnet builds have a 17,280-ledger upgrade timelock from events 2.0.0 and profile 1.2.1; testnet builds keep zero, and `deploy_mainnet.sh` always builds without the testnet setting. The deployed 1.7.0 and 1.2.0 builds have no timelock until the upgrade in Section 4.4, which itself runs without one. Tests: `apply_upgrade_waits_out_the_timelock`, `apply_upgrade_waits_out_the_timelock_profile`. | Mitigated |
| **EoP.10** | Mainnet builds have a 17,280-ledger upgrade timelock from events 2.0.0 (1.8.0 on mainnet) and profile 1.2.1; testnet builds keep zero, and `deploy_mainnet.sh` always builds without the testnet setting. The deployed 1.7.0 and 1.2.0 builds have no timelock until the upgrade in Section 4.4, which itself runs without one. Tests: `apply_upgrade_waits_out_the_timelock`, `apply_upgrade_waits_out_the_timelock_profile`. | Mitigated |
| **EoP.11** | `admin_slash_reputation` requires the admin and a non-empty reason, emits `AdminReputationSlashed`, and is the only way to lower a score. | Mitigated |
| **EoP.12** | Both migration functions require the admin, the cursor only moves forward, and `migrate` runs once per version. | Mitigated |
| **EoP.13** | After the first binding, a change is `propose_events_contract`, a 17,280-ledger wait, then `accept_events_contract` within 7 days, all admin-only and all emitting events. The wait only means something once the profile's upgrade timelock is deployed (EoP.10). | Mitigated |
Expand Down Expand Up @@ -461,10 +461,10 @@ These threats concerned on-chain applications and submissions, which 2.0.0 remov

| Item | What is needed |
|---|---|
| Mainnet upgrade | Mainnet runs events 1.7.0 and profile 1.2.0. Every fix in Section 4.2 takes effect only after the admin multisig upgrades both, to events 2.0.0 and profile 1.2.1, following `docs/upgrade-runbook.md` Section 8. The deployed builds have no timelock, so this upgrade applies immediately; every later one waits 17,280 ledgers (EoP.10). |
| Mainnet upgrade | Mainnet runs events 1.7.0 and profile 1.2.0. Every fix in Section 4.2 takes effect only after the admin multisig upgrades both, to events 1.8.0 (the 2.0.0 code, tag `events-v1.8.0`) and profile 1.2.1, following `docs/upgrade-runbook.md` Section 8. The deployed builds have no timelock, so this upgrade applies immediately; every later one waits 17,280 ledgers (EoP.10). |
| Old-layout records (Tamp.10) | Two mainnet event records cannot be read until converted. Both are completed events with no escrow, so no money is affected. Mainnet holds eight events, so one `migrate_events` call during the upgrade converts the two and leaves the others unchanged, and `migrate` then stamps the version. |
| Backend changes that must ship first | Page winner and contributor reads at 90 or fewer and run refunds in batches of 15 or fewer (DoS.17); pages of 100 and batches of 25 already fail on the deployed build for large events. Split hackathon selections above 20 winners (DoS.17). Call `forfeit_milestone` when a grant milestone is rejected, or the grant can never be cancelled (EoP.14). Support `claim_refund` for refunds set aside (DoS.14). Stop calling the participation functions that 2.0.0 removed. |
| Events 2.1.0 | Grant milestone splits (Tamp.20) run on testnet from 8 October 2026 and reach mainnet in a separate upgrade after 2.0.0, which waits the 17,280-ledger timelock (`docs/upgrade-runbook.md` Section 9). |
| Events 2.1.0 | Grant milestone splits (Tamp.20) run on testnet from 8 October 2026 and reach mainnet in a separate upgrade after 1.8.0, which waits the 17,280-ledger timelock (`docs/upgrade-runbook.md` Section 9). |

### 4.5 Accepted risks

Expand Down
Loading
Loading