Crypto Training
Mellow Verifier vs Zodiac Roles Modifier: Curator Calldata Checks
A code-level comparison of how Mellow Flexible Vaults and Zodiac Roles Modifier authorize curator transactions: allowlists, calldata constraints, allowances, adapters, and failure semantics.
When people compare these systems, they often mix up two different questions:
- who is allowed to act
- what exact calldata is allowed to pass
This article focuses on the second question: curator calldata checks.
Code references:
- Mellow Flexible Vaults (
src): https://github.com/mellow-finance/flexible-vaults/tree/main/src - Zodiac Roles Modifier (
packages/evm/contracts): https://github.com/gnosisguild/zodiac-modifier-roles/tree/main/packages/evm/contracts
Scope and mental model#
Both systems gate outbound calls to external protocols, but they sit at different layers.
- Mellow checks happen in a Subvault call gateway (
CallModule+Verifier) inside a vault architecture. - Roles checks happen in a Safe modifier (
Roles) between Safe modules and the Safe execution target.
1) Entry points and where checks are enforced#
Mellow#
SubvaultcomposesCallModuleandVerifierModule:Subvault.sol- External call entrypoint is
CallModule.call(where, value, data, payload):CallModule.sol CallModule.callinvokesverifier().verifyCall(...)beforeAddress.functionCallWithValue(...).
Roles Modifier#
- Main contract:
Roles.sol - Main execution APIs:
execTransactionFromModule,execTransactionWithRole, and...ReturnDatavariants. - Authorization logic starts in
_authorize(...)fromPermissionChecker.sol.
2) Mellow verifier semantics#
Mellow verifier logic is in Verifier.sol.
Role gate first#
getVerificationResult(...) starts by requiring vault().hasRole(CALLER_ROLE, who).
Verification types#
ONCHAIN_COMPACT: checks(who, where, selector)in on-chain set.MERKLE_COMPACT: merkle-proved compact call hash.MERKLE_EXTENDED: merkle-proved exact(who, where, value, full data)hash.CUSTOM_VERIFIER: merkle-proved pointer to custom verifier + custom payload.
Practical implications#
- Mellow can do exact full-calldata pinning cheaply with a merkle root update workflow.
- Runtime numeric bounds (
amount <= x) are not native unless encoded via custom verifier logic.
3) Roles Modifier semantics#
Core logic is split across:
Role membership and target/function clearance#
_authorize(...) checks:
- role key is non-zero
- caller has role membership
- if a transaction unwrapper is configured, unwrap then check each child tx
- per tx, enforce
Clearance.TargetorClearance.Function - if function-scoped and not wildcarded, evaluate condition tree
Condition tree engine#
Roles supports rich operators and ABI-aware decoding:
- logical:
And,Or,Nor - structure-aware:
Matches,ArraySome,ArrayEvery,ArraySubset - comparisons:
EqualTo,GreaterThan,LessThan, signed variants - bit-level:
Bitmask - quota:
WithinAllowance,EtherWithinAllowance,CallWithinAllowance - extension:
Custom
Integrity enforcement at config time#
When calling scopeFunction(...), Integrity.enforce(conditions) validates tree shape, BFS ordering, operator/type compatibility, and root suitability.
That means malformed condition trees are rejected at permission-write time, not at first execution.
4) Storage model differences#
Mellow#
- compact allowlist entries: on-chain set of
bytes32hashes - merkle mode: one root + proofs supplied per call
- custom mode: verifier address comes via merkle-proved payload
Roles#
- scoped function condition tree is packed and stored through
WriteOncepointers - header in storage references packed body pointer
- on execution, loader reconstructs condition tree and allowance references
5) Allowance and quota semantics#
This is a major differentiator.
Roles has first-class allowance consumption with refill rules in AllowanceTracker.sol, and it uses a two-phase commit model:
_flushPrepare(consumptions)before execution_flushCommit(consumptions, success)after execution- failed execution restores balances
Mellow verifier path has no native equivalent for allowance/rate tracking.
6) Multi-call and bundled transaction handling#
Roles has native adapter hooks (setTransactionUnwrapper) via _Periphery.sol, allowing policy checks over decomposed subcalls.
Examples:
Mellow handles one outward call(...) at a time and leaves higher-level batching semantics to external tooling or target contract behavior.
7) Side-by-side comparison for curator calldata checks#
| Dimension | Mellow Verifier | Zodiac Roles Modifier |
|---|---|---|
| Primary entrypoint | Subvault.call | execTransactionWithRole / module execution |
| Principal role gate | CALLER_ROLE on Vault ACL | membership in role for module/signer |
| Selector allowlist | Yes (ONCHAIN_COMPACT) | Yes (allowFunction wildcard / scoped) |
| Exact full calldata pinning | Yes (MERKLE_EXTENDED) | Possible via conditions, but generally expression-based |
| Merkle-proof flow | Native | Not core pattern |
| Runtime typed constraints | Custom verifier required | Native condition operators |
| Built-in spending/call quotas | No | Yes (WithinAllowance, CallWithinAllowance, etc.) |
| Native bundle unwrapping | No | Yes (transaction unwrappers) |
| Policy update gate | Vault role system | Roles owner (onlyOwner) |
| Typical policy representation | compact hashes + merkle root | condition trees + packed storage pointer |
8) Failure surfaces and debugging shape#
Mellow#
- Failures are relatively binary: caller not role-authorized, merkle proof invalid, hash mismatch, custom verifier false.
- Good for deterministic pinning, less expressive for dynamic ranges unless custom verifier stack grows.
Roles#
- Failures include granular status codes from condition engine (
FunctionNotAllowed,BitmaskNotAllowed, allowance violations, etc.). - More expressive, but also more moving parts: decoder tree shape, operator semantics, and adapter correctness.
9) Security tradeoff summary#
Interpreting this:
- Mellow Verifier is strongest when you want strict, pre-approved call shapes and minimal runtime interpretation.
- Roles Modifier is strongest when you need expressive, runtime-checked policies and native quotas.
10) Practical selection criteria#
Choose Mellow-style verifier checks first when:
- policy can be represented as compact allowlists or exact merkle-pinned calls
- you value deterministic call pinning and simple verification logic
- strategy execution already sits inside Mellow vault/subvault architecture
Choose Roles first when:
- one role must support dynamic ranges and structured calldata predicates
- you need native rate limits and allowance consumption
- you rely on Safe modules and bundle-unwrapping workflows
Use custom logic in both systems when protocol semantics become too specific for generic selectors.
- Mellow path: custom verifier contracts via
CUSTOM_VERIFIER - Roles path:
ICustomConditionor transaction unwrappers
11) Final take#
For curator calldata checks only, the strongest concise framing is:
- Mellow verifier is a hash/proof-first authorization system with optional custom verifier escape hatches.
- Roles modifier is a typed-condition authorization VM with built-in quotas and adapter-aware bundle checks.
Neither is universally better. They optimize for different policy expression models and operational constraints.