Add ERC: Token Launch Abuse Detection and Remediation - #1942
Open
Cybercentry wants to merge 51 commits into
Open
Add ERC: Token Launch Abuse Detection and Remediation#1942Cybercentry wants to merge 51 commits into
Cybercentry wants to merge 51 commits into
Conversation
Updated terminology and formatting in the README for clarity.
Clarified language regarding adjudicator disclosure and trust issues.
Collaborator
File
|
4 tasks
…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.
Cybercentry
force-pushed
the
erc-8376-draft
branch
from
August 21, 2026 14:13
27f8755 to
38c5101
Compare
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.
|
The commit 89837c6 (as a parent of 9869534) contains errors. |
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.