Skip to content

ClaimRegistry: one fee, one claim - #1

Merged
Yonkoo11 merged 5 commits into
mainfrom
claim-registry
Sep 18, 2026
Merged

Yonkoo11 merged 5 commits into
mainfrom
claim-registry

Conversation

@Yonkoo11

Copy link
Copy Markdown
Owner

What this is

A contract on Robinhood Chain that records a claim and takes its fee in the same transaction. claim(digest) pulls 100,000 QUORUM from the claimant, burns it through the token's own burn(uint256), and only then writes claimedAt[digest][claimant]. A record cannot exist without its own fee and a fee cannot back two records. No owner, no pause, no upgrade, no ether, nothing to sweep. A different fee is a different registry address.

This closes the limit that SECURITY.md and the README honesty table have carried since 13 Sep: on chain, nothing tied a burn to a claim.

What is built and tested

  • contracts/src/ClaimRegistry.sol (about 70 lines, solc 0.8.28, Paris).
  • 18 Foundry tests with a mock token whose misbehaviour is switchable (returns false, no return value, fee-on-transfer, reverting burn, no-op burn, re-entry during transferFrom), a fuzz test at 10,000 runs, and an invariant (supply drop equals fee times distinct (digest, claimant) pairs; the registry never holds a balance). forge test passes.
  • Python: attest approves only when the allowance is short and claims through the registry; read_claim reads the Claimed log from the registry address and ignores that log from any other address; a self-addressed QUORUM2 claim mined after the registry existed is refused; older claims still read; the import ledger is charged to the claim for registry claims and to the burn for older ones; the in-browser verifier on the registry page reads the receipt. 52 tests pass.

What is not done

  • Not deployed. REGISTRY_DEFAULT is None, so on this branch quorum attest refuses until QUORUM_REGISTRY is set. That is why this is a draft and not on main.
  • The fork test against the real token (forge test --match-contract Fork --fork-url "$QUORUM_RPC" --evm-version cancun) could not complete: the public Robinhood RPC drops the connection mid-run. It runs when a stable node is available.
  • Not verified on the explorer (the Robinhood Blockscout API blocks scripts; verification is the browser form).

Merge conditions

  1. Deploy with forge script script/Deploy.s.sol:Deploy --rpc-url "$QUORUM_RPC" --broadcast --slow (the key comes from DEPLOYER_PRIVATE_KEY in the environment).
  2. One real claim through the registry, verified with quorum verify, revealed, and imported into a fresh memory.
  3. REGISTRY_DEFAULT and REGISTRY_SINCE set; SECURITY.md, README and the registry page updated in the same commit.

Clone note: git submodule update --init for forge-std.

🤖 Generated with Claude Code

Yonkoo11 and others added 3 commits September 15, 2026 20:43
…de; not deployed)

contracts/: ClaimRegistry.sol pulls the fee and burns it in the transaction that records the claim, one record per
(digest, claimant), no owner, no pause, no upgrade, no ether. 18 Foundry tests with a switchable mock token, a fuzz
at 10,000 runs, an invariant (supply drop equals fee times distinct claims), and a fork test against the real token
that runs when a Robinhood RPC is reachable. Python: attest approves only when short and claims through the registry;
read_claim reads the Claimed log; a self-addressed claim mined after the registry is refused; the import ledger is
charged to the claim for registry claims and to the burn for older ones. The in-browser verifier reads the receipt.
REGISTRY_DEFAULT stays None until the user deploys. 52 tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Deployed 2026-09-18 at block 66593107, runtime bytecode matched against the
local build with the immutables masked, token and fee read back from chain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Quorum

3 finding(s) confirmed by two lenses each. 5 candidate(s) seen by one lens and held back.

reentrancy in fixtures/VulnerableVault.sol withdraw

  • callorder-lens, line 17: an external call happens before a state write in the same function, or in a helper it runs. (bool success, ) = msg.sender.call{value: amount}("");
  • guard-lens, line 17: a function (or a helper it runs) moves value out and carries no reentrancy guard. (bool success, ) = msg.sender.call{value: amount}("");

unguarded-state-write in fixtures/OpenFeeSetter.sol setFeeRate

  • modifier-lens, line 12: externally callable, writes storage, carries no modifier at all. feeRate = newRate;
  • sender-lens, line 12: writes a privileged-looking variable with no msg.sender check anywhere. feeRate = newRate;

unguarded-state-write in fixtures/OpenFeeSetter.sol setTreasury

  • modifier-lens, line 17: externally callable, writes storage, carries no modifier at all. treasury = newTreasury;
  • sender-lens, line 17: writes a privileged-looking variable with no msg.sender check anywhere. treasury = newTreasury;

A finding is a shape worth review, not a vulnerability claim. Two independent readings agreed; a person decides.

Yonkoo11 and others added 2 commits September 18, 2026 23:50
…orge tests in CI

Deployed 2026-09-18 at block 66593107. First claim through it the same day
(block 66594959), verified, revealed, imported into an empty memory. Older
QUORUM2 claims still read against their separate burn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Yonkoo11
Yonkoo11 marked this pull request as ready for review September 18, 2026 22:52
@Yonkoo11
Yonkoo11 merged commit 1388714 into main Sep 18, 2026
5 checks passed
@Yonkoo11
Yonkoo11 deleted the claim-registry branch September 18, 2026 22:54
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.

1 participant