Namespaced storage (ERC-7201)
Every upgradeable contract in the protocol keeps its state in namespaced storage, following ERC-7201. Instead of placing variables in sequential slots, each contract places its state in a storage region whose position is derived from a name. A contract's layout can then evolve across upgrades without the risk that a new variable overwrites an old one, or the state of a contract it inherits from or is composed with.
For a system that is meant to be upgraded over a long life, and that builds accounts and tokens out of several layers, this is a safeguard and not a convenience. It removes a class of upgrade error that has caused serious failures in upgradeable systems built without it.
The problem it removes
A proxy keeps the state. The implementation behind it defines how that state is read. With sequential storage, a variable's position is its rank in the order of declaration, counted across the whole inheritance chain.
A variable is added to a parent contract
Every variable declared after it, in every child, shifts by one slot and reads another variable's data.
The order of inheritance changes
The same.
A dependency adds state in a new release
The same, without any change in the project's own code.
The usual defense is to reserve empty slots in each contract and to audit the layout by hand at every upgrade. It works as long as nobody makes a mistake.
How namespaced storage works
Each contract, or each layer of a contract, defines one structure holding all its state, and stores it at a position computed from a name.
Namespace
A string that identifies the layer, for example erc3643.storage.TREXToken.
Location
A storage position derived from the namespace by a fixed formula. Two namespaces give two positions far apart.
Structure
The state of the layer, laid out from that location onward.
A layer reads and writes only inside its own structure. Adding a field to one layer cannot move anything in another, whatever the inheritance.
The namespaces of the protocol
Token suite
Each contract of a suite is composed of several layers, each with its own namespace.
Token
The standard token: erc3643.storage.ERC3643Token. The T-REX extension, with the bridged ledger: erc3643.storage.TREXToken. Messaging: erc3643.storage.TREXMessaging.
TREXRegistry
The three standard registries: erc3643.storage.IdentityRegistry, erc3643.storage.TrustedIssuersRegistry, erc3643.storage.ClaimTopicsRegistry. The T-REX extension: erc3643.storage.TREXEligibility.
IdentityRegistryStorage
erc3643.storage.IdentityRegistryStorage
ModularCompliance
The standard compliance: erc3643.storage.Compliance. The T-REX extension: erc3643.storage.TREXCompliance. The ledger: erc3643.storage.ComplianceLedger. The validations: erc3643.storage.TransferValidation.
TREXAccessManager
The domain registry: erc3643.storage.TREXAccessManager, next to the namespaces of OpenZeppelin's AccessManager.
Compliance modules
The common base: erc3643.storage.AbstractModuleUpgradeable. One namespace per module that keeps settings, for example erc3643.storage.MaxBalancePerIdentityModule.
The OpenZeppelin contracts these build on, such as the ERC-20 base and the pause, use namespaced storage of their own.
Identity layer
Identity
onchainid.identity.metadata
ERC734Validator
onchainid.validators.erc734-validator
IdentityFactory
onchainid.IdentityFactory
ReputationRegistry
onchainid.ReputationRegistry
What the separation buys
The standard and the extension evolve independently. The token keeps the state defined by ERC-3643 in one namespace and the state that T-REX adds in another. No extension writes into the namespace of a standard base. The standard layer can be replaced or updated without touching the extension's state, and the reverse.
Each concern can be reviewed alone. Messaging has its own namespace, so the messaging layer is reviewed without the token's ledger, and the ledger's layout is never disturbed by a change to messaging. The same holds for the compliance ledger and for the validations.
A dependency cannot shift the protocol's state. A new release of a library that adds state adds it in the library's namespace.
Shared contracts keep state per user
Some contracts are deployed once and serve many users. Their state is keyed inside the namespace, so users never share it.
ERC734Validator
Every identity
The identity
KeyApprovalModule
Every identity that installs it
The identity
A compliance module
Every compliance that binds it
The compliance, and the current binding
For a compliance module, tying the settings to the current binding means that a module unbound and bound again starts from empty settings. Nothing from a previous binding can reappear.
What it does not do
Namespacing protects layers from one another. It does not make every change to a layer safe.
Fields are only appended. Inside a structure, new fields go at the end. Existing fields are never reordered, removed or retyped.
Within one namespace, the layout is still positional.
A namespace is never renamed.
The name defines the location. A new name points at empty storage.
Values fixed in the implementation are not storage.
An identity reads its factory and its key registry from its implementation, not from its own storage. An upgrade of the implementation changes them for every identity, which is why the IdentityFactory checks that a new implementation names the same ones. See Upgradeability: beacons and versions.
For integrators
Do not read the protocol's state by storage slot. The location of a variable is an implementation detail of a layer, and the supported way to read state is the view functions of each contract. Tools that inspect storage, such as upgrade-safety checkers, recognize ERC-7201 through the annotation each structure carries.
Last updated