Repository navigation
feat(factory): add variantParams bytes field to B20Created event for stablecoin currency (BOP-158) - #102
Merged
amiecorso merged 5 commits intoMay 29, 2026
Conversation
…stablecoin currency (BOP-158)
Add a `bytes variantParams` field to the `B20Created` event carrying
variant-specific immutable identity data that isn't already covered by the
fixed event fields. The bytes payload leads with a version byte and is
ABI-encoded per variant — same shape as the existing `createParams` pattern
— so stream-based indexers (e.g. Coindexer) that can't make mid-handler
RPC calls can decode by `(variant, leading version byte)` and recover
immutable fields without reading storage.
Payload per variant:
- DEFAULT: empty (no extra immutable fields beyond what's already in the event)
- STABLECOIN: abi.encode(B20StablecoinEventParams { version, currency })
- SECURITY: empty (its `isin` and `minimumRedeemable` are mutable and
surfaced via their own SecurityIdentifierUpdated /
MinimumRedeemableUpdated events)
Forward-compatible: any variant can promote from empty to non-empty in a
future version without re-issuing a new event type.
Adds:
- `struct B20StablecoinEventParams { uint8 version; string currency; }` in
IB20Factory.sol
- `uint8 B20_STABLECOIN_EVENT_PARAMS_VERSION = 1` constant in B20FactoryLib.sol
- New emission logic in MockB20Factory.createB20 that builds variantParams
per variant
- Updates to the two existing B20Created event-match tests (DEFAULT,
SECURITY) for the new 6-arg signature
- Two new STABLECOIN-focused tests: expectEmit-level pin of the encoded
payload, and a recorded-logs decode test that round-trips the currency
through B20StablecoinEventParams and verifies the version constant
Supersedes Option B (separate StablecoinCreated/SecurityCreated companion
events) — see closed PR #85. The Rust precompile counterpart is tracked
at BOP-216.
…yLib Pull the abi.encode of B20StablecoinEventParams out of MockB20Factory and into a B20FactoryLib.encodeStablecoinEventParams helper, matching the existing encodeXCreateParams pattern (encodeDefaultCreateParams / encodeStablecoinCreateParams / encodeSecurityCreateParams). The struct itself stays in IB20Factory.sol alongside the create-params structs. Adds the matching encoder unit test file (test/unit/B20FactoryLib/encodeStablecoinEventParams.t.sol) with the two-test shape used by every existing encoder: - round-trips-through-decode (pins version constant + currency) - matches-hand-encoded-struct (pins byte-for-byte encoding shape)
| /// `B20StablecoinCreateParams.currency`. Surfaced here | ||
| /// because `currency` has no setter and no other | ||
| /// event ever emits it; indexers that cannot make | ||
| /// mid-handler view calls (e.g. Coindexer) would |
There was a problem hiding this comment.
Suggested change
| /// mid-handler view calls (e.g. Coindexer) would | |
| /// mid-handler view calls would |
| /// @param name ERC-20 token name. | ||
| /// @param symbol ERC-20 token symbol. | ||
| /// @param decimals ERC-20 decimals (fixed per variant). | ||
| /// @param variantParams ABI-encoded variant-specific immutable identity |
There was a problem hiding this comment.
Naming convention should match between variantParams and the underlying encoded data, i.e. `B20StablecoinEventParams.
…riantParams → eventParams) Two cleanups from Stevie's review on PR #102: 1. Strip three Coindexer references from OSS-facing NatSpec/comments (src/interfaces/IB20Factory.sol, test/lib/mocks/MockB20Factory.sol, test/unit/B20Factory/createToken.t.sol). Per the leak-prevention rule in AGENTS.md, internal Coinbase tool names should not appear in anything under projects/<oss>/. The remaining text — "stream-based indexers" / "indexers that cannot make mid-handler view calls" — conveys the same meaning without naming a specific tool. 2. Rename the B20Created event field variantParams → eventParams to align with the B20*EventParams struct family stem (B20StablecoinEventParams, encodeStablecoinEventParams, B20_STABLECOIN_EVENT_PARAMS_VERSION). The struct family already uses the *EventParams stem to signal event-time payload (vs. *CreateParams for create-time payload); the field now shares that stem. Wire-level B20Created selector changes because the field rename changes the canonical ABI signature (bytes variantParams → bytes eventParams). Will need to ship in lockstep with the Rust counterpart (BOP-216).
| /// with `currency`. Indexers decode by | ||
| /// `(variant, leading version byte)`. | ||
| event B20Created( | ||
| address indexed token, B20Variant indexed variant, string name, string symbol, uint8 decimals, bytes eventParams |
Collaborator
There was a problem hiding this comment.
nit:
Suggested change
| address indexed token, B20Variant indexed variant, string name, string symbol, uint8 decimals, bytes eventParams | |
| address indexed token, B20Variant indexed variant, string name, string symbol, uint8 decimals, bytes variantParams |
|
…y review) Roll back the variantParams → eventParams rename from 64ec9b9 per Conner's review comment on PR #102. Stevie's earlier ask was to align the event field with the underlying encoded data; that's satisfied either direction (eventParams matching the *EventParams struct family, or variantParams emphasizing the per-variant payload nature). Conner reviewed three minutes after the rename push and explicitly suggested variantParams, so the field returns to its original name. The B20StablecoinEventParams struct, the encodeStablecoinEventParams helper, and the B20_STABLECOIN_EVENT_PARAMS_VERSION constant all keep their *EventParams names (they are about event-time payload type, distinct from the variantParams field name which is about per-variant event-payload bytes carriage). Coindexer strip from 64ec9b9 stays.
Compromise between the two reviewer threads on PR #102: - Conner asked for variantParams (emphasizes per-variant nature) - Stevie asked for the field to share a stem with the encoded data, which lives in the B20*EventParams struct family variantEventParams captures both: "variant" prefix preserves Conner's per-variant semantics; "EventParams" suffix matches the struct / encoder / version-constant family already in place (B20StablecoinEventParams, encodeStablecoinEventParams, B20_STABLECOIN_EVENT_PARAMS_VERSION). Side effect: the longer identifier pushes the event signature past forge fmt's 120-char limit, so it reflows to one-param-per-line.
amiecorso
deleted the
amiecorso/bop-158-b20created-variantparams-bytes-field
branch
May 29, 2026 22:06
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.
Context
Tracking ticket: BOP-158
Adds a
bytes variantParamsfield toB20Createdso stream-based indexers (e.g. Coindexer) can recover the immutablecurrencyof a stablecoin without making mid-handler RPC calls. The payload format mirrors the existingcreateParamspattern: ABI-encoded variant-specific struct prefixed by a version byte.Supersedes the closed #85 (Option B: separate
StablecoinCreated/SecurityCreatedcompanion events). Rust precompile counterpart: BOP-216.Design
Indexer contract: variant comes from the indexed field; if
variantParams.length == 0, no extra immutable fields; if non-empty, first byte is the version, decoder selected by(variant, version). Same shape asB20CreateParams/B20StablecoinCreateParams/B20SecurityCreateParams.Per-variant payload
variantParamspayloadDEFAULT"")STABLECOINabi.encode(B20StablecoinEventParams { version: 1, currency })currencyis immutable, no setter, no other event emits itSECURITY"")isinis mutable viaupdateSecurityIdentifier(emitsSecurityIdentifierUpdated);minimumRedeemableis mutable viaupdateMinimumRedeemable(emitsMinimumRedeemableUpdated)Forward-compatible: any variant can promote from empty to non-empty in a future version (just bump the variant's
*_EVENT_PARAMS_VERSIONand decode by(variant, version)).Why Option A over Option B (PR #85)
Changes
src/interfaces/IB20Factory.solbytes variantParamstoB20Created; addstruct B20StablecoinEventParamssrc/lib/B20FactoryLib.solB20_STABLECOIN_EVENT_PARAMS_VERSION = 1constanttest/lib/mocks/MockB20Factory.solvariantParamsper variant beforeemit B20Createdtest/unit/B20Factory/createToken.t.soltest_createB20_success_emitsB20Created_stablecoin(expectEmit-level pin) andtest_createB20_success_b20CreatedVariantParams_stablecoin_decodes(recorded-logs decode round-trip)Verification
forge fmt --checkclean on touched filesforge buildclean (only pre-existing warnings on unrelated files)B20Createdemission tests pass:Diff stats
4 files changed, 170 insertions, 6 deletions.
Notes for review
bytes memory variantParamslocal at the bottom ofcreateB20. Stayed under Solc's stack-too-deep limit; no inlining gymnastics needed (the local is allocated after_writeBaseStorageso several other locals are no longer live by the time it's introduced).test_createB20_success_b20CreatedVariantParams_stablecoin_decodes) uses a deterministic seed → 3-letter uppercase generator (_make3LetterUppercase) rather thanvm.assume(_isValidFiatCode(...))over a raw string. Avoids foundry's fuzz rejection-rate ceiling that the assume-filter approach hit on the first run._computeAddressand_writeBaseStoragesignatures are unchanged.Related