ERC20Verifier

Overview

ERC20Verifier is a role-driven ICustomVerifier implementation that enforces strict, granular permissioning over ERC20 approve and transfer function calls. It builds upon OwnedCustomVerifier, using MellowACL-style roles to validate the caller, target asset, and recipient of each operation.

This verifier is designed for use in modular vaults such as SubVault where only specific ERC20 operations should be allowed through a customizable permission matrix.

Purpose

To allow or deny ERC20 approve and transfer calls based on:

  • The caller (must have CALLER_ROLE)

  • The asset address (must have ASSET_ROLE)

  • The recipient (must have RECIPIENT_ROLE)

  • Additionally:

    • transfer must not be for zero amount

    • approve allows any amount

    • value sent with the call must be 0

    • Only exact calldata is accepted (no encoding variation or garbage data)

Roles

Each permission check is mapped to a distinct bytes32 role:

Role Constant
Purpose

ASSET_ROLE

Marks which ERC20 tokens are allowed to be interacted with

CALLER_ROLE

Who is allowed to perform approve or transfer

RECIPIENT_ROLE

Who is allowed to receive tokens (for transfer) or get approval (for approve)

These roles are expected to be configured via the initialize() function inherited from OwnedCustomVerifier.

Contract Behavior

Constructor

constructor(string memory name_, uint256 version_)
  • Passes initialization parameters to OwnedCustomVerifier and disables further initializers

verifyCall Function

function verifyCall(
    address who,
    address where,
    uint256 value,
    bytes calldata callData,
    bytes calldata /* verificationData */
) external view override returns (bool)

Summary:

Checks if a specific ERC20 call is authorized.

Logic Steps:

  1. Pre-checks:

    • Must be a zero-ETH call: value == 0

    • Calldata must be exactly 68 bytes: 4-byte selector + 32 bytes address + 32 bytes uint

    • where (the token address) must have ASSET_ROLE

    • who (the caller, usually curator) must have CALLER_ROLE

  2. Selector Validation:

    • Accepts only two ERC20 functions:

      • approve(address,uint256)

      • transfer(address,uint256)

  3. Recipient & Amount Validation:

    • to address must have RECIPIENT_ROLE

    • For transfer:

      • amount must not be zero

    • to must not be zero address in any case

  4. Exact Calldata Matching:

    • Ensures call is not forged via alternate encodings:

      keccak256(abi.encodeWithSelector(selector, to, amount)) == keccak256(callData)

Returns:

  • true if all checks pass

  • false otherwise

Security Considerations

  • Prevents misuse of approve and transfer by enforcing:

    • Strict role-based gating

    • Zero ETH payload enforcement

    • Calldata normalization to eliminate encoding ambiguity

  • Ensures no contract or address receives funds or allowances without being explicitly whitelisted