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_DEPOSIT, aliceAddress);

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

// VEHICLE_STEAM_REDEEM works identically for redeem operations
eac.grantRole(VEHICLE_STEAM_REDEEM, aliceAddress);

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

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

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

// VEHICLE_STEAM_REDEEM works the same way for redeem operations
eac.grantScopedRole(VEHICLE_STEAM_REDEEM, vehicleAddress, aliceAddress);

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

// Now anyone can deposit on that vehicle
bool anyoneHasAccess = eac.hasScopedRole(VEHICLE_STEAM_DEPOSIT, vehicleAddress, bob); // true

// VEHICLE_STEAM_REDEEM can be made public separately for redeem operations
eac.setScopedRolePublic(VEHICLE_STEAM_REDEEM, vehicleAddress, 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
ASSET_REGISTRY_SET_ASSETAuthorize, deauthorize, or reconfigure assets in the AssetRegistry

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_STEAM_DEPOSITInteract with STEAM functions (create, resume, unlock, recover) on deposit queries
VEHICLE_STEAM_REDEEMInteract with STEAM functions (create, resume, unlock, recover) on redeem queries
VEHICLE_SET_INTERCEPTIONSConfigure reward interception rules
VEHICLE_ALLOWManage the allowlist of modules in the vehicle’s ModulesManager
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.

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 roles public for a specific vehicle so any user can interact. Deposits and redeems can be opened independently:
eac.setScopedRolePublic(VEHICLE_STEAM_DEPOSIT, vehicleAddress, true);
eac.setScopedRolePublic(VEHICLE_STEAM_REDEEM, 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);