This page covers the smart contract implementation details. See Glossary.
VEHICLE_EXEC role.
How modules work
The Modules Manager is a registry plus a per-target allowlist. It never executes a module itself:- Registry —
ModulesManagerstores approved module contract addresses. A module is identified by its address; there are no module IDs. - Per-target allowlist — each target contract must explicitly allow a registered module before that module can run on it. The allowlist is keyed
target → module → bool. - Execution on the target — the Vehicle
delegatecalls the module from its ownexecuteModulefunction, so module code runs against the Vehicle’s storage.
addModule and removeModule take effect immediately — the Modules Manager has no timelock. Safety comes from the two-sided opt-in instead: a module must be registered by a MODULE_MANAGER and allowed by the target Vehicle before it can execute.Module lifecycle
Every module goes through two steps:- Registration — an account with
MODULE_MANAGERcallsaddModule(module)on the Modules Manager. The module must be a deployed contract and must not already be registered. - Authorization — the target contract calls
allowModule(module)on the Modules Manager, which records the allowance formsg.sender. For a Vehicle, an account withVEHICLE_ALLOWcallsvehicle.allowModule(module, true)and the Vehicle forwards the call.
Prerequisites
Each role is checked on the External Access Control of the contract that carries the gated function, either globally or scoped to that contract:Add a module
Adding a module is a two-step process: register it globally, then allow it on each Vehicle that should run it.1
Register the module
Call Reverts with
addModule with the module’s contract address.Requires: MODULE_MANAGER role.ExistingModule if the address is already registered, or ZeroCode if it is not a deployed contract.2
Allow the module on target contracts
Each target contract must explicitly authorize the module before it can be executed there. For Vehicles, use the Vehicle’s own Reverts with
allowModule wrapper, which calls the Modules Manager on the Vehicle’s behalf.Requires: VEHICLE_ALLOW role on the Vehicle.ModuleNotFound if the module is not registered, or AlreadyAllowed if the Vehicle already allows it.Execute a module
Once a module is registered and allowed on the target, execute it on the target, not on the Modules Manager. Requires:VEHICLE_EXEC role on the Vehicle.
modulesManager.isRegistered(module) and modulesManager.allowed(address(this), module), then delegatecalls IModule.exec(data) and emits ModuleExecuted(module, data, msg.value). It reverts with ModuleNotFound, ModuleNotAllowed, or MissingModulesManager when the Vehicle has no manager configured.
The target contract must implement the
IModuleTarget interface, whose only function is executeModule(address module, bytes calldata data). All Railnet Vehicles implement it through BaseVehicle. Conduits are not module targets and have no Modules Manager.Replace a module
There is no update function: a module is its address. To swap an implementation, remove the old address and register the new one.Remove a module
Removing a module from the registry stops it from executing anywhere immediately, because every execution re-checksisRegistered.
Requires: MODULE_MANAGER role.
Removal does not clear per-target allowances. Targets that had allowed the module keep a stale allowlist entry and should disallow it explicitly if the module is gone for good.
Disallow a module on a target
Revoke a module’s authorization on a specific target without removing it from the registry. Requires:VEHICLE_ALLOW role on the Vehicle.
AlreadyDisallowed if the Vehicle was not allowing the module.
Next steps
Configure fees
Set up fee structures for your Multi-Vehicle.
Configure rewards
Capture and distribute additional protocol rewards with interceptors.