Language Reference
Complete guide to Rego syntax and language features for writing policies
Policies are written in Rego, a declarative policy language designed for expressing complex access control decisions. Unlike imperative languages where you describe how to compute a result, Rego lets you describe what conditions should trigger a policy decision. This page covers all supported syntax and language features supported in 256 Blocks.
Policy Structure
Every policy you write is automatically wrapped with the necessary boilerplate by 256 Blocks. This means you can focus purely on writing your rules without worrying about package declarations or default values.
All policies:
- Are automatically wrapped with the necessary
packagestatement - Have a preset
default deny := false(allow by default) - Have a preset
default denyGasSponsor := false(sponsor by default) - Can only define rules, not override defaults
Basic Rule Syntax
Rules are the building blocks of policies. A rule consists of a head (the rule name, eg: deny or denyGasSponsor) and a body (the conditions). When all conditions in the body are true, the rule evaluates to true.
Example
# Basic structure: rule_name if { conditions }
deny if {
input.usd_value > 10000
}Multiple Conditions (AND Logic)
When you include multiple conditions in the same rule body, ALL conditions must be true for the rule to trigger. Each condition is implicitly joined with AND.
Example
# Both conditions must be true to deny
deny if {
input.chain == "ethereum"
input.usd_value > 1000
}Multiple Rules (OR Logic)
When you define multiple rules with the same name, ANY matching rule will trigger the policy. This provides OR logic between rules.
Example
# Either condition triggers denial
deny if {
input.chain == "ethereum"
}
deny if {
input.usd_value > 10000
}Combining AND and OR
You can combine both patterns for complex logic. Each rule body uses AND logic internally, while multiple rules provide OR logic between them.
Example
# Deny if: (ethereum AND high-value) OR (any chain AND blocked country)
deny if {
input.chain == "ethereum"
input.usd_value > 5000
}
deny if {
input.source_country in {"KP", "IR", "CU"}
}Variable Assignment
Use the := operator to assign values to variables. Variables make your policies more readable and maintainable by giving meaningful names to values and enabling reuse.
Policy-Level Variables
Variables defined outside rule bodies act as constants that can be referenced by multiple rules. Use these for thresholds, allowlists, and blocklists.
Example
# Policy-level constants - defined once, used everywhere
max_transaction_value := 50000
blocked_countries := {"KP", "IR", "CU", "SY"}
approved_senders := {"0x742d35cc...", "0xa0b86991..."}
deny if {
input.usd_value > max_transaction_value
}
deny if {
input.source_country in blocked_countries
}
deny if {
not input.from_address in approved_senders
}Local Variables
Variables defined inside a rule body are local to that rule. Use these for intermediate calculations or to improve readability.
Example
deny if {
# Calculate with a safety margin
value_with_buffer := input.usd_value * 1.15
value_with_buffer > 10000
}
deny if {
# Estimate gas cost in wei
gas_cost := to_number(input.gas_limit) * to_number(input.max_fee_per_gas)
gas_cost > 1000000000000000000 # 1 ETH in wei
}Comparison Operators
Rego provides standard comparison operators for evaluating conditions. These work with strings, numbers, and other comparable types.
| Operator | Description | Example |
|---|---|---|
== | Equal to | input.chain == "ethereum" |
!= | Not equal to | input.chain != "polygon" |
> | Greater than | input.usd_value > 1000 |
>= | Greater than or equal | input.usd_value >= 1000 |
< | Less than | input.usd_value < 100 |
<= | Less than or equal | input.usd_value <= 100 |
Example
# Block high-value transactions on expensive chains
deny if {
input.chain == "ethereum"
input.usd_value >= 5000
}
# Block transactions below a minimum value (potential spam)
deny if {
input.usd_value != null
input.usd_value < 1
}Arithmetic Operators
You can perform arithmetic calculations within your policy conditions. This is useful for computing thresholds, applying multipliers, or combining values.
| Operator | Description | Example |
|---|---|---|
+ | Addition | input.usd_value + 100 |
- | Subtraction | input.usd_value - fees |
* | Multiplication | input.usd_value * 1.1 |
/ | Division | input.usd_value / 2 |
% | Modulo (remainder) | input.gas_limit % 1000 |
Example
# Apply a 10% buffer when checking limits
deny if {
input.usd_value * 1.1 > 10000
}
# Block if combined value exceeds threshold
deny if {
total := input.usd_value + 500 # Add estimated fees
total > 5000
}Logical Operators
Beyond the implicit AND (multiple conditions) and OR (multiple rules), Rego provides explicit logical operators for more complex expressions.
The not Operator
Use not to negate a condition. This is particularly useful for allowlist patterns where you want to deny anything NOT in a permitted set.
Example
# Only allow specific chains (deny everything else)
allowed_chains := {"polygon", "base", "arbitrum"}
deny if {
not input.chain in allowed_chains
}Example: Combining with Other Conditions
# Deny if NOT a read-only method AND value is high
deny if {
not input.rpc_method in {"eth_call", "eth_getBalance"}
input.usd_value > 1000
}Helper Rules
Beyond deny and denyGasSponsor, you can define your own custom rules to organize complex logic. Helper rules act as reusable building blocks that make your policies more readable and maintainable.
A helper rule is simply a named condition that evaluates to true when its body matches. You can then reference these rules in your deny or denyGasSponsor rules, including with the not operator to negate them.
Example
# Define a helper rule to check if request is from a trusted source
is_trusted if {
input.source_country in {"US", "GB", "DE"}
input.source_ip in {"203.0.113.10", "203.0.113.11"}
}
# Define another helper for high-risk transactions
is_high_risk if {
input.usd_value > 10000
}
is_high_risk if {
input.source_country in {"RU", "CN"}
}
# Use helper rules in deny conditions
deny if {
not is_trusted
is_high_risk
}Helper rules are particularly useful for:
- Reusability: Define a condition once, use it in multiple rules
- Readability: Give meaningful names to complex conditions
- Negation: Use
notto check when a condition is NOT met - Organization: Break down complex policies into logical components
String Operations
Strings are sequences of characters enclosed in double quotes (""). Most input fields like input.chain, input.rpc_method, and addresses are strings. Rego provides several built-in functions for working with strings, essential for pattern matching on addresses, method names, and other text fields.
| Function | Description | Returns |
|---|---|---|
contains(string, substring) | Check if string contains substring | boolean |
startswith(string, prefix) | Check if string starts with prefix | boolean |
endswith(string, suffix) | Check if string ends with suffix | boolean |
lower(string) | Convert to lowercase | string |
upper(string) | Convert to uppercase | string |
substring(string, start, length) | Extract portion of string | string |
sprintf(format, values) | Format string with values | string |
Example
# Block any method containing "sign" (covers eth_sign, personal_sign, etc.)
deny if {
contains(input.rpc_method, "sign")
}
# Block addresses starting with known malicious prefix
deny if {
startswith(lower(input.to_address), "0x000000000000000000000000000000000000dead")
}
# Block debug and admin methods
deny if {
startswith(input.rpc_method, "debug_")
}
deny if {
startswith(input.rpc_method, "admin_")
}Array Operations
Arrays are ordered lists of values, defined with square brackets []. Unlike sets, arrays preserve the order of elements and can contain duplicates. You'll frequently work with arrays in policies, especially input.contract_addresses which contains all contract addresses involved in a request.
# Array syntax
my_array := ["first", "second", "third"]Rego provides several ways to inspect and work with arrays.
| Function | Description | Returns |
|---|---|---|
count(array) | Number of elements | number |
array[index] | Access element by index (0-based) | element |
value in array | Check if value exists in array | boolean |
Example
# Limit the number of contracts in a single request
deny if {
count(input.contract_addresses) > 10
}
# Check if a specific address is in the contract list
deny if {
"0xbanned..." in input.contract_addresses
}
# Access specific array elements
deny if {
count(input.contract_addresses) > 0
first_contract := input.contract_addresses[0]
startswith(first_contract, "0xdead")
}Membership Testing
Membership testing is one of the most common operations in policies. You'll use it to check if values belong to allowlists, blocklists, or other collections of permitted values.
Sets
Sets are unordered collections of unique values, defined with curly braces {}. Use the in operator to test if a value exists in a set.
# Set syntax
my_set := {"value1", "value2", "value3"}Sets are optimized for fast membership testing, making them ideal for allowlists and blocklists.
Example
# Block requests from sanctioned countries
blocked_countries := {"KP", "IR", "CU", "SY", "RU"}
deny if {
input.source_country in blocked_countries
}
# Only allow specific chains
allowed_chains := {"polygon", "base", "arbitrum"}
deny if {
not input.chain in allowed_chains
}Inline vs Named Sets
You can define sets inline within rules or as named variables at the policy level. Named sets improve readability and allow reuse across multiple rules.
Example
# Inline set - good for simple, one-off checks
deny if {
input.rpc_method in {"eth_sign", "personal_sign", "eth_signTypedData"}
}
# Named set - better for reusability and readability
approved_contracts := {
"0xdac17f958d2ee523a2206206994597c13d831ec7", # USDT
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", # USDC
"0x6b175474e89094c44da98b954eedeac495271d0f" # DAI
}
deny if {
some addr in input.contract_addresses
not addr in approved_contracts
}Iteration with some
The some keyword lets you iterate over collections and check conditions against each element. This is essential when working with arrays like input.contract_addresses.
Basic Iteration
Use some to check if ANY element in a collection matches a condition.
Example
# Deny if ANY contract address is in the blocklist
blocked_contracts := {"0xbanned1...", "0xbanned2..."}
deny if {
some addr in input.contract_addresses
addr in blocked_contracts
}Iteration with Index
You can also access the index of each element during iteration.
Example
# Check conditions on specific positions
deny if {
some i, addr in input.contract_addresses
i == 0 # First contract only
startswith(addr, "0x000")
}Example: Finding Any Match
# Deny if any contract starts with a suspicious prefix
deny if {
some addr in input.contract_addresses
startswith(lower(addr), "0x000000000000000000000000")
}
# Deny if any contract is not in the approved list
approved := {"0xusdt...", "0xusdc...", "0xdai..."}
deny if {
some addr in input.contract_addresses
not addr in approved
}Iteration with every
While some checks if ANY element matches, every requires that ALL elements match a condition. This is useful for ensuring entire collections meet your criteria.
Example
# Deny only if ALL contracts are from a suspicious set
# (unlikely to be legitimate if every address is suspicious)
suspicious_prefixes := {"0x0000000000000000"}
deny if {
count(input.contract_addresses) > 0
every addr in input.contract_addresses {
some prefix in suspicious_prefixes
startswith(addr, prefix)
}
}Example: Validation Patterns
# Ensure all contracts are from approved list (inverse of some/not)
approved_contracts := {"0xusdt...", "0xusdc...", "0xdai..."}
# This denies if ANY contract is not approved
deny if {
some addr in input.contract_addresses
not addr in approved_contracts
}
# Equivalent using every (deny if NOT every contract is approved)
deny if {
count(input.contract_addresses) > 0
not every addr in input.contract_addresses {
addr in approved_contracts
}
}Comprehensions
Comprehensions let you create new collections by transforming or filtering existing ones. They're useful for extracting specific values or building derived datasets.
Array Comprehension
Create a new array from elements that match certain criteria.
Example
# Extract all contract addresses that start with a specific prefix
matching_contracts := [addr |
some addr in input.contract_addresses
startswith(addr, "0xa")
]
deny if {
count(matching_contracts) > 3
}Set Comprehension
Create a set of unique values.
Example
# Get unique prefixes from all contract addresses
contract_prefixes := {substring(addr, 0, 6) |
some addr in input.contract_addresses
}
# Deny if there are too many different contract prefixes (suspicious)
deny if {
count(contract_prefixes) > 5
}Conditional Else
The else keyword lets you define fallback values when conditions aren't met. This is useful for computing derived values that vary based on input.
Example
# Assign risk level based on transaction value
risk_level := "critical" if {
input.usd_value > 100000
} else := "high" if {
input.usd_value > 10000
} else := "medium" if {
input.usd_value > 1000
} else := "low"
# Use the computed risk level in deny rules
deny if {
risk_level == "critical"
}
denyGasSponsor if {
risk_level in {"high", "critical"}
}Example: Chain-Specific Thresholds
# Different limits for different chains
chain_limit := 1000 if {
input.chain == "ethereum"
} else := 5000 if {
input.chain == "polygon"
} else := 10000
deny if {
input.usd_value > chain_limit
}