In Railnet Advanced Strategies, the policy engine is a Zodiac Roles Modifier v2 contract enabled as a module on the Strategy. Every rule is transparent, auditable, and verifiable on-chain.
How it works
An operator (the “member signer”) never calls the Strategy directly. Every transaction routes through the policy engine, which checks the operator’s role and validates every parameter before forwarding the call. The operator builds their protocol calldata as usual (e.g., an Aavesupply call), wraps it in a single execTransactionWithRole call, and sends it to the policy engine address with the associated role key. The policy engine validates the call against the operator’s role permissions. If all checks pass, the policy engine forwards the call through the Strategy, which executes it on the target protocol.
From the protocol’s perspective, msg.sender == STRATEGY_ADDRESS. The target contract sees no difference between a call from the policy engine and a direct call from the Safe. The operator’s signer address is never visible to the target protocol.
Permission layers
The policy engine enforces four layers of constraints, each narrowing what an operator can do. Every layer is configured on-chain and readable by anyone.Contract whitelists
The first layer controls which contracts the operator can interact with. Only explicitly whitelisted target addresses are allowed — any call to a non-whitelisted contract reverts. For example, an operator managing a lending strategy might have three whitelisted targets: the Aave V3 Pool, the USDC token contract (for approvals), and the Morpho Blue contract. Any attempt to call a contract outside this set fails at the policy engine before reaching the Strategy.Function-level permissions
The second layer controls which functions on whitelisted contracts the operator can call. Each function is identified by its 4-byte selector. Functions are categorized by risk level, which determines their default state:| Risk level | Default state | Examples |
|---|---|---|
| Low | Enabled | withdraw, repay, claimWithdrawals |
| Medium | Enabled (with spending limits) | supply, borrow, swap |
| High | Disabled | liquidate, absorb, admin functions |
withdraw and repay are enabled by default because they move funds back to the Strategy. Medium-risk functions like supply and borrow are enabled but prompt for spending limits. High-risk functions like liquidate and admin functions are disabled by default and require explicit opt-in.
Calldata-level checks
The third layer inspects the actual parameter values in each function call. The policy engine defines condition trees using these operators:| Operator | What it checks | Example use |
|---|---|---|
EqualTo | Parameter must match an exact value | Restrict to specific tokens or pool IDs |
EqualToAvatar | Parameter must equal the Strategy address | Lock recipients so funds cannot leave the Strategy |
GreaterThan | Parameter must exceed a threshold | Enforce minimum slippage protection (amountOutMinimum > 0) |
LessThan | Parameter must be below a threshold | Cap individual transaction sizes |
WithinAllowance | Parameter must fit within a spending budget | Rate-limit operations with periodic refills |
Recipient locking
Recipient locking
The most critical constraint. Any parameter named
to, recipient, receiver, onBehalfOf, or owner is constrained to EqualToAvatar — meaning it must equal the Strategy’s own address. This prevents the operator from sending funds to any external address.This constraint is non-overridable. It applies automatically to every function that has a recipient-like parameter, and the operator cannot remove it.Token restrictions
Token restrictions
The
asset or tokenIn parameter is constrained to a specific set of approved tokens using EqualTo. The operator can only interact with tokens explicitly listed in the policy — for example, USDC and WETH but not any arbitrary ERC-20.Slippage protection
Slippage protection
Parameters like
amountOutMinimum are constrained with GreaterThan(0) to prevent zero-slippage swaps that could be exploited by MEV bots. The operator can set a higher minimum, but cannot set it to zero.Non-overridable constraints
Three calldata constraints are safety-critical and cannot be removed by anyone, including the guardian:- Recipient = Strategy address — prevents funds from being sent to external addresses
- Approve spender = known protocol contract — prevents arbitrary token approvals
- delegatecall = false — always disabled on all permissions, preventing code injection
Spending limits and allowances
The fourth layer rate-limits how much capital the operator can deploy within a time period. Spending limits use theWithinAllowance operator with a periodic refill mechanism.
Each allowance is defined by:
- Refill amount — how much budget is restored each period (e.g., 100,000 USDC)
- Period — how often the budget refills (daily, weekly, or monthly)
- Max accrual — the maximum budget that can accumulate (prevents rollover)
| Scope | Allowance key example | Effect |
|---|---|---|
| Per-protocol | aave_v3_daily | Caps all Aave V3 operations combined |
| Per-route | eth_arb_usdc_daily | Caps a specific bridge route |
| Global outbound | eth_outbound_usdc_daily | Caps all outbound operations from one chain |
Cross-chain policies
Advanced Strategies can operate across multiple EVM chains using the same Safe address, deployed deterministically via CREATE2. Each chain has its own Roles Modifier instance with its own set of permissions.How cross-chain scoping works
The Roles Modifier on each chain controls outbound operations only — bridge calls, token transfers, and cross-chain messages originating from that chain’s Safe. Inbound bridged funds are permissionless. Anyone can send tokens to the Safe address, and the funds arrive without any Roles configuration. For bridge operations, the policy engine enforces:- Bridge contract whitelist — only approved bridge contracts (e.g., Across SpokePool) can be called
- Recipient locked to Strategy address — the
recipientparameter in bridge calls must equal the Safe address, which is the same on all chains due to deterministic deployment - Approved destination chains — the operator can only bridge to explicitly allowed chain IDs
- Per-route spending caps — each source-destination pair has its own daily allowance (e.g.,
eth_arb_usdc_daily)
Per-chain, per-route spending caps
Each bridge route has an independent allowance. An operator bridging from Ethereum can have separate daily caps for Ethereum-to-Arbitrum, Ethereum-to-Base, and Ethereum-to-Optimism routes. For stricter control, routes from the same source chain can share a single allowance key. This enforces a global outbound cap — the total bridged across all destinations from one chain cannot exceed the shared limit.Comparing guardrail models
Railnet offers two guardrail architectures. Allocation Strategies use the External Access Control (EAC) system built into Railnet’s smart contracts. Advanced Strategies use the Zodiac Roles Modifier as an external policy engine.| Aspect | Advanced Strategy | Allocation Strategy |
|---|---|---|
| Engine | Zodiac Roles Modifier v2 | External Access Control (EAC) |
| Scope | Any contract call on any chain | Railnet operational roles |
| Granularity | Calldata-level parameter checks | Role-based access control |
| Spending limits | Per-function allowances with period refill | Allocation queue targets |
| Governance | Guardian timelock | Admin role retention |
| Visibility | All rules on-chain, verifiable | All roles on-chain, verifiable |
What to read next
Operate an Advanced Strategy
Integration guide for routing transactions through the policy engine.
Advanced Strategies overview
What Advanced Strategies are, why they exist, and how they connect to Railnet.
Allocation Strategy guardrails
Role-based access control for standard Allocation Strategies.
Risk management
Portfolio-level risk parameters, circuit breakers, and monitoring.