Repository navigation
Releases: nuthatch-org/redstart
Release list
v0.17.0
Most of this was found by porting the PancakeSwap Infinity CL subgraph (a real 2,097-line
AssemblyScript subgraph, not an example) in nuthatch-org/pancakeswap-infinity-cl-redstart.
Fixed
- A module-qualified helper call emitted an undefined namespace (#6). Helpers are emitted flat into
mappings.ts, butaccounts::double(x)lowered through the generic path rule toaccounts.double(x), whichcheckaccepted andgraph buildrejected withCannot find name 'accounts'. The qualifier is now checked against the declaring module and dropped in the output, andredstart testresolves the same call. redstart testreported failures against the wrong file. A failure inside a handler or helper was located in the file holding thetestblock, at the line and column of the real fault, somain.red:16:31was printed asaccounts.red:16:31. The location now comes from the module the failing code belongs to.- A keyword used as a binding name said only "expected an identifier".
let from = …is the natural first line of a transfer handler, andfromis reserved. The error now names the reserved word and suggestsfrom_. Some(x)was emitted as a call to an undefined function. AnOption<T>lowers to graph-ts'sT | null, soSomeis the identity constructor. It fell through to the generic call path instead and emittedSome(x)literally, whichcheckaccepted and onlygraph buildrejected.Nonewas already handled.- Matching an
Optionemitted a loose!= null, crashing the AssemblyScript compiler. graph-ts givesBigInt,BigDecimal,BytesandAddressan@operator('!=')taking two of themselves, so comparing one tonullsendsascintocompileBinaryOverload, where it fails an internal assertion and aborts the whole build with no line number and no recoverable diagnostic. Null tests are now strict!==, which skips overload resolution and is the correct test for a nullable reference. Any subgraph with a nullableBigIntfield - a nullabletickis the common one - hit this. Entity.create(…)/Entity.loadOrCreate(…)only lowered inletposition. As a bare statement (Swap.create(id, { … }), the "record this event and move on" shape) or in areturn, it emittedEntity.create(id, /* record */)- a static graph-ts does not have, with the record literal replaced by a comment. Both positions now expand into a temporary, guarded so that<Template>.create(addr)still spawns a data source: in statement position the two are textually identical.
Added
graft { base, block }. Start from another deployment's entity store instead of indexing from scratch, which is what a port of a live subgraph almost always wants: re-syncing a busy chain from deployment takes days. Emits both thegraft:block andfeatures: [grafting], since graph-node rejects a graft that is not declared as a feature. Previously listed inporting.mdunder Things that will not translate. Note that only graph-node validates a graft, at deploy time, and only a node already holding the base can perform one — Subgraph Studio holds only what is deployed to Studio and refuses withthe graft base is invalid: deployment not found.mutableon an entity, to decline the append-only inference. The §4.3 optimisation infers@entity(immutable: true)for an entity nothing loads back. That changes graph-node's storage layout, so a schema that must stay compatible with a graft base could not use it — and had no way to say so.mutableopts out,immutablestill opts in, and a timeseries cannot opt out and now says so rather than silently ignoring it.E006, unknown entity modifiers. They were silently ignored, which was tolerable untilmutableexisted: a misspelledmutibleleft the inference in place, turned the entity immutable, and made the subgraph un-graftable with no diagnostic anywhere. Unknown modifiers are now an error with a "did you mean" suggestion, andmutable+immutable/mutable+timeseriesare rejected as contradictory.E073, duplicate function. Twofns of the same name, in one module or two, collided in the flatmappings.tsand failed only insidegraph build. Each name must now be unique across the project.E072, call to undefined function.redstart checkreportedno errorsfor a project calling helpers that exist nowhere in the module tree; onlyverifycaught it, as an AssemblyScriptCannot find namefrom insidegraph build- no Redstart source line, and only after codegen. That is the worst place for it, because an unresolved call is the single most common thing a half-finished port has, andcheckis the fast inner loop: it is whatredstart devre-runs on save and whatcheck --jsonhands an agent. A barename(…)call is now resolved against everyfndeclared anywhere in the project, and a miss is an error at the call site. The fix-it is tailored: a case-only mismatch suggests the declared spelling, and an entity name called as a function points atEntity.create(id, { … }). A module-qualifiedmod::helper(…)is resolved against that module (from the root, or relative to the caller), so a qualifier naming the wrong module is the same error, with a fix-it pointing at the bare call. Every other call shape (Entity.load,<Contract>.bind,<Template>.create,log.info,BigInt.fromI32) was never at risk. The catalogue is 37 codes (31 errors, 6 warnings).redstart test: thelognamespace.log.debug/info/warning/errorevaluate their arguments and yield unit;log.criticalfails the test, since in graph-node it terminates the handler. A handler that logged could not be tested at all before.redstart test: the transaction fields the porting guide documents..from,.to,.valueand.gasPrice(plusgasLimit,gasUsed,nonce,index) on the mock transaction, which carried only.hash.redstart test: numeric and string surface.BigInt.fromI32/fromString/fromByteArrayandBigDecimal.fromString;pow,mod,leftShift,rightShift,neg,equals/notEqual/lt/gt/le/ge;BigDecimalarithmetic, which previously fell through to theBigIntpath and silently failed;startsWith,endsWith,includes,slice,charAt,concat,split,toLowerCase,toUpperCase, and.lengthon strings and byte strings;pushon arrays. Any subgraph doing real arithmetic was untestable natively.assertMissing(Entity, id). Asserts an entity was not written. A test that only checks a handler did not abort asserts nothing; this is what makes it a test.
v0.16.0
The porting release.
Fixed
- Handler symbols are source-qualified when they would collide. Two sources handling the same event name (three registry contracts all emitting
CollSurplusPoolAddressChanged, say) emitted three identically-named exported functions, three imports sharing one alias, and three manifest entries pointing at the same symbol, whichgraph buildrejects withDuplicate identifier. Colliding handlers now becomehandle<Source><Event>and their trigger classes are aliased per source. A name with nothing to disambiguate is untouched, so the common single-source case still readshandleTransfer. - A bound ABI is imported and listed in the manifest.
Abi.bind(addr)for a contract the data source doesn't itself watch, such as reading a token'ssymbol()from a factory handler or a registry from inside a helperfn, emitted the contract class with no import and no manifestabis:entry, failing eject withCannot find name. Every bind, including in a helper, is now resolved across the project: the ABI is listed on each data source and imported from the modulegraph codegenwrites it to. Declaring an inert template with a dummy block handler is no longer necessary. - Narrow Solidity integers lower correctly. A
uint8/uint16/uint24orint8…int32parameter was typedBigInt, soevent.params.op == 1emitted.equals(1)on whatgraph codegenhad made a nativei32. The width table now matches graph-cli's exactly, and Solidity array types map to lists. - A description containing a colon no longer breaks the manifest. It is emitted as a quoted YAML scalar. Previously the graph CLI refused to parse
subgraph.yamlat all. Bytes.empty/Address.zeroare called, not referenced. They lowered to a bare function reference that failed to compile, likeBigInt.zerobefore it.
Added
redstart verifybuilds, then runsgraph codegen+graph buildover the output.checkandbuildanswer questions about your source;verifyanswers the one that decides a deploy, and it is the command to put in CI. (deploy --dry-runalready did this.verifyis the honest name for it.)entity.field = Noneclears an optional field, lowering to the generated setter'sunset, with no rawstore.get/unset/store.setescape hatch.E056rejects clearing a field that isn'tOption<T>.Booleanis accepted alongsideBool, because that is what everyschema.graphqlsays. The checker used to reject it while its own error text recommended it.E057, unknown trigger parameter.event.params.x(orcall.inputs.x) naming something the ABI doesn't declare is now an error listing the real parameter names, instead of passing through to fail atgraph build. Unnamed ABI inputs areparam0,param1, … exactly asgraph codegennames them.E055,.save()teaches instead of confusing. It used to report "Xhas no fieldsave". It now explains that entities dirty-track and save themselves. The catalogue is 35 codes (29 errors, 6 warnings).- Porting an existing subgraph, a book chapter covering the schema and mapping translation tables, exactly what an event handler can see (and that
event.receiptis not available), the transaction-keyed staging-entity pattern that replaces receipt inspection, multi-source event names, and multi-network deployment. redstart newwrites a fuller.gitignore, andredstart buildsays plainly that its output is generated.
v0.15.0
Author-side MCP server — roadmap §5.3 (the agent keystone).
Added
-
redstart mcp— a Model Context Protocol server. Exposes the toolchain the
compiler already owns as MCP tools an AI agent can drive in a write → check → fix
loop, over stdio (newline-delimited JSON-RPC 2.0, no external SDK):check— type-check a project (path) or inlinesourceand return the
same structured diagnostics asredstart check --json(errors and lint
warnings, each with a code, message, and file/line). A parse/load failure is
returned as a normal{ ok: false, diagnostics }result, not a tool error, so
the agent always gets machine-readable feedback. This is the keystone tool.explain— explain a diagnostic code (what triggers it, the footgun it
prevents, the fix); omitcodeto list every code.build— emitschema.graphql,subgraph.yaml, andsrc/mappings.ts,
returning the artifacts (and optimisation notes);write: truealso writes them.test— run the project'stestblocks natively and return per-test results.
New
redstart-mcpcrate; the CLI gains theredstart mcpsubcommand alongside
redstart lsp.
v0.14.0
Optimising compiler — roadmap §4.3 (fix --ids completeness).
Changed
redstart fix --idsnow converts the via-a-local id pattern. The most common
real-world shape —let id = addr.toHexString(); Entity.create(id, …)— is now
auto-converted (previously reported and skipped): the.toHexString()is dropped
on theletbinding, the construction site is left untouched, and the entity is
flipped toId<Bytes>. It stays conservative: the local is converted only when it
is used exactly once in the handler/function body (that single id site), so
re-typing it toBytescan't affect any other reader. A local that is read again
(logged, concatenated, used as a second id) is still reported and left untouched
(⤫ … id built via a local that is used more than once).
v0.13.0
Optimising compiler — roadmap §4.4 (stored arrays → @derivedFrom).
Added
-
W050 — stored array of entity references. The checker now flags an entity
field typed[Child](a stored array of another entity, not aderived from
relation) and steers to@derivedFrom. graph-node keeps such arrays inline and
rewrites the whole array into a new versioned row on every append, so a growing
one-to-many is O(n²) on disk; a@derivedFromreverse lookup is computed on
read and never stored. Scalar and enum arrays ([String],[BigInt],
[TokenStandard]) are genuinely stored values and are never flagged, and the
recommendedderived fromform stays clean. The diagnostic suggests a concrete
back-ref field name. Registered inredstart explain W050. Brings the
diagnostic catalogue to 32 codes (26 errors, 6 warnings).Like W040, this is a warning rather than an auto-rewrite: the migration needs a
back-ref field the author chooses (and it changes the stored data model), so it
can't be applied mechanically the way immutability inference can.
v0.12.0
Optimising compiler — roadmap §4.3 (Bytes-ids, the rewrite half).
Added
-
redstart fix --ids— the W040 autofix. Turns theString→Bytesid
lint into an opt-in, in-place rewrite: it flips the entity's schema declaration
toId<Bytes>and drops the.toHexString()at every construction site, in
one coordinated pass across all modules. ABytesid indexes ~28% faster and
stores ~48% less than the equivalent hex-string id (Edge & Node benchmark).Conservative by construction — an entity is converted only when every one of
its id sites (in handlers, functions andtestblocks) is provably a single
stringifiedBytes/Addressvalue. A single literal-string id, composite key,
or id built via an intermediate local and the whole entity is left untouched and
reported (⤫ Entity skipped: …), so the command never emits code that fails to
check. It re-verifies the result and refuses to leave a broken tree.--dry-runpreviews the plan without writing. Because aBytesid changes the
stored id representation (hex-string → raw bytes), this is a deliberate data
change — hence opt-in — so redeploy affected subgraphs from the relevant block.
redstart explain W040now points at the fixer.
v0.11.0
Optimising compiler — roadmap §4.3 (Bytes-ids, the id half).
Added
- W040 — stringified-id warning. The checker now flags an entity keyed on a
singleBytes/Addressvalue stringified with.toHexString()/.toHex()
(directly, or via a local), and recommendsid: Id<Bytes>. ABytesid (with
immutability) indexes ~28% faster and stores ~48% less than the equivalent
hex-string id (Edge & Node benchmark). Genuine composite keys
(a.toHexString() + "-" + b…) and literal-string ids are never flagged.
This is a warning rather than an auto-rewrite: converting a String id toBytes
changes the stored id representation (hex-string → raw bytes), so it is a data
change the author opts into — unlike immutability inference, which is
store-identical. Registered inredstart explain W040. Brings the diagnostic
catalogue to 31 codes (26 errors, 5 warnings).
v0.10.0
Optimising compiler — roadmap §4.5.
Added
indexerHints: prune: autoby default. graph-node's default is
prune: never(largest DB, slowest queries); Redstart now emitsprune: auto,
which keeps only the history needed for reorgs — a smaller database and faster
queries, with no effect on current-state queries. The eject path is unchanged
(graph buildaccepts it). Reported as a build note.
v0.9.0
The optimising compiler begins — roadmap §4.3 (Lever 2).
Added
- Inferred
immutableentities. The checker now proves which entities are
append-only — created but never loaded (load/loadInBlock/loadOrCreate) and
never field-mutated anywhere — and codegen emits@entity(immutable: true)for
them automatically. Immutable entities index faster and use far less disk
(Edge & Node benchmark: up to 19% faster / 48% less disk).redstart build
reports each inference (⚡ inferred @entity(immutable: true) for …).
Conservative by construction; consistent with the gate-proven hand-annotated
immutability on the ERC-20 fixture. Generated.notes— informational optimisation notes, separate from warnings.
v0.8.0
Division footguns — roadmap §3.4 / §3.5.
Added
- E090 — dividing by a statically-zero value (
/ 0,/ 0.0,
/ BigInt.zero(),/ BigDecimal.zero()) is now a compile error. A divide-by-
zero is a fatal, deterministic sync halt (attempted to divide … by zero). - W030 — assigning a
BigInt / BigIntresult to aBigDecimalfield is
flagged: integer division truncates the fraction first (the canonical Uniswap
"price returns 0" bug). Use.divDecimal(). Integer division into integer
fields is unaffected (no false positives). redstart explaincovers both new codes.