XRC‑137 ValidationGas Specification (Off‑Chain Cost Model)
This document specifies how ValidationGas is computed for an XRC‑137 rule and how list comprehensions
(e.g., map, filter, exists, all) are priced deterministically using n·m + overhead.
ValidationGas is a billing and resource model for XDaLa off‑chain processing. It is not EVM gas and does not affect on-chain transaction execution.
1. What ValidationGas represents
ValidationGas approximates the off‑chain work performed by the engine:
- parsing and evaluating rule expressions (CEL)
- contract reads and save handling
- API calls and response extraction (CEL on
resp) - building outcome payloads
- resolving execution parameters (address, args, value)
- optional encryption/log logging overhead
- wait-time related overhead for spawned workflows (join/wait semantics)
The objective is: - predictable customer pricing - deterministic, explainable computation - alignment with hard caps (so estimated work ≈ permitted work)
2. Key terms
Common gas vs branch gas
ValidationGas is split into:
- Common: paid regardless of branch outcome (payload + rules + reads + API calls + base).
- Branch extras:
- onValid extra
- onInvalid extra
Branch extras cover outcome payload mapping, execution resolution, encryption/logs, and wait-time cost.
Operators, functions, placeholders
Expressions are scored into counts:
- Operators (ops): CEL operator functions (e.g.,
>=,&&,+). - Functions (funcs): CEL function calls (including helper functions and comprehension/macros).
- Placeholders (ph): occurrences of
[Identifier](only true placeholders; not[0],["k"], …). - Regex usage: detected via
matches(...)and surcharged.
These counts are multiplied by fixed constants (documented below).
Comprehension
A comprehension is CEL’s internal representation of map, filter, exists, all and similar list macros.
Comprehensions can execute work proportional to list length; therefore we price them explicitly as n·m + overhead.
3. Fixed constants (protocol values)
All constants below are fixed protocol parameters used by the estimator.
3.1 Base and payload inputs
| Constant | Value | Meaning |
|---|---|---|
gBase |
10,000 | Fixed base cost per rule evaluation (framework overhead). |
gPerRequiredInput |
1,000 | Cost per payload field without a default (must be provided). |
gPerOptionalInput |
200 | Cost per payload field with a default (engine can proceed without caller value). |
Note: The term “optional” here means “defaulted input”. There is no separate optional: true/false flag.
3.2 Rule expressions (rules[])
| Constant | Value | Meaning |
|---|---|---|
gPerRuleBase |
1,200 | Base cost per rule expression entry. |
gPerOp |
600 | Cost per operator occurrence. |
gPerFunc |
800 | Cost per function call occurrence. |
gPerPlaceholder |
250 | Cost per placeholder [Key] occurrence. |
gRegexSurcharge |
4,000 | Additional cost if matches(...) is used (regex). |
3.3 Contract reads
| Constant | Value | Meaning |
|---|---|---|
gPerReadBase |
6,000 | Base cost per contract read. |
gPerReadArg |
600 | Cost per read argument. |
gPerReadSave |
400 | Cost per saved output field. |
gPerReadDefault |
250 | Extra cost when a saved field defines a default (fallback value handling). |
3.4 API calls and extraction
| Constant | Value | Meaning |
|---|---|---|
gPerAPICallBase |
8,000 | Base cost per API call. |
gPerAPIPlaceholder |
200 | Cost per placeholder occurrence in API templates (URL/body), if used. |
gPerAPIExtract |
600 | Base cost per extract-map entry. |
gPerAPIExtractOp |
500 | Cost per operator occurrence inside an extract expression. |
gPerAPIExtractFunc |
400 | Cost per function call inside an extract expression. |
gAPIMatchesSurcharge |
4,000 | Additional cost if matches(...) is used inside extraction. |
3.5 Outcomes and execution resolution (branch extras)
| Constant | Value | Meaning |
|---|---|---|
gPerOutcomeKey |
400 | Base cost per outcome payload key. |
gPerOutcomeExpr |
600 | Extra cost if the value is an expression (not a pure template). |
gPerExecBase |
1,200 | Base cost when an execution block exists (address/ABI setup). |
gPerExecArg |
700 | Base cost per execution argument (regardless of expression complexity). |
gPerExecValue |
800 | Base cost for evaluating and converting execution.value. |
gPerEncryptLogs |
2,000 | Extra cost if encrypt/logging is enabled for the branch. |
3.6 Wait-time surcharge for spawns
| Constant | Value | Meaning |
|---|---|---|
gWaitGasPerHourPerSpawn |
100 | Cost per started hour of wait time, per spawned child. |
Wait-time cost uses the formula:
- WaitGas = ceil(waitSeconds / 3600) · gWaitGasPerHourPerSpawn · spawnCount
4. Overall calculation (high level)
ValidationGas is computed as:
Common
- gBase
- payload fields (required/defaulted)
- rule expressions
- contract reads
- API calls + extraction
Branch extras (onValid / onInvalid) - outcome payload mapping (keys + placeholders + expression costs + regex surcharge) - execution resolution (base + args + arg expressions + value expression) - encryption/log overhead (if enabled) - wait-time surcharge (if configured and spawns exist)
The final per-branch cost is typically reported as:
- common
- common + onValidExtra
- common + onInvalidExtra
5. Expression scoring and comprehension pricing
5.1 Placeholder count
Placeholders are counted strictly as occurrences of [Identifier].
[A_out]counts as 1 placeholder.[0]counts as 0 placeholders (indexing).["key"]counts as 0 placeholders.
5.2 Operator/function counting
For valid CEL, the estimator parses + type-checks and then counts: - operator calls (internal CEL operator functions) - function calls (including helpers)
If parsing/checking fails, the estimator falls back to a conservative token heuristic so the estimator can still return a number. Runtime evaluation may still abort for invalid expressions; therefore valid CEL is required for production use.
5.3 Comprehension pricing: n·m + overhead
Comprehensions are priced as:
- n: the list size
- if the iterated range is a list literal,
n = literal length - otherwise (unknown/dynamic),
n = MaxListCap = 64 - m: the cost of the comprehension body
- cost of
loopCondition + loopStep(operators + functions) inside the loop - overhead: a fixed surcharge for the comprehension itself
In the estimator, the overhead is modeled as one additional function call.
Therefore, the comprehension contributes:
cost(iterRange) + overhead + n · cost(loopCondition + loopStep) + cost(result)
This implements the required n·m + overhead rule.
6. Worked examples for comprehension pricing
The following examples use the constants:
- Rule ops cost:
gPerOp = 600 - Rule funcs cost:
gPerFunc = 800 - MaxListCap:
64
Example A: fixed list literal
Expression (rule context):
[1, 2, 3].map(x, x + 1)
- iterRange is a list literal of length 3 →
n = 3 - body contains one operator
+→m_ops = 1,m_funcs = 0 - overhead = 1 function call (the comprehension itself)
Loop contribution:
- per-iteration: 1·gPerOp = 600
- total loop: n·600 = 3·600 = 1,800
- overhead: 1·gPerFunc = 800
So the comprehension contributes at least:
- 1,800 (loop) + 800 (overhead) plus any additional costs from surrounding calls.
Example B: dynamic list (capped)
Expression (API extract context):
resp.items.filter(i, bool(i.active))
- iterRange is
resp.items(dynamic) →n = MaxListCap = 64 - body includes one cast
bool(...)→ counted as a function - overhead = 1 function call
Body cost:
- m_funcs = 1 → m = 1·gPerAPIExtractFunc
Total loop cost scales as:
- 64 · (1·gPerAPIExtractFunc) plus overhead.
This is intentionally conservative: even if resp.items has only 5 entries, the estimator assumes the capped
maximum for billing and DoS safety unless the list size is statically known.
Example C: nested comprehensions
Expression:
resp.items.filter(i, i.tags.exists(t, t == "x"))
- outer loop:
n_outer = 64 - inner
exists(...)is itself a comprehension inside the outer body - the inner comprehension is priced using the same rule, so the body cost
malready includes a scaled component if the inner iterRange is dynamic
This yields multiplicative growth in the estimate, as intended: nested list processing is expensive.
7. Regex surcharges
Regex matching via matches(...) is surcharged because it is significantly more expensive than simple operators:
- Rule contexts:
+ gRegexSurcharge - API extract contexts:
+ gAPIMatchesSurcharge
Surcharges apply once per expression where regex is detected.
8. Wait-time surcharge examples
If a branch specifies:
- waitSec = 4,500 seconds (1.25 hours)
- spawnCount = 3
Then:
- ceil(4500 / 3600) = 2
- WaitGas = 2 · 100 · 3 = 600
This surcharge is added to the corresponding branch extra (valid/invalid) depending on where the wait is defined.
9. Alignment with deterministic hard caps
The runtime evaluator enforces hard caps: - MaxExprLen = 1024 - MaxAstNodes = 4096 - MaxListCap = 64
The ValidationGas estimator uses the same list cap value for comprehension pricing in the worst case. This keeps estimation and enforcement aligned: customers do not get priced for work that the engine would refuse.
10. Practical guidance
- Prefer producing intermediate values (payload/API/reads) and use simple rule comparisons.
- Avoid nested comprehensions unless absolutely necessary.
- Use explicit casts (
int64,double,bool) in API extracts to avoid overload errors. - Treat ValidationGas as a customer-visible contract: keep expressions stable and explainable.