Skip to main content
This page covers the smart contract implementation details. See Glossary.
External Access Control (EAC) is Railnet’s central permission system. Based on OpenZeppelin’s AccessControlDefaultAdminRules, it manages permissions across all vehicles and protocol components with granular, auditable control.

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. When you grant an account 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 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)).

Public roles

Public roles are effectively granted to everyone. When you make a role public, hasRole and hasScopedRole checks for that role return true for any account.
The DEFAULT_ADMIN_ROLE cannot be made public. While a role is public, grantRole, revokeRole, renounceRole, grantScopedRole, revokeScopedRole, and renounceScopedRole all revert with PublicRoleAuthDenied for it — make it private again before managing individual holders.

Checking permissions

EAC provides three methods for checking access: Contracts do not call these views directly. They route through AccessControlLib, which wraps hasRoleOrScopedRole in two flavours:
  • gatedCheckRole(role, scope, account) is fail-closed: it reverts ZeroAddress when the contract has no access control set, then reverts MissingRole unless the account holds the role globally or scoped. Every operator-facing function uses this variant.
  • ungatedCheckRole(role, account) is fail-open: an unset access control means access control is disabled and the call passes. Vehicles reach it through _onlyRoleWhenEnabled, which additionally skips the check entirely until the Vehicle is enabled, so the factory can seed the initial deposit before roles exist.

Admin management

EAC implements a secure, time-delayed mechanism for transferring the default admin role:
1

Initiate transfer

The current admin calls beginDefaultAdminTransfer(newAdmin) to start the transfer process.
2

Wait for delay

A configurable time delay must pass before the transfer can complete.
3

Accept transfer

The pending admin calls acceptDefaultAdminTransfer() to complete the transfer.
The delay is the initialDelay (in seconds) passed to the constructor, and is changed later with changeDefaultAdminDelay(uint48 newDelay) — which is itself scheduled rather than immediate.
The default admin role can never be dropped: beginDefaultAdminTransfer(address(0)) and renounceRole(DEFAULT_ADMIN_ROLE, ...) both revert with DefaultAdminCannotBeRenounced.

Role reference

Factory roles

Beacon and proxy roles

Vehicle roles

STEAM authorization is split across deposit and redeem so operators can gate each direction independently — for example, keeping deposits open while pausing redemptions during a strategy wind-down, or restricting deposits to KYC’d addresses while exits remain public.

Conduit roles

FeeManager roles

Multi-Vehicle roles

ModulesManager roles

Module registration and removal take effect immediately; there is no module timelock and no cancellation role. Authorizing a registered module on a specific target is a separate step gated by that target’s VEHICLE_ALLOW, and executing it is gated by VEHICLE_EXEC.

Keeper roles

FreezablePausableBeacon

Vehicle and Conduit proxies read their implementation address from a FreezablePausableBeacon. The beacon holds the implementation; the EAC gates who may upgrade, freeze, pause, or unpause it. The beacon has two protective states:
A permanent and irreversible state. Once frozen, the implementation address can never be upgraded again. This provides a “trustless” guarantee that the contract logic is immutable.

Common permission patterns

Use scoped roles to restrict the operator to a single Multi-Vehicle. Scope each role to the contract that carries the gated function, not to the Multi-Vehicle itself: MULTI_VEHICLE_DISPATCH is gated on the Sector Accounting Engine, while MULTI_VEHICLE_SET_QUEUES is gated on the Queue Strategy Engine.
Make the STEAM roles public for a specific vehicle so any user can interact. Deposits and redeems can be opened independently:
Grant the roles needed to manage and distribute fees: