Skip to content

Add ERC: Token Launch Abuse Detection and Remediation - #1942

Open
Cybercentry wants to merge 51 commits into
ethereum:masterfrom
Cybercentry:erc-8376-draft
Open

Add ERC: Token Launch Abuse Detection and Remediation#1942
Cybercentry wants to merge 51 commits into
ethereum:masterfrom
Cybercentry:erc-8376-draft

Conversation

@Cybercentry

Copy link
Copy Markdown
Contributor

This PR proposes a new ERC: Token Launch Abuse Detection and Remediation.

Summary
This standard makes deployer abuse at token launches detectable in a comparable, accountable way and actionable while the money is still reachable. Detection scores ten abuse patterns from a fixed twelve-signal vector, published as bonded reports committing to chain-anchored evidence. Escrow has the launch venue hold sale proceeds and release them to the deployer on a schedule rather than immediately. Remediation adds a deployer bond, bonded claims with a contest window, and pro-rata refunds pulled by buyers from escrow, with any shortfall drawn from the bond. Scoring is restricted to deployer conduct: price decline, drawdown and buyer losses are excluded by rule, so honest failure does not read as fraud.

Dependencies: ERC-20, ERC-165, EIP-214

Files added: ERCS/erc-8376.md and assets/erc-8376/

Reference Implementation
A self-contained Foundry project accompanies the draft with no external dependencies. It implements every interface in the specification, all ten scoring profiles, and a committee adjudicator. 178 tests and four fuzzed invariants covering fund conservation and escrow solvency at 128,000 calls each.

Discussion Thread
https://ethereum-magicians.org/t/erc-8376-token-launch-abuse-detection-and-remediation/29359

The thread is active and seeks feedback on the signal vector, bond sizing, adjudicator design, the detection-only conformance tier, and how detector accuracy should be established against labelled historical launches.

Many thanks,
Leigh (@Cybercentry),
cybercentry.base.eth.

@eip-review-bot

eip-review-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

File ERCS/erc-8376.md

Requires 1 more review from Editors: @g11tech, @jochem-brouwer, @samwilsn, @xinbenlv

@eip-review-bot eip-review-bot changed the title Add ERC: Token Launch Abuse Detection and Remediation. Add ERC: Token Launch Abuse Detection and Remediation Aug 13, 2026
@github-actions github-actions Bot added the w-ci label Aug 13, 2026
@github-actions github-actions Bot removed the w-ci label Aug 13, 2026
@github-actions github-actions Bot added the w-ci label Aug 13, 2026
…l vector

Six changes to the draft:

- Define the detector pool and fund it: detectorFeeBps, claimDetectorReward
  and DetectorRewarded on ILaunchRemediation, with venues permitted to pay
  detectors directly but never conditioned on the score returned.
- Require a detection-conformant venue to surface abuseScore, confidence and
  evidenceURI to the buyer at the point of purchase, and to present an
  unreported launch as unknown rather than safe.
- Version the signal vector and add extension schemas, so a pattern that can
  be named can also be scored. Adds vectorVersion, extensionSchema and
  extensionSignals to AbuseReport.
- Add PATTERN_IMPERSONATION and specify it as the reference extension schema.
- State in Security Considerations that realised value is only visible at the
  venue, so pro-rata refunds are unsound after liquidity graduates.
- Answer the scale objection in Motivation: the losses are small in aggregate,
  distributed, constant, and unlike protocol exploits still recoverable.

Also updates the Abstract counts, adds error codes for the version and schema
rules, and adds conformance tests 19-23.
Applies the audience revisions, which change who the document speaks to
without softening any of the machinery.

- Abstract and Motivation open on the venue: the deployer is pseudonymous
  and gone, the venue is named and still trading, and it holds the only
  record of who bought what.
- Add a third conformance level. Consumer conformance requires no deployed
  contract: call checkLaunch, disclose the score, confidence and evidence
  URI at the point of purchase, and never present an unreported launch as
  safe. It is the level a venue can adopt in an afternoon, and the one from
  which the others become reachable.
- Add "What a venue must do", a table of every venue obligation by level,
  and forbid claiming a level not met in full or describing a lower level
  in language implying a higher one.
- Scope Part 1 to detectors and Part 3 to adjudicators, with an opening in
  each telling a venue how much of it applies.
- State the cost of adoption by level in Backwards Compatibility.
- Add the rationale for a level with no contracts.

The disclosure requirement moves from detection to consumer conformance,
so it is stated once rather than twice.
The sources and tests were still under assets/erc-xxxx from before the
number was assigned, while assets/erc-8376 held only the README. Both
now sit together under the proposal's own directory.

No source changes. forge build succeeds and all 178 tests pass in place.
…rence implementation

Applies the remaining revision blocks to the text:

- Name ILaunchGuard among the interfaces and require all six at full
  conformance. The guard was required at the middle level, absent from the
  list, and never covered by supportsInterface.
- State how a consumer holding only a token address resolves it to a launch:
  match escrowOf or venueOf against the venue it is transacting with, else
  check every launch and take the highest score. A tier whose whole cost is
  one view call cannot require the caller to invent a selection rule.
- Move the retraction MUST off setSubmitter, where it described nothing, onto
  retractReport.
- AbuseReport.launchId is listed in ILaunchDirectory, not ILaunchEscrow: at
  detection and consumer conformance there is no escrow.

Brings the reference implementation up to the specification it accompanies:

- AbuseReport carries vectorVersion, extensionSchema and extensionSignals.
  The registry refuses a version it does not implement and refuses a schema
  and payload that do not travel together.
- ScoreEvaluator scores extension fields on identical terms to base signals,
  with PATTERN_IMPERSONATION and its reference schema as the case that proves
  the mechanism: every base signal reads clean while the token is a copy.
- LaunchDetector derives an extension fieldId from schema and field name, so
  an extension leaf cannot collide with a base signal's.
- LaunchRemediation reserves DETECTOR_FEE_BPS of a successful restitution for
  the detector behind the upheld claim, pulled with claimDetectorReward and
  bounded by FEE_BPS.

Extension.t.sol covers conformance tests 19 to 22. 192 tests pass.
…ation

- Declare extensionFieldId on ILaunchDetector. The derivation was required in
  prose but the interface never exposed it, so no consumer could recompute an
  extension leaf from the specification alone.
- Rename error ReportRetracted to ReportAlreadyRetracted. An error cannot
  share a name with the event of the same name in the registry, so the
  specification as written did not compile.
- Add error NotRemediation, the escrow's counterpart to NotVenue.
- The venue role is not the same at every level: it consults and discloses at
  all three, and holds proceeds only at full conformance.
- Remove the schedule-change paragraph made redundant by the cost-of-adoption
  block in Backwards Compatibility.

Every interface and struct in the specification now matches the reference
implementation signature for signature, and the error lists agree.
The reward introduced with detectorFeeBps turned c.reportId from a liability
into an asset, and two things that were harmless while it only decided who
was slashable became exploitable once it decided who was paid.

A claim must cite a report that carries it. openClaim validated the cited
report only for liveness and launch, taking the qualifying score from
activeScore or corroboratedScore and never binding the two together. Anyone
holding a detector bond could file a worthless report, cite it while the
launch scored on somebody else's work, and collect the detector's share of a
finding they had no part in. The claim lifecycle in the specification already
required the cited report to score at or above the claim threshold; the
implementation did not enforce it. It does now, on score and confidence both.

A reservation must be visible to the accounting that can spend it. The
detector's share was reserved by leaving it in the escrow's bond and
recording it here, but escrow.bondOf reports the raw balance, so a second
upheld claim on the same launch saw another claim's reservation as available
and swept it into the refund pool. The first detector was then owed money
that had gone. Reserved wei are now tracked per launch and netted off before
any later payout from the same bond.

Both are covered by regression tests: a claim citing a zero-score report is
refused, and two upheld claims on one launch leave both detectors payable in
full. 194 tests pass.
The weight profile was given only in the reference extension subsection, so
the score computation table covered ten of the eleven patterns. It now
carries all eleven, matching the taxonomy above it and the reference
implementation's profileFor.
submitReport is explicitly delegable through setSubmitter, so msg.sender is
the hot key rather than the detector whenever a submitter is authorized.
Deriving reportId from the caller would give the same report two identifiers
depending on which key sent it, and contradicts the rule stated one function
below, that accountability follows the detector and not the transport.

The reference implementation already derived it from the principal.
… consumer conformance cannot prove

Two limits the draft implied but never said.

The reference implementation is not independently audited. It shows the
interfaces can be implemented and that the invariants hold under test; it is
not deployable as it stands, and the contracts that hold value are the ones
that warrant a review first.

Consumer conformance cannot be verified on-chain. A venue at that level
deploys nothing, so nothing distinguishes one that consults the guard and
discloses from one that says it does. Any mechanism that made the claim
checkable would put a deployment back into the level defined by not having
one, so the obligation is stated and left unenforced, and consumers are told
to treat the claim as a claim.
The same guard was written two ways in one reference implementation: a
string require in LaunchAbuseRegistry and the custom error the proposal
declares in LaunchEscrow. Both now revert with NotRemediation.
The escrow trusts the venue on purchases and sales, and SaleVenue passed that
trust to anyone. reportSale was callable by any address with any buyer and any
amount: since a sale report reduces the buyer's net contribution and the refund
denominator with it, one call zeroed a chosen buyer's entitlement permanently
and redirected their share of the pool. open was callable by anyone too, so a
first caller could bind the venue to a launch naming themselves as deployer
with no bond. Both are now operator-only.

recordPurchase added the gross amount to the refund denominator while net
contribution floors at zero, so a buyer who had already realised more than
they paid could add value nobody could ever claim against, scaling down every
honest buyer's share. It now tracks the change in net contribution, as
recordSale already did.

executeRemedy reported fromEscrow as the lesser of the award and the escrow,
while the escrow moves the whole unreleased balance into the pool as the
specification requires. A one wei award emitted one wei against a raise it had
just taken in full. It now reports what actually moved; the award still decides
what is drawn from the bond.

198 tests pass.
The bytecode scan trusted the token it was examining. `_codeEnd` read the last
two bytes of runtime code as a metadata length and trimmed to it, so a deployer
could name a length that collapsed the scan window to a single byte. Every
privileged power then read as absent rather than unavailable, which is an
exculpatory answer drawn from evidence never examined: a honeypot scored around
fifteen instead of eighty. The hint is now followed only where it points at a
CBOR map header of the kind a compiler emits and is small enough to be one.

Any proxy of a kilobyte or more was classified as an ordinary contract with no
privileged powers, which is the strongest possible statement about the code
least possible to examine. A transparent proxy clears that size comfortably.
Delegation now means unresolved proxy at any size.

A report could carry `windowEnd` at the maximum a uint64 holds, so the age
comparison overflowed and every later read of the launch's score panicked. The
guard swallowed it and told buyers there was no live report on a launch
carrying conclusive ones. The comparison is now done in uint256.

Impersonation could not be scored honestly from the base vector: ninety of its
hundred weight sits in the extension, so dropping it left `priorUpheldClaims`
deciding alone, and one prior claim scored 100 on no impersonation evidence.
A profile weighted on an extension now refuses to score without one, and the
registry refuses an impersonation report that declares no schema.

Half of every forfeited claimant bond was credited to the registry, which has
no way to call `withdraw`, so it accumulated permanently unreachable. The
registry now holds an explicit detector pool and is paid into.

A claim could be blocked by padding: sixty-four worthless reports pushed a real
one out of the scan window, and `openClaim` re-derived the score from that
window after already checking the cited report on its own terms. The redundant
gate is gone.

199 tests pass.
Two one-sided acts, each of which handed one party control over another's
position.

The deployer could close any claim alone, for any amount including nothing.
A claim opened against a launch could be answered in the next block with a
zero settlement: terminal, unfrozen, no adjudication and no refund, at no
cost, for every claim ever brought. The freeze, the bond, the detector reward
and the refund pool all existed at the respondent's discretion. Settlement is
now an offer the deployer funds and the claimant accepts, worth more than
zero, withdrawable while it stands and returned if the claim is adjudicated
or expires.

A bonded detector could name any address as its submitter without asking.
Naming one that had not yet bonded captured everything it later published:
its reports attributed to the nominator, its rewards paid to the nominator,
and the address unable to retract even its own work. A nomination now binds
nothing until the nominee accepts. Revocation stays one-sided, so a detector
can still retire a key it no longer trusts.

The specification carried the first of these: it required the settle path and
called it agreement without ever requiring the claimant to agree.

200 tests pass, and every interface still matches the implementation
signature for signature.
Each of these passed review as written and failed it on the second reading.

Settlement acceptance took whatever was standing. The claimant agreed to a
figure off-chain and accepted a mapping lookup on-chain, so a deployer could
watch for the acceptance, replace five ether with one wei in front of it, and
have the claim close anyway: terminal, unfrozen, and worth nothing. That is
the same veto the two-sided settlement was added to remove, one step later in
the sequence. The claimant now names the offer they agreed to.

The detector pool only ever filled. Forfeited claimant bonds were paid in and
nothing could pay them out, which is value taken from claimants and given to
nobody. The pool now tops up the reward paid to the detector behind an upheld
claim, bounded by the reward itself.

The metadata hint could still hide delegation. The trailing length is written
by the token, and a token that could name any offset could name one that
trimmed a delegating fallback out of view, reading afterwards as an ordinary
contract with no privileged powers. A region carrying DELEGATECALL is not
metadata whatever it claims, and the bound is now the size of a real one.

A declared schema with nothing readable in it scored anyway. Every field at
the unavailability sentinel dropped ninety of impersonation's hundred weight
and left one prior upheld claim reaching 100 with no impersonation evidence,
which is exactly what declaring the schema was made to prevent.

204 tests pass and every interface still matches the implementation.
Two ways a deployer could still be certified clean.

The trim test asked whether the skipped region carried DELEGATECALL, which was
never an inertness test. A dispatch table needs no delegation: a JUMPDEST and
five entries of DUP1 PUSH4 EQ PUSH2 JUMPI fit inside sixty-four bytes, none of
them containing 0xf4, and the mint and pause selectors sat in a region the
scan had been asked not to look at. The token then read as an ordinary
contract with no privileged powers, which is a clean answer drawn from
evidence deliberately excluded.

The question is not whether the skipped bytes look like metadata, since every
byte deciding that is written by the party under examination. It is whether
control could reach them. A region carrying a JUMPDEST, or sitting after a
body that does not end in a terminator, could have run, and a scan that
skipped it has established the absence of nothing: the reading is now the
unavailability sentinel rather than a clean mask. Powers actually found still
stand, wherever they were found. Unreachable bytes are still trimmed, so an
ordinary token keeps an exact reading.

CALLCODE was not treated as delegation. It runs foreign code against this
contract's own storage, so for everything this signal is for it is delegation,
and a token substituting 0xf2 for 0xf4 was read as an ordinary contract and
its implementation never scanned.

206 tests pass.
Both the proposal and the README read as though four invariants sat on top
of the test count. They are four of the tests, not four more.
…ction

Four attempts to decide which bytes were metadata, four bypasses. The length,
the header and every byte that would justify skipping a region are written by
the party under examination, so each rule was one the subject could satisfy by
construction: name a length, place a CBOR header, avoid whichever opcode the
last version looked for. The reachability test was the same mistake once more.
It read the byte before the trim as an opcode when it could be a PUSH
immediate, so a body ending PUSH1 0x00 fell through into a region the scan had
been told to skip; it never applied to a resolved implementation's own trim at
all; and it asked whether the region could execute when a dispatcher only
needs its constants there as data, four bytes at a time.

Nothing is trimmed now. The opposite error costs something real but survivable:
bytes that are data may read as an opcode by coincidence, and an over-reported
power is a claim a detector must corroborate before acting on, which the
proposal already requires. An under-reported one is an assurance nobody checks.

Selectors are matched as constants wherever they sit rather than only behind a
PUSH4, because PUSH5 with a leading zero pushes the same value and read as
absent, and a dispatcher can hold its constants as data and CODECOPY them in.
How a selector reaches the stack is the author's choice and is not evidence.

206 tests pass.
Scanning the whole runtime is right for selectors and wrong for delegation.
A selector only has to sit somewhere as data, four bytes at a time, so every
byte has to be read. An opcode has to execute to mean anything, and bytes that
cannot execute mean nothing. Reading them anyway does not over-report a power,
which a detector must corroborate in any case; it reports an ordinary token as
a proxy whose code could not be seen, and blanks privilegedPowers outright.

That is not a theoretical cost. It fired on four of the fifty-eight contracts
compiled here, and it moved whenever an unrelated source file changed, because
the bytes deciding it are a metadata hash. A token that has never delegated to
anything lost the signal on a recompile it had no part in, which falls on
exactly the honest launches the signal exists to clear.

So the region is skipped on unreachability rather than on being metadata. The
length locates a candidate and settles nothing, since the subject writes it.
A region holding no JUMPDEST cannot be jumped into. Whether execution can fall
into it is left to the walk, which is the only thing that knows if the byte
before it was an instruction or a PUSH immediate: reading that byte directly
is how the last reachability test was bypassed, and a body ending PUSH1 0x00
still reads as ending in STOP to anything that tries.

Arrange for the region to be reachable and the region gets walked. Both ways
in are tested, including the PUSH1 0x00 fall-through.

208 tests pass.
Both signals were written in terms of liquidity provider tokens. That holds
for constant-product pools, which mint a fungible ERC-20 against a deposit,
and fails for concentrated liquidity, where a position is an ERC-721 with its
own price range and there is no supply to take a share of. Base launches
graduate into both kinds, so a launch on the second sort had to report both
signals unavailable and lean on the availability renormalisation to absorb it.

Liquidity is what both pool types have. For a constant-product pool it is
proportional to LP token balance, so nothing about the existing computation
changes and lockedShare is untouched; for a concentrated-liquidity pool it is
the liquidity the positions represent, which the detector supplies and is
accountable for exactly as it is for peakLiquidity today. Both routes yield
basis points of pool liquidity, which is the property comparability needs.

A named liquidity token decides the value where there is one, so supplied
amounts cannot be presented in place of balances anyone can read. An empty
pool, or one reporting more locked than it holds, is unavailable rather than
zero: zero asserts that liquidity was measured and none of it was locked.

lpLockRemaining is the shortest lock where several apply. Reporting the
longest would let a deployer show a year while most of the float unlocks next
week, and it now follows the share it qualifies rather than the presence of a
token, since a lock on liquidity that could not be measured secures nothing.

No weight, no threshold and no profile moves. The reported vector is unchanged,
so vectorVersion stays at 1; ChainInputs is detector input and gains two fields
before Draft rather than silently.

211 tests pass.
@github-actions

Copy link
Copy Markdown

The commit 89837c6 (as a parent of 9869534) contains errors.
Please inspect the Run Summary for details.

@github-actions github-actions Bot added the w-ci label Aug 22, 2026
The directory is assets/erc-8376 and the link read ../assets/eip-8376/, which
looks like a typo and is not one. The site renders every ERC as EIPS/eip-N and
serves its assets from assets/eip-N to match, so the eip- form is the path that
resolves and the erc- form is the one that 404s. HTMLProofer said so on the
built page; 202 other proposals already link this way.
@github-actions github-actions Bot removed the w-ci label Aug 22, 2026
The interface opened by counting four of ten signals from chain state, three
from the service and three from the escrow. The vector has twelve, and that
split was never this code: observe derives seven, one of them through the
escrow the directory names, carries four from the service, and passes the time
left on a liquidity lock straight through.

The paragraph admitting supplied liquidity amounts restated two rules the
signal table already gives three hundred lines earlier, and added a third that
could not hold. Publishing both amounts does not make the share checkable when
nothing names the pool the positions sit in, and a MUST that reads as a
verifiability guarantee while providing none is worse than no clause at all.
What is left is the part the location needs: why a supplied figure is admitted
into a recomputable signal at all, and what answers for it when the pool says
otherwise.

211 tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants