External Access Control (EAC)
External Access Control (EAC) is Railnet’s central Role-Based Access Control (RBAC) system. Based on OpenZeppelin’s AccessControlDefaultAdminRules, it manages permissions across all vehicles and protocol components, ensuring secure and granular control over system operations.
Role Types
EAC supports three distinct types of roles to provide flexible permission management:
Global Roles
Global roles are standard bytes32 identifiers that apply across the entire protocol. These are managed using the standard grantRole and revokeRole methods. When an account is granted a global role, it holds that permission for all protocol components that check for it.
Scoped Roles
Scoped roles are restricted to a specific contract address (the scope). This allows for fine-grained permissions, such as granting an account the ability to manage a specific vehicle without giving it permissions over all vehicles. Internally, a scoped role is represented as keccak256(abi.encodePacked(role, scope)). These are managed via grantScopedRole and revokeScopedRole.
Public Roles
Public roles are effectively granted to everyone. When a role is made public, hasRole and hasScopedRole checks for that role will always return true, regardless of the account being checked. This is useful for features that should be accessible to any user. Note that the DEFAULT_ADMIN_ROLE cannot be made public.
Admin Management
EAC implements a secure, time-delayed mechanism for transferring the default admin role:
- Initiation:
beginDefaultAdminTransfer(newAdmin)starts the transfer process. - Completion:
acceptDefaultAdminTransfer()completes the transfer. This must be called by the pending admin after the required delay has passed. - Delay Configuration: The delay period can be updated using
changeDefaultAdminDelay(newDelay).
Key API Methods
| Method | Parameters | Description |
|---|---|---|
grantRole | role, account | Grants a global role |
revokeRole | role, account | Revokes a global role |
grantScopedRole | role, scope, account | Grants a role for specific scope |
revokeScopedRole | role, scope, account | Revokes a scoped role |
hasRole | role, account | Checks global role (true if public) |
hasScopedRole | role, scope, account | Checks scoped role (true if public) |
hasRoleOrScopedRole | role, scope, account | Checks either global OR scoped |
setRolePublic | role, isPublic | Makes a global role public |
setScopedRolePublic | role, scope, isPublic | Makes a scoped role public |
FreezablePausableBeacon
The EAC also manages the implementation address for beacon proxies (vehicle clones) through the FreezablePausableBeacon functionality. This component has two critical states:
- Freeze: A permanent and irreversible state. Once frozen, the implementation address can never be upgraded again, providing a “trustless” guarantee to users that the contract logic is immutable.
- Pause: A temporary and reversible state. While paused, the
implementation()method reverts, effectively disabling all proxies that rely on this beacon.
Beacon API
upgradeTo(newImpl): Upgrades the implementation address (requiresBEACON_UPGRADErole).freeze(): Permanently freezes the beacon (requiresBEACON_FREEZErole, irreversible).pause()/pauseFor(period): Pauses beacon operations (requiresBEACON_PAUSErole).unpause(): Resumes paused beacon operations (requiresBEACON_UNPAUSErole).
Complete Roles Reference
Factory Roles
| Role | Description |
|---|---|
FACTORY_SPAWN | Deploy new vehicles via a vehicle factory |
FACTORY_DEPRECATE | Deprecate a factory, blocking further spawns |
FACTORY_SPAWN does not cover conduits. ConduitFactory.spawn checks CONDUIT_SPAWN instead, and holding FACTORY_SPAWN grants nothing there — the
call reverts MissingRole(CONDUIT_SPAWN, <factory>, <caller>).
Beacon & Proxy Roles
| Role | Description |
|---|---|
BEACON_UPGRADE | Upgrade beacon implementation |
BEACON_FREEZE | Permanently freeze beacon (irreversible) |
BEACON_PAUSE | Pause beacon operations |
BEACON_UNPAUSE | Resume paused beacon |
Vehicle Roles
| Role | Description |
|---|---|
VEHICLE_STEAM_DEPOSIT | Use the STEAM deposit functions on a vehicle |
VEHICLE_STEAM_REDEEM | Use the STEAM redeem functions on a vehicle |
VEHICLE_PROCESS_QUEUE | Submit a size-bounded request from a vehicle’s async queues |
VEHICLE_EXEC | Trigger module executions |
VEHICLE_SET_INTERCEPTIONS | Configure reward interception rules |
VEHICLE_ALLOW | Allow/disallow modules on a vehicle |
FeeManager Roles
| Role | Description |
|---|---|
FEE_MANAGER_SET_FEES | Update fee percentages |
FEE_MANAGER_SET_FEE_RECIPIENTS | Update fee recipient addresses |
FEE_MANAGER_DISPATCH_ERC20 | Distribute collected fees |
MultiVehicle Roles
| Role | Description |
|---|---|
MULTI_VEHICLE_SET_VEHICLE_AUTHORIZATION | Authorize/unauthorize sub-vehicles |
MULTI_VEHICLE_MOVE | Move assets or shares between sectors or vehicles |
MULTI_VEHICLE_DISPATCH | Dispatch assets to sub-vehicles |
MULTI_VEHICLE_SET_QUEUES | Configure deposit/redeem queues |
MULTI_VEHICLE_SET_THRESHOLDS | Configure the Multi-Vehicle thresholds |
MULTI_VEHICLE_DEPOSIT | Deposit assets into accounting engine |
MULTI_VEHICLE_PROGRESS_QUERY | Progress queries in the SubQueryEngine |
MULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUE | Feed the query redeem queue |
MULTI_VEHICLE_RETRIEVE_QUERY_REDEEM_QUEUE_ASSETS | Retrieve redeem-queue assets back into the accounting engine |
MULTI_VEHICLE_MOVE_ASSETS and MULTI_VEHICLE_MOVE_SHARES were collapsed into MULTI_VEHICLE_MOVE, and MULTI_VEHICLE_REBALANCE was removed along with the rebalance() entrypoint. Granting a removed name still succeeds and guards
nothing — see the note above.
Conduit Roles
| Role | Description |
|---|---|
CONDUIT_SPAWN | Deploy a new Conduit via ConduitFactory.spawn |
CONDUIT_PROCESS | Advance a Conduit query through the STEAM lifecycle |
CONDUIT_FORCE_REDEEM | Eject a blocked or sanctioned holder |
CONDUIT_SET_INTERCEPTIONS | Configure reward routing on a Conduit |
CONDUIT_SET_TRANSFER_ENABLED | Flip the one-way share-transfer latch |
Modules & Registry Roles
| Role | Description |
|---|---|
MODULE_MANAGER | Add/update/remove modules |
ACCOUNT_LIST_MANAGER | Manage an AccountList’s allow-list, block-list and sanctions oracle |
ASSET_REGISTRY_SET_ASSET | Register an asset and its initial deposit amount |
CANCEL_MODULE and UPDATE_TIMELOCK were listed here previously and do not exist
in Roles.sol. As with the removed MultiVehicle names, granting them succeeds and
guards nothing.
Keeper & Job Roles
| Role | Description |
|---|---|
KEEPER_ON_REPORT | Submit a keeper report |
JOB_LISTING_REGISTER | Register a keeper job |
JOB_LISTING_UNREGISTER | Unregister a keeper job |
JOB_LISTING_EXECUTE | Execute a listed keeper job |
Usage Examples
Granting a Scoped Role
In this example, Alice is granted the VEHICLE_STEAM_DEPOSIT role specifically for a single vehicle address.
// Grant VEHICLE_STEAM_DEPOSIT to Alice for a specific vehicle
eac.grantScopedRole(VEHICLE_STEAM_DEPOSIT, vehicleAddress, aliceAddress); Making a Role Public
In this example, the VEHICLE_STEAM_DEPOSIT role is made public for a specific vehicle, allowing anyone to perform STEAM queries on it.
// Make VEHICLE_STEAM_DEPOSIT public for a specific vehicle
eac.setScopedRolePublic(VEHICLE_STEAM_DEPOSIT, vehicleAddress, true);