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.
// Grant a global role
eac.grantRole(VEHICLE_STEAM, aliceAddress);

// Check a global role
bool hasAccess = eac.hasRole(VEHICLE_STEAM, aliceAddress); // true for all vehicles

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)).
// Grant VEHICLE_STEAM to Alice for a specific vehicle only
eac.grantScopedRole(VEHICLE_STEAM, vehicleAddress, aliceAddress);

// Alice has access to this specific vehicle
bool hasAccess = eac.hasScopedRole(VEHICLE_STEAM, vehicleAddress, aliceAddress); // true

// But NOT to other vehicles
bool hasGlobal = eac.hasRole(VEHICLE_STEAM, aliceAddress); // false

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.
// Make VEHICLE_STEAM public for a specific vehicle
eac.setScopedRolePublic(VEHICLE_STEAM, vehicleAddress, true);

// Now anyone can perform STEAM queries on that vehicle
bool anyoneHasAccess = eac.hasScopedRole(VEHICLE_STEAM, vehicleAddress, bob); // true
The DEFAULT_ADMIN_ROLE cannot be made public.

Checking permissions

EAC provides three methods for checking access:
MethodChecksUse when
hasRole(role, account)Global role only (or public)You need protocol-wide access
hasScopedRole(role, scope, account)Scoped role only (or public for scope)You need access to a specific contract
hasRoleOrScopedRole(role, scope, account)Either global OR scopedMost common — allows both patterns

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 period can be adjusted using changeDefaultAdminDelay(newDelay).

Role reference

Factory roles

RoleDescription
FACTORY_SPAWNDeploy new vehicles and conduits via factory
FACTORY_SET_DEPRECATEDDisable a factory to prevent future deployments

Beacon and proxy roles

RoleDescription
BEACON_UPGRADEUpgrade the implementation address of a beacon
BEACON_FREEZEPermanently freeze a beacon (irreversible)
BEACON_PAUSEPause beacon operations
BEACON_UNPAUSEResume paused beacon operations

Vehicle roles

RoleDescription
VEHICLE_STEAMInteract with STEAM functions: create, resume, unlock, recover
VEHICLE_SET_INTERCEPTIONSConfigure reward interception rules
VEHICLE_ALLOWManage the allowlist of modules in the vehicle’s ModulesManager

FeeManager roles

RoleDescription
FEE_MANAGER_SET_FEESUpdate fee percentages
FEE_MANAGER_SET_FEE_RECIPIENTSUpdate fee recipient addresses
FEE_MANAGER_DISPATCH_ERC20Distribute collected fees
FEE_MANAGER_REDEEM_VEHICLE_SHARESRedeem vehicle shares held as fees

Multi-Vehicle roles

RoleDescription
MULTI_VEHICLE_SET_VEHICLE_AUTHORIZATIONAuthorize or deauthorize sub-vehicles
MULTI_VEHICLE_MOVE_ASSETSMove assets between sectors
MULTI_VEHICLE_MOVE_SHARESMove shares between sectors
MULTI_VEHICLE_DISPATCHDispatch assets to sub-vehicles
MULTI_VEHICLE_SET_QUEUESConfigure deposit and redeem queues
MULTI_VEHICLE_REBALANCERebalance between vehicles
MULTI_VEHICLE_DEPOSITDeposit assets into the accounting engine
MULTI_VEHICLE_PROGRESS_QUERYAdvance sub-query states in the SubQueryEngine
MULTI_VEHICLE_SET_THRESHOLDSConfigure operational thresholds
MULTI_VEHICLE_FEED_QUERY_REDEEM_QUEUEAdd pending queries to the redeem queue
MULTI_VEHICLE_RETRIEVE_QUERY_REDEEM_QUEUE_ASSETSRetrieve assets from the redeem queue

ModulesManager roles

RoleDescription
EXECExecute a module on a target
MODULE_MANAGERAdd, update, or remove modules
CANCEL_MODULECancel a pending module operation during timelock
UPDATE_TIMELOCKUpdate the global timelock duration

Keeper roles

RoleDescription
JOB_LISTING_REGISTERRegister new keeper jobs
JOB_LISTING_UNREGISTERRemove registered keeper jobs
JOB_LISTING_EXECUTEExecute registered jobs
KEEPER_ON_REPORTForward job reports to the Keeper contract

FreezablePausableBeacon

The EAC also manages the implementation address for beacon proxies (vehicle clones) through the FreezablePausableBeacon. This component has two critical 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.
// Permanently freeze the beacon (irreversible)
eac.freeze();
// Requires BEACON_FREEZE role

Common permission patterns

Use scoped roles to restrict the operator to a single Multi-Vehicle:
eac.grantScopedRole(MULTI_VEHICLE_DISPATCH, multiVehicleAddress, operatorAddress);
eac.grantScopedRole(MULTI_VEHICLE_SET_QUEUES, multiVehicleAddress, operatorAddress);
eac.grantScopedRole(MULTI_VEHICLE_REBALANCE, multiVehicleAddress, operatorAddress);
Make the STEAM role public for a specific vehicle so any user can interact:
eac.setScopedRolePublic(VEHICLE_STEAM, vehicleAddress, true);
Grant the roles needed to manage and distribute fees:
eac.grantScopedRole(FEE_MANAGER_SET_FEES, feeManagerAddress, operatorAddress);
eac.grantScopedRole(FEE_MANAGER_SET_FEE_RECIPIENTS, feeManagerAddress, operatorAddress);
eac.grantScopedRole(FEE_MANAGER_DISPATCH_ERC20, feeManagerAddress, operatorAddress);
eac.grantScopedRole(FEE_MANAGER_REDEEM_VEHICLE_SHARES, feeManagerAddress, operatorAddress);