betterERC
All posts
InternalsMarch 4, 20267 min read

Sealing should be loud

Until an assembly seals its extension set, every discovery view reverts. A forgotten seal should be impossible to ship, not merely unlikely to be shipped.

An assembly built on this framework has to call one function at the end of its constructor. It freezes the extension set and validates that the declared behaviours are consistent with one another. There are two reasonable things to do when somebody forgets.

The quiet option is to treat an unsealed registry as an empty one. The token deploys, reports no extensions and a behaviour word of zero, and every integrator who reads it caches a declaration that was never made. A token with a live fee looks like a plain ERC-20 to everyone who checked. That is the exact failure this framework exists to prevent, reached from the inside.

The loud option is to revert every discovery view until the seal happens. An assembly that forgets has no discovery surface at all rather than a quietly unvalidated one, and it fails at deployment — in a test, on a fork, in front of the person who can still fix it.

Checking a flag on the transfer path instead would tax every transfer forever, to catch a mistake that can only be made once.

The check that had to be non-overridable

There is a subtler version of the same hole. The public function returning a module's configuration performs two checks before it answers: that the registry is sealed, and that the module is actually installed. If a module could override that public function, it would answer for its own identifier before either check ran — and a token with an unsealed registry would still look configured to anyone asking about that module.

So the public function is deliberately not overridable, and modules extend an internal one that it reaches only after both checks have passed. It is a small thing. It is also the difference between a guarantee and a convention.

What sealing validates

Sealing is also where the declared behaviours are checked against each other. Declaring transfers impossible and a transfer fee in the same breath is not a contradiction the compiler can see, but it is one an integrator would have to reason about: the fee sits on an unreachable path, and it frightens off anyone who read the word without reading the source.

The combination test deploys all thirty-two subsets of the module set. Twenty deploy and transfer exactly as declared. Twelve revert at construction, which is the right place for a token that could never have made sense to stop existing.