Docs/UX: L-02 ReferralRegistry registration ordering (first included tx wins; mitigated by 1 CL8Y burn) #121

Closed
opened 2026-05-04 04:18:17 +00:00 by PlasticDigits · 4 comments
PlasticDigits commented 2026-05-04 04:18:17 +00:00 (Migrated from gitlab.com)

Source

  • Audit: audits/audit_smartcontract_1777813071.md — L-02: Referral code registration is front-runnable
  • Severity (audit): Low — code squatting / griefing; does not steal protocol funds or existing registrations.

Summary

ReferralRegistry.registerCode(string) emits the plaintext code in calldata. Any observer who sees a pending registration transaction can submit the same normalized code with a higher gas price (or otherwise get earlier inclusion in the same block). The first transaction that successfully executes wins: subsequent attempts revert with ReferralRegistry: code taken. The intended registrant loses the race but does not pay the burn on the failed tx; the successful squatter pays registrationBurnAmount CL8Y (sent to the dead burn address).

Product decision for this issue: Treat ordering as transparent and mempool-fair: the first included successful registration wins. Do not implement commit-reveal or offchain reservations in this work item. Mitigation already in protocol: each successful registration requires a fixed CL8Y burn (see DeployDev.s.sol — 1e18 wei = 1 CL8Y at 18 decimals; tests use the same pattern in TimeCurveReferral.t.sol), which raises the cost of purely griefing squatting and makes casual front-running economically explicit.

Technical detail

Item Detail
Contract contracts/src/ReferralRegistry.sol — registerCode, codeOwner, ownerCode
Uniqueness One bytes32 codeHash → one owner; one owner → one code (IReferralRegistry)
Burn cl8yToken.safeTransferFrom(msg.sender, BURN_ADDRESS, registrationBurnAmount) after uniqueness checks
Calldata leak Code is visible before execution; MEV / mempool ordering can prioritize another registerCode with identical normalized string
  1. Docs (primary): State explicitly that code ownership is determined by the first successful onchain registration, that pending registrations can be front-run on a public mempool, and that the registration burn is the economic barrier to claiming a code (not a guarantee of “fair queue order” for calldata-visible txs).
  2. Frontend copy: On /referrals / register panel (ReferralRegisterSection.tsx), add short, non-alarmist copy: e.g. desirable codes may be claimed by others who see the same pending tx; users accept onchain ordering and the published burn cost.
  3. Contract NatSpec (optional but low-cost): In ReferralRegistry.sol, extend @notice on registerCode to reference first successful inclusion and calldata visibility (keeps expectations next to the implementation; no bytecode behavior change if only comments).

Out of scope here (future issues if ever required): commit-reveal registration, signed reservations from an admin, private mempools / Flashbots-only registration flows.

Files to update (checklist)

Documentation (required)

File Change
docs/product/referrals.md New subsection under Anti-abuse or Referral codes: mempool visibility, first successful tx wins, link to audit L-02; tie to existing 1 CL8Y burn bullet
docs/testing/invariants-and-business-logic.md Short invariant row or cross-link under referrals (#64 area): referral registration ordering is not fifo-on-intent; chain order + burn

Frontend (required)

File Change
frontend/src/pages/ReferralsPage.tsx Adjust hero / explanatory copy if needed so it matches docs
frontend/src/pages/referrals/ReferralRegisterSection.tsx Register panel: visible note before submit (burn amount already read from chain; add ordering / front-run expectation)

Contracts (optional comment-only)

File Change
contracts/src/ReferralRegistry.sol NatSpec on registerCode — first-included wins; calldata visible; burn mitigates spam

Audit trail (optional)

File Change
audits/audit_smartcontract_1777813071.md Optional footnote under L-02: “Addressed by documentation / UX” + link to this GitLab issue after merge
File Change
docs/testing/manual-qa-checklists.md One bullet under referrals (#64): confirm new copy renders and matches docs/product/referrals.md

No change expected (unless copy references added)

Regression tests

Behavior is unchanged — this is primarily a documentation and disclosure item. Recommended verification:

  1. Existing suites (must stay green): cd contracts && FOUNDRY_PROFILE=ci forge test (referral tests in TimeCurveReferral.t.sol, registry deploy paths).
  2. Frontend: npm test / Vitest for any touched strings-only components (if tests assert copy, update snapshots).
  3. E2E: frontend/e2e/anvil-referrals.spec.ts — only extend if adding a stable data-testid for the disclosure line (optional).
  4. No new Forge test required for mempool front-running (not reproducible in deterministic unit tests without a forked mempool harness); document that in test strategy if reviewers ask.

Optional future test (separate issue): If a commit-reveal design is adopted, add dedicated Foundry tests for the two-phase flow.

Acceptance criteria

  1. docs/product/referrals.md explicitly states: first successful onchain registration owns the code; public mempool can reorder competing registerCode txs; squatting is limited by registrationBurnAmount CL8Y burn to the winner only on success.
  2. Register UX surfaces the same expectation before the user submits (concise, accurate).
  3. No change to registerCode logic unless product later requests commit-reveal (out of scope).
  4. Cross-links: testing invariants doc (or equivalent) references this behavior so future agents don’t assume “pending tx = reserved code”.
  5. Review: wording reviewed for not promising offchain reservation or fifo fairness.

Verification checklist (multi-step)

A. Doc accuracy

  • Open docs/product/referrals.md and confirm the new subsection matches ReferralRegistry semantics (code taken, already registered, burn timing).
  • Confirm burn amount narrative stays aligned with 1e18 in DeployDev.s.sol and product line “1 CL8Y” in existing doc.
  • Grep repo for contradictory phrases (“reserved in mempool”, “private queue”, etc.) and fix if any.

B. Frontend

  • Run app against local Anvil stack; open /referrals with registry configured.
  • Verify new copy appears in register flow and does not overflow layout on narrow viewports.
  • Confirm registrationBurnAmount() display still matches chain (no regression).

C. CI / tests

  • forge test (CI profile) passes.
  • Frontend unit tests pass if snapshots/copy tests exist.

D. Sign-off

  • Link this issue in the MR description.
  • Optionally add one line under L-02 in the audit file pointing to this issue after merge.

Non-goals

  • Eliminating mempool visibility of referral codes without a protocol upgrade.
  • Legal/compliance language (keep technical / product-accurate only unless legal requests).

Keywords: ReferralRegistry, registerCode, front-running, mempool, L-02, CL8Y burn, documentation, UX disclosure

## Source - **Audit:** [`audits/audit_smartcontract_1777813071.md`](audits/audit_smartcontract_1777813071.md) — **L-02: Referral code registration is front-runnable** - **Severity (audit):** Low — code squatting / griefing; does **not** steal protocol funds or existing registrations. ## Summary `ReferralRegistry.registerCode(string)` emits the **plaintext code in calldata**. Any observer who sees a pending registration transaction can submit the **same normalized code** with a **higher gas price** (or otherwise get **earlier inclusion** in the same block). The **first transaction that successfully executes** wins: subsequent attempts **`revert`** with `ReferralRegistry: code taken`. The intended registrant loses the race but **does not** pay the burn on the failed tx; the successful squatter pays **`registrationBurnAmount`** CL8Y (sent to the dead burn address). **Product decision for this issue:** Treat ordering as **transparent and mempool-fair**: **the first included successful registration wins**. Do **not** implement commit-reveal or offchain reservations in this work item. **Mitigation already in protocol:** each successful registration requires a **fixed CL8Y burn** (see [`DeployDev.s.sol`](contracts/script/DeployDev.s.sol) — `1e18` wei = **1 CL8Y** at 18 decimals; tests use the same pattern in [`TimeCurveReferral.t.sol`](contracts/test/TimeCurveReferral.t.sol)), which raises the cost of purely griefing squatting and makes casual front-running economically explicit. ## Technical detail | Item | Detail | |------|--------| | **Contract** | [`contracts/src/ReferralRegistry.sol`](contracts/src/ReferralRegistry.sol) — `registerCode`, `codeOwner`, `ownerCode` | | **Uniqueness** | One `bytes32 codeHash` → one owner; one owner → one code ([`IReferralRegistry`](contracts/src/interfaces/IReferralRegistry.sol)) | | **Burn** | `cl8yToken.safeTransferFrom(msg.sender, BURN_ADDRESS, registrationBurnAmount)` **after** uniqueness checks | | **Calldata leak** | Code is visible before execution; MEV / mempool ordering can prioritize another `registerCode` with identical normalized string | ## Recommended fix (documentation + UX) 1. **Docs (primary):** State explicitly that **code ownership is determined by the first successful onchain registration**, that **pending registrations can be front-run on a public mempool**, and that the **registration burn** is the **economic barrier** to claiming a code (not a guarantee of “fair queue order” for calldata-visible txs). 2. **Frontend copy:** On [`/referrals`](frontend/src/pages/ReferralsPage.tsx) / register panel ([`ReferralRegisterSection.tsx`](frontend/src/pages/referrals/ReferralRegisterSection.tsx)), add **short, non-alarmist** copy: e.g. desirable codes may be claimed by others who see the same pending tx; users accept **onchain ordering** and the **published burn** cost. 3. **Contract NatSpec (optional but low-cost):** In [`ReferralRegistry.sol`](contracts/src/ReferralRegistry.sol), extend `@notice` on `registerCode` to reference **first successful inclusion** and **calldata visibility** (keeps expectations next to the implementation; **no bytecode behavior change** if only comments). **Out of scope here (future issues if ever required):** commit-reveal registration, signed reservations from an admin, private mempools / Flashbots-only registration flows. ## Files to update (checklist) ### Documentation (required) | File | Change | |------|--------| | [`docs/product/referrals.md`](docs/product/referrals.md) | New subsection under **Anti-abuse** or **Referral codes**: mempool visibility, **first successful tx wins**, link to audit L-02; tie to existing **1 CL8Y** burn bullet | | [`docs/testing/invariants-and-business-logic.md`](docs/testing/invariants-and-business-logic.md) | Short invariant row or cross-link under referrals (**#64** area): referral **registration ordering** is **not** fifo-on-intent; chain order + burn | ### Frontend (required) | File | Change | |------|--------| | [`frontend/src/pages/ReferralsPage.tsx`](frontend/src/pages/ReferralsPage.tsx) | Adjust hero / explanatory copy if needed so it matches docs | | [`frontend/src/pages/referrals/ReferralRegisterSection.tsx`](frontend/src/pages/referrals/ReferralRegisterSection.tsx) | Register panel: visible note before submit (burn amount already read from chain; add **ordering / front-run** expectation) | ### Contracts (optional comment-only) | File | Change | |------|--------| | [`contracts/src/ReferralRegistry.sol`](contracts/src/ReferralRegistry.sol) | NatSpec on `registerCode` — first-included wins; calldata visible; burn mitigates spam | ### Audit trail (optional) | File | Change | |------|--------| | [`audits/audit_smartcontract_1777813071.md`](audits/audit_smartcontract_1777813071.md) | Optional footnote under L-02: “Addressed by documentation / UX” + link to this GitLab issue after merge | ### Manual QA (recommended) | File | Change | |------|--------| | [`docs/testing/manual-qa-checklists.md`](docs/testing/manual-qa-checklists.md) | One bullet under referrals (**#64**): confirm new copy renders and matches `docs/product/referrals.md` | ### No change expected (unless copy references added) - [`contracts/src/TimeCurve.sol`](contracts/src/TimeCurve.sol) — referral **apply** path unchanged - Indexer — no new events - [`skills/README.md`](skills/README.md) / agent skills — only if playbooks mention “guaranteed” code reservation ## Regression tests **Behavior is unchanged** — this is primarily a **documentation and disclosure** item. Recommended verification: 1. **Existing suites (must stay green):** `cd contracts && FOUNDRY_PROFILE=ci forge test` (referral tests in [`TimeCurveReferral.t.sol`](contracts/test/TimeCurveReferral.t.sol), registry deploy paths). 2. **Frontend:** `npm test` / Vitest for any touched strings-only components (if tests assert copy, update snapshots). 3. **E2E:** [`frontend/e2e/anvil-referrals.spec.ts`](frontend/e2e/anvil-referrals.spec.ts) — only extend if adding a **stable `data-testid`** for the disclosure line (optional). 4. **No new Forge test required** for mempool front-running (not reproducible in deterministic unit tests without a forked mempool harness); **document** that in test strategy if reviewers ask. **Optional future test (separate issue):** If a commit-reveal design is adopted, add dedicated Foundry tests for the two-phase flow. ## Acceptance criteria 1. **`docs/product/referrals.md`** explicitly states: **first successful onchain registration** owns the code; **public mempool** can reorder competing `registerCode` txs; **squatting** is limited by **`registrationBurnAmount`** CL8Y burn to the **winner** only on success. 2. **Register UX** surfaces the same expectation **before** the user submits (concise, accurate). 3. **No change** to `registerCode` **logic** unless product later requests commit-reveal (out of scope). 4. **Cross-links:** testing invariants doc (or equivalent) references this behavior so future agents don’t assume “pending tx = reserved code”. 5. **Review:** wording reviewed for **not** promising offchain reservation or fifo fairness. ## Verification checklist (multi-step) ### A. Doc accuracy - [ ] Open `docs/product/referrals.md` and confirm the new subsection matches **`ReferralRegistry`** semantics (`code taken`, `already registered`, burn timing). - [ ] Confirm burn amount narrative stays aligned with **`1e18`** in [`DeployDev.s.sol`](contracts/script/DeployDev.s.sol) and product line “**1 CL8Y**” in existing doc. - [ ] Grep repo for contradictory phrases (“reserved in mempool”, “private queue”, etc.) and fix if any. ### B. Frontend - [ ] Run app against local Anvil stack; open **`/referrals`** with registry configured. - [ ] Verify new copy appears in **register** flow and does not overflow layout on narrow viewports. - [ ] Confirm **`registrationBurnAmount()`** display still matches chain (no regression). ### C. CI / tests - [ ] `forge test` (CI profile) passes. - [ ] Frontend unit tests pass if snapshots/copy tests exist. ### D. Sign-off - [ ] Link this issue in the MR description. - [ ] Optionally add one line under L-02 in the audit file pointing to this issue after merge. ## Non-goals - Eliminating mempool visibility of referral codes without a protocol upgrade. - Legal/compliance language (keep technical / product-accurate only unless legal requests). --- **Keywords:** `ReferralRegistry`, `registerCode`, front-running, mempool, L-02, CL8Y burn, documentation, UX disclosure
PlasticDigits commented 2026-05-04 05:16:45 +00:00 (Migrated from gitlab.com)

mentioned in commit edce84ca3c

mentioned in commit edce84ca3cf4344fdc91a4c0c546e671056804fd
PlasticDigits commented 2026-05-04 05:16:47 +00:00 (Migrated from gitlab.com)

mentioned in commit 9905b25c9c

mentioned in commit 9905b25c9c11f63b798de6a4fc4ab3127fbd8a9e
PlasticDigits commented 2026-05-04 05:17:02 +00:00 (Migrated from gitlab.com)

Implemented on main (commits through 9905b25) — referral registerCode ordering / mempool fairness per issue spec (audit L‑02 mitigation via docs + UX; no bytecode behavior change aside from ReferralRegistry NatSpec).

cc @brouie — leaving this issue open for your sign-off. Please verify when you have a cycle.

What changed (short)

Verification checklist for @brouie

  • Docs: Ordering subsection matches onchain semantics (first successful registerCode; failures do not burn; public mempool wording).
  • /referrals: Unregistered + connected wallet shows disclosure above the register CTA; layout OK on narrow width; burn line still from registrationBurnAmount().
  • E2E (optional): bash scripts/e2e-anvil.sh — anvil-referrals.spec.ts expects referrals-register-ordering-disclosure visible before register (same mock wallet caveat as existing spec if wallet already registered).
  • CI parity: FOUNDRY_PROFILE=ci forge test; frontend Vitest unchanged/green.
**Implemented on `main` (commits through `9905b25`)** — referral `registerCode` ordering / mempool fairness per issue spec (audit **L‑02** mitigation via docs + UX; no bytecode behavior change aside from ReferralRegistry **NatSpec**). cc @brouie — leaving this issue **open** for your sign-off. Please verify when you have a cycle. ### What changed (short) - **Product + invariants:** [docs/product/referrals.md § registration ordering](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/docs/product/referrals.md), [`INV-REFERRAL-121-UX` / §121](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/docs/testing/invariants-and-business-logic.md), [strategy § Stage 1](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/docs/testing/strategy.md) (deterministic mempool tests out of scope; E2E asserts disclosure element). - **UX:** [`ReferralRegisterSection.tsx`](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/frontend/src/pages/referrals/ReferralRegisterSection.tsx) — `data-testid="referrals-register-ordering-disclosure"` before **Register & burn CL8Y**; overview strip on [`ReferralsPage.tsx`](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/frontend/src/pages/ReferralsPage.tsx). - **Contracts:** [`ReferralRegistry.sol` `registerCode` NatSpec](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/contracts/src/ReferralRegistry.sol) (first-successful inclusion, calldata visibility, burn-after-checks). - **QA trails:** [manual-qa TOC + §121 checklist](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/docs/testing/manual-qa-checklists.md), [audit L‑02 footnote](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/audits/audit_smartcontract_1777813071.md). - **3rd-party / agents:** [`skills/README.md`](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/skills/README.md), contributor [`.cursor/skills/yieldomega-guardrails` item 15](https://gitlab.com/PlasticDigits/yieldomega/-/blob/main/.cursor/skills/yieldomega-guardrails/SKILL.md). ### Verification checklist for @brouie - [ ] **Docs:** Ordering subsection matches onchain semantics (first successful `registerCode`; failures do not burn; public mempool wording). - [ ] **/`referrals`:** Unregistered + connected wallet shows disclosure **above** the register CTA; layout OK on narrow width; burn line still from `registrationBurnAmount()`. - [ ] **E2E (optional):** `bash scripts/e2e-anvil.sh` — `anvil-referrals.spec.ts` expects `referrals-register-ordering-disclosure` visible before register (same mock wallet caveat as existing spec if wallet already registered). - [ ] **CI parity:** `FOUNDRY_PROFILE=ci forge test`; `frontend` Vitest unchanged/green.
Brouie commented 2026-05-04 06:20:10 +00:00 (Migrated from gitlab.com)

verified on commit edce84c (merged via 9905b25). disclosure lands in all 3 surfaces from your spec.

inline form disclosure in ReferralRegisterSection.tsx (testid referrals-register-ordering-disclosure):

"Desirable codes are not 'held' while your transaction waits: whoever gets the first successful on-chain registration wins the slug. Codes are visible in public mempool calldata, so others may compete for inclusion order. Submitting confirms you accept on-chain ordering and this published burn."

page-level disclosure in ReferralsPage.tsx:

"Burn CL8Y once via ReferralRegistry. Ownership is whoever wins the first successful registration for that slug (public mempool ordering); plaintext stored here only helps share links."

doc-level disclosure in docs/product/referrals.md covers the mempool calldata risk, the burn-as-economic-barrier-not-FIFO model, and the v1 product stance (transparent ordering, no commit-reveal).

audit L-02 cross-link added to the audit MD per your spec footnote.

E2E test frontend/e2e/anvil-referrals.spec.ts updated to assert disclosure visibility per the diff stat. v1 framing preserved — no commit-reveal, registration as transparent on-chain auction with the 1 CL8Y burn as the spam barrier.

verified on commit `edce84c` (merged via `9905b25`). disclosure lands in all 3 surfaces from your spec. **inline form disclosure** in `ReferralRegisterSection.tsx` (testid `referrals-register-ordering-disclosure`): > "Desirable codes are not 'held' while your transaction waits: whoever gets the **first successful on-chain registration** wins the slug. Codes are visible in public mempool calldata, so others may compete for inclusion order. Submitting confirms you accept **on-chain ordering** and this **published burn**." **page-level disclosure** in `ReferralsPage.tsx`: > "Burn CL8Y once via ReferralRegistry. Ownership is whoever wins the **first successful registration** for that slug (public mempool ordering); plaintext stored here only helps share links." **doc-level disclosure** in `docs/product/referrals.md` covers the mempool calldata risk, the burn-as-economic-barrier-not-FIFO model, and the v1 product stance (transparent ordering, no commit-reveal). audit `L-02` cross-link added to the audit MD per your spec footnote. E2E test `frontend/e2e/anvil-referrals.spec.ts` updated to assert disclosure visibility per the diff stat. v1 framing preserved — no commit-reveal, registration as transparent on-chain auction with the 1 CL8Y burn as the spam barrier.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-04 08:19:11 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#121
No description provided.