Skip to content

EVML assertion reference

The assert command in full: its parts, the operators each type accepts, lenses, and every helper that reads the chain when the batch executes, by module.

This page lists what an assert line accepts. To learn how to write one, start with Writing assertions in EVML. The helper tables below are read from the compiler the builder uses, so they match what it accepts.

assert <value> [<operator> <expected>] ["message"] [--delta <amount>]
Part Required What it is
<value> yes A live value: a ::! read, or a helper whose name ends in !
<operator> no One of ==, !=, >, <, >=, <=, ~=
<expected> with an operator A fixed value, or another live value
"message" no The text the revert carries when the check fails
--delta <amount> with ~= How far the value may be from the expected one

With no operator, the value must be a boolean and the line checks that it is true. The message comes after the expected value, so a line with a message always has an operator. For a boolean, write == true:

assert $vault::!{paused()(bool)}
assert $vault::!{paused()(bool)} == false "vault is paused"

Anything after the message is an error. An expression with an infix operator such as a + b is not a value on its own: wrap it in @calc!( ) or @bool!( ).

Kind Read Written as
Live when the batch executes a ::! read, or a helper ending in ! such as @balance!
Fixed when the script is built a literal (100e18, "USDC", true), a $variable, or a helper without ! such as @token(WETH)

The value on the left must be live. A read written with a plain :: happens when the script is built, and is refused anywhere inside an assertion. To compare against a value read at build time, store it with set first and use the variable.

Type of the value Operators
uint and int of any width == != > < >= <= ~=
address, bool, bytes32, string, bytes == !=

A value declared int compares as a signed number, and one declared uint as unsigned. The declared return type decides, so declare the type the function returns.

~= passes when the value and the expected value differ by at most --delta.

  • --delta is required.
  • Both sides and the delta must be numbers.
  • A fixed delta must be a whole number of base units and cannot be negative.
  • A fixed expected value must be a whole number.
  • The expected value and the delta can each be live.
assert $oracle::!{price()(uint256)} ~= 2000e8 --delta 50e8 "price out of range"

On-chain values are whole numbers. When a fixed limit has a fraction, it is rounded so that the comparison keeps its meaning:

Operator A fractional limit is rounded Example
>= and < up >= 0.5 checks >= 1
> and <= down <= 1.9 checks <= 1
== and != not at all: the line is an error
<address>::!{<function>(<argument types>)(<return types>) <arguments>}
  • The address is a literal, a $variable, a helper, or any expression that gives an address when the batch executes.
  • The signature is written in the line. The first parentheses hold the argument types and the second the return types.
  • Arguments are separated by spaces. An argument can itself be a ::! read or a ! helper.
  • Reads chain: $pool::!{token()(address)}::!{symbol()(string)}. Every read in a chain except the last must give an address.

A lens in square brackets after a read selects one part of what it returns.

In a lens Meaning
$ the part you want
_ skip one part
... skip to the end, so the parts after it count from the end
[ ] inside the lens step into an array or a struct
assert $pool::!{getReserves()(uint112,uint112,uint32)}[_ $ _] >= 1000 "low reserve"
assert $safe::!{getOwners()(address[])}[[_ $]] == $signer "second owner changed"
assert $safe::!{getOwners()(address[])}[[... $]] == $signer "last owner changed"

A position past the end of a live array makes the assertion revert.

Wrapper Accepts
@calc!( ) + - * // % ^ on whole numbers, with overflow checked. // is integer division.
@calcFloor!( ), @calcCeil!( ) the same, with a * b / c rounded down or up
@bool!( ) the comparison operators, plus and, or, xor and not

Wrappers nest. @ifElse! needs spaces around ? and :.

@all!, @any!, @filter!, @find!, @map!, @reduce! and @sort! take a named definition as the test or the transform. A definition states its parameters and its result, then its body:

def @ge100! "$x: number -> bool" @bool!($x >= 100)
assert @all!($vault::!{caps()(uint256[])} @ge100!) == true "a cap is below 100"

Every helper here ends in ! and runs when the batch executes. Arguments in square brackets are optional, and ... marks an argument that repeats. Arguments are separated by spaces.

Always available: no load line is needed.

HelperReturnsWhat it reads or computes
@abi.decode!(types data [lens])anyDecode ABI-encoded bytes into values given a comma-separated type list; a lens selects one of them. Needs a [_ $] lens and returns only the selected value; the data argument must be a live call or bytes expression, and array selections are refused.
@abi.decodeCall!(contract calldata [abi])anyDecode calldata into [contract signature [args]] with human-readable EVML values. Takes an inline signature and a lens instead of fetching an ABI; checks the selector on-chain (a mismatch reverts) and returns only the selected argument.
@abi.encode!(types ...values)bytesABI-encode values given a comma-separated type list, like Solidity abi.encode. Encode live ABI values, including tuples and arrays, using a constant type descriptor.
@abi.encodeCall!(signature ...params)bytesABI-encode a function call from its signature and arguments. The signature must be constant; live arguments may include arrays and tuples.
@abi.encodePacked!(types ...values)bytesABI non-standard packed encoding, matching Solidity's abi.encodePacked. Live values are cut to their packed width and live string/bytes values pass through whole, any number per call; the type list, arrays and tuples stay constant.
@balance!(token holder)numberBalance in base units: the native balance for ETH, or an ERC-20 balanceOf for any token symbol or address. The token and the holder may each be a ::! call resolving to an address.
@bool!(...tokens)boolEvaluate a boolean expression or convert a value to a boolean string. Composes live comparisons with on-chain logic: and, or, xor and not.
@bytes!(a [b] [c])bytesConvert a value to hex bytes, force UTF-8 encoding, or perform a bitwise operation. Bitwise operations run over the raw 32-byte words (shifts in bits, >> arithmetic on a signed value); with one argument it is the raw word cast.
@calc!(...tokens)numberEvaluate checked 256-bit integer arithmetic; use // for truncating division. The same checked arithmetic evaluated on-chain.
@calcCeil!(...tokens)numberEvaluate checked 256-bit integer arithmetic; root division rounds ceil. The same checked arithmetic evaluated on-chain.
@calcFloor!(...tokens)numberEvaluate checked 256-bit integer arithmetic; root division rounds floor. The same checked arithmetic evaluated on-chain.
@hash!(text [algorithm])bytes32Hash a string with keccak256 (default) or sha256, or a hex value as bytes with the bytes modes. Bytes, an address or a bytesN need a bytes mode and a string a text mode; a number is refused until an encoder states its bytes.
@ifElse!(...expression)anyA ternary over live reads: cond ? then : else, evaluating only the winning branch. Parenthesized ternaries nest as branches. Compiles to the core's lazy cond: the condition's first resolved word judges (nonzero = then) and the losing branch is never resolved.
@orElse!(primary fallback)anyThe value of the first read, or the second one when the first reverts. Both branches must resolve to the same kind of value, and a constant fallback must fit in one word.
@reverts!(call [arrow] [error] [lens])anyWhether a live call reverts: true when the chain refuses the call, false when it resolves; -!> matches the reason and a lens selects an error argument. Bare probes test call failure. Error expectations resolve live targets and arguments before matching the final call's revert data.
@sigValid!(address data signature)boolVerify a signature against an expected signer address. Auto-detects EIP-712 typed data (JSON) vs. plain message. Contract signers verify through ERC-1271 (the plain face cannot); the signer and message must be constants, and a delegated account verifies against its key.

Add load lang at the top of the script.

HelperReturnsWhat it reads or computes
@all!(arr fn)boolWhether every element satisfies the predicate. Predicates return bool and stop at the first decisive result. Supports typed dynamic arrays and word-specialized predicates.
@any!(arr fn)boolWhether at least one element satisfies the predicate. Predicates return bool and stop at the first decisive result. Supports typed dynamic arrays and word-specialized predicates.
@at!(value index)anyAccess an element by index in an array. Accepts a constant or live signed index; negative indices count from the end and out-of-range indices revert.
@bytes.at!(value index)bytesAccess a single byte by index in a bytes value.
@bytes.concat!(first ...rest)bytesConcatenate bytes values together. Live parts remain unresolved until execution; each input is resolved once when the calldata is assembled.
@bytes.len!(value)numberByte length of a bytes value (a UTF-8 character may span several bytes).
@bytes.not!(value)bytesBitwise NOT of a bytes value (256-bit complement).
@bytes.slice!(value start [end])bytesExtract a byte range from a bytes value.
@concat!(first ...rest)arrayConcatenate arrays together. Live parts remain unresolved until execution; each input is resolved once when the calldata is assembled.
@count!(arr fn)numberCount the elements of an array that satisfy the predicate.
@enumerate!(arr)arrayPair every element of an array with its index. The result is a record, readable with @keys!, @values! and @lookup!.
@filter!(arr fn)arrayKeep elements of an array for which a helper returns truthy. Uses a named boolean predicate. Callbacks may compose helpers and ABI calls over typed word or multiword values.
@find!(arr fn)anyFirst element that satisfies the predicate; no match is an error. Returns the first matching typed value and stops evaluating predicates immediately; no match reverts.
@flat!(arr)arrayFlatten one level of nesting in an array. Flattens runtime nested arrays or a literal list of compatible array parts, preserving element types.
@includes!(value item)boolCheck whether an array contains an element. The element may be constant or live. Generic arrays compare canonical ABI values, preserving dynamic tuple and array boundaries.
@keys!(record)arrayEntry names of a record ([a:1 b:2] or [name value] pairs), as an array. The record is the word-pair payload @zip! and @enumerate! produce, and string names travel as their keccak digests.
@len!(value)numberLength of a value: element count for an array, byte length for a string or bytes.
@lookup!(record name)anyLook up an entry by name in a record ([a:1 b:2] or [name value] pairs). The record is a @zip!/@enumerate! word-pair payload, string names travel as keccak digests, and a missing name reverts.
@map!(arr fn)arrayTransform each element of an array by applying a helper. Uses named callbacks that compose helpers and ABI calls, preserving argument and result types, including multiword values.
@num.format!(value decimals)stringFormat an integer in base units as ordinary decimal notation, trimming trailing fractional zeros. Format live signed or unsigned integers with 0–77 decimal places.
@num.parse!(value decimals [rounding] [signedness])numberParse ordinary decimal notation into base units with explicit rounding and signedness. Parse live decimal strings; precision must be 0–77. Rounding and signedness are constant options.
@reduce!(arr fn initial)anyReduce an array to a single value by applying a helper. Accumulator comes first. Generic values and repeated accumulators accept composed ABI-typed definitions; word folds also accept associative operator names.
@reverse!(arr)arrayReverse the order of an array's elements.
@slice!(value start [end])arrayExtract a section of an array. Supports typed arrays and live signed indices. Bounds clamp to the array length and end is exclusive, matching the off-chain helper.
@sort!(arr [order])arraySort an array: ascending by default, desc for descending, or by a comparator helper. Supports natural word ordering or an ABI-typed comparator definition, including composed expressions; equal elements retain their order.
@str.at!(value index)stringAccess one UTF-8 byte as a string; reject bytes belonging to multibyte characters. Selects one UTF-8 byte; a byte belonging to a multibyte character is rejected.
@str.charset!(value class)boolCheck whether every byte of a string is in a character class (ranges like a-z0-9-; a leading or trailing dash is the literal -).
@str.concat!(first ...rest)stringConcatenate strings together. Live parts remain unresolved until execution; each input is resolved once when the calldata is assembled.
@str.includes!(value item)boolCheck whether a string contains a substring (exact byte sequence, case-sensitive). The substring may be a live call. Every string contains the empty string.
@str.join!(arr delim)stringJoin array elements into a string with a delimiter. Array elements must be strings or bytes; literal arrays support live parts. The delimiter may be constant or live.
@str.len!(value)numberLength of a string in UTF-8 bytes. Counts bytes, so a multi-byte UTF-8 character counts more than once.
@str.lower!(s)stringConvert ASCII letters in a string to lowercase; preserve other characters. Maps ASCII letters only; every other byte passes through unchanged.
@str.replace!(s old replacement)stringReplace all occurrences of a substring (every non-overlapping left-to-right match). The needle and replacement may be live calls; a constant needle must be non-empty, and an empty live one reverts.
@str.slice!(value start [end])stringExtract a UTF-8 byte range, with clamped indexes and complete characters. Slices UTF-8 bytes; ranges cutting through a character are rejected.
@str.split!(s delim [index])arraystringSplit a string by a delimiter into an array of strings, or select one segment when an index is given. Returns the complete array when no index is supplied; indexed selection supports negative indexes.
@str.upper!(s)stringConvert ASCII letters in a string to uppercase; preserve other characters. Maps ASCII letters only; every other byte passes through unchanged.
@sum!(arr)numberSum the elements of an array.
@unique!(arr [equal])arrayRemove duplicates from an array, preserving first-occurrence order. Uses canonical ABI equality by default, or a custom equality predicate, preserving first-occurrence order.
@unzip!(pairs [lane])arrayTranspose an array of pairs into two separate arrays. Omitting lane returns both typed lanes. An explicit lane selects 0 or 1; keys and values select the corresponding lane.
@values!(record)arrayEntry values of a record ([a:1 b:2] or [name value] pairs), as an array. The record is the word-pair payload @zip! and @enumerate! produce, and string names travel as their keccak digests.
@zip!(a b)arrayCombine two arrays element-wise into an array of pairs. Either or both sides may be live, a length mismatch reverts, and the result retains the type of each lane.

Add load math at the top of the script.

HelperReturnsWhat it reads or computes
@absDiff!(a b)numberAbsolute difference |a - b|. Never underflows, so @absDiff!(a b) <= d is the composable approximate-equality.
@exp!(exponent)numbere raised to a wad-scaled power, in wad (1e18) fixed point. Continuous growth over a period: a rate r compounded continuously multiplies a balance by exp(r). Accepts live values and carries the result’s wad scale for surrounding arithmetic.
@ln!(value)numberThe natural logarithm of a wad-scaled value, in wad (1e18) fixed point. The inverse of exp: it turns a growth factor back into the rate that produced it. Accepts live values and carries the result’s wad scale for surrounding arithmetic.
@log2!(value)numberThe base-2 logarithm of a whole number, rounded down — the position of its highest set bit, so it also gives a bit length. Undefined at zero.
@max!(...values)numberMaximum of two or more values. Operands are written out at the call site, or given as one literal array; an array a call returns has no on-chain form here.
@min!(...values)numberMinimum of two or more values. Operands are written out at the call site, or given as one literal array; an array a call returns has no on-chain form here.
@pow!(value exponent [base])numberRaise a fixed-point value to a whole power, where one unit is base (1e18 by default, 1e27 for a ray). Compounding a per-period rate over N periods is pow(unit + rate, N). A value carrying a scale other than a wad must state its unit, since the plain face cannot see a scale and would compound at 1e18.
@sqrt!(...expression)numberInteger square root (floor).

Add load contracts at the top of the script.

HelperReturnsWhat it reads or computes
@codeAt!(address)bytesDeployed bytecode at an address. Sees code a batch deployed in an earlier step, and an address that self-destructed or was redeployed, which a build-time read cannot.
@codeHash!(address)bytes32Code hash of an address, with EXTCODEHASH semantics: bytes32(0) for a nonexistent account (zero nonce, balance and code), keccak256 of the code otherwise. The account may be a call resolving to an address (:: at build time, ::! on-chain), such as a proxy implementation.
@slot.array!(base index)bytes32Derive the storage slot of element index of a dynamic array declared at a base slot: keccak256(base) + index. The base slot must be a constant; the index may be live, e.g. a length read. Reading the slot on-chain needs a target with an extsload-style getter.
@slot.mapping!(base key)bytes32Derive the storage slot of mapping[key] for a mapping declared at a base slot: keccak256(h(key) . base). The base slot must be a constant; the key may be live. Reading the slot on-chain needs a target with an extsload-style getter.

Add load receipts at the top of the script. This module is experimental, so its helpers may change between versions.

HelperReturnsWhat it reads or computes
@block.baseFee!([block] [chain])numberBase fee in wei of a sealed block, addressed by number or tag (default: latest). Reads the block being written, and takes no arguments.
@block.blobBaseFee!([block] [chain])numberBlob base fee in wei: the live value with no arguments, or the EIP-4844 value of a sealed block computed from its excess blob gas (blocks predating EIP-4844 error). Reads the block being written, and takes no arguments.
@block.coinbase!([block] [chain])addressFee recipient address of a sealed block, addressed by number or tag (default: latest). Reads the block being written, and takes no arguments.
@block.gasLimit!([block] [chain])numberGas limit of a sealed block, addressed by number or tag (default: latest). Reads the block being written, and takes no arguments.
@block.hash!([block] [chain])bytes32Hash of a sealed block, addressed by number or tag (default: latest). BLOCKHASH semantics: the block number is required, only the previous 256 blocks are reachable, and anything outside them reads 0.
@block.number!([block] [chain])numberNumber of a sealed block, addressed by number or tag (default: latest, so tags like finalized resolve to their current number). Reads the block being written, and takes no arguments.
@block.prevrandao!([block] [chain])numberRANDAO mix of a sealed block, as a number, addressed by number or tag (default: latest; pre-merge blocks carry proof-of-work difficulty semantics). Reads the block being written, and takes no arguments.
@block.timestamp!([block] [chain])numberTimestamp of a sealed block, addressed by number or tag (default: latest). Reads the block being written, and takes no arguments.
@chainId!numberThe chain id of the chain the script runs against.
@tx.blobHash!(...index)bytes32Versioned hash of a blob carried by the executing transaction, or 0 when the index is out of range.
@tx.from!(hash [chain])addressSender of a transaction, addressed by hash. Reads the origin of the transaction being written (the ORIGIN opcode), and takes no arguments.
@tx.gasPrice!numberGas price of the executing transaction, in wei.

Add load token at the top of the script.

HelperReturnsWhat it reads or computes
@token:allowance!(tokenSymbol owner spender)numberAllowance an owner has granted to a spender, in base units.
@token:amount!(tokenSymbolOrAddress amount)numberConvert a human-readable token amount to its base unit (applying decimals). Scales against a live decimals() read rather than a build-time constant.
@token:decimals!(tokenSymbol)numberNumber of decimals of a token.
@token:symbol!(tokenSymbol)stringSymbol a token reports, looked up by address. The native token has no contract, so it folds to its constant symbol instead of an on-chain read.
@token:totalSupply!(tokenSymbol)numberTotal supply of a token, in base units.

Add load vault at the top of the script. This module is experimental, so its helpers may change between versions.

HelperReturnsWhat it reads or computes
@vault:asset!(vault)addressUnderlying asset token address of an ERC-4626 vault.
@vault:claimableDeposit!(vault [controller] [requestId])numberAssets of a fulfilled deposit request claimable from an ERC-7540 vault, in base units of the asset.
@vault:claimableRedeem!(vault [controller] [requestId])numberShares of a fulfilled redemption request claimable from an ERC-7540 vault, in base units of the share.
@vault:convertToAssets!(vault shares)numberAmount of underlying assets an ERC-4626 vault would return for a given amount of shares, in base units of the asset.
@vault:convertToShares!(vault assets)numberAmount of shares an ERC-4626 vault would mint for a given amount of underlying assets, in base units of the share token.
@vault:isOperator!(vault operator [controller])boolWhether an account is an approved operator of a controller on an ERC-7540 vault.
@vault:maxWithdraw!(vault [account])numberMaximum amount of underlying assets an account can withdraw from an ERC-4626 vault, in base units of the asset.
@vault:pendingDeposit!(vault [controller] [requestId])numberAssets of a pending (not yet fulfilled) deposit request on an ERC-7540 vault, in base units of the asset.
@vault:pendingRedeem!(vault [controller] [requestId])numberShares of a pending (not yet fulfilled) redemption request on an ERC-7540 vault, in base units of the share.
@vault:share!(vault)addressShare token address of a vault. ERC-7575 vaults expose a separate share token; plain ERC-4626 vaults are their own share token, so the vault address itself is returned.
@vault:totalAssets!(vault)numberTotal amount of underlying assets managed by an ERC-4626 vault, in base units of the asset.

Add load acl at the top of the script. This module is experimental, so its helpers may change between versions.

HelperReturnsWhat it reads or computes
@acl:canCall!(manager caller target signature)boolWhether a caller can immediately call a restricted function of a contract managed by an AccessManager. Reads the immediate flag of the pair, not the delay.
@acl:defaultAdmin!(contract)addressCurrent default admin of an AccessControlDefaultAdminRules contract.
@acl:defaultAdminDelay!(contract)numberDelay in seconds applied to default admin transfers of an AccessControlDefaultAdminRules contract.
@acl:hasRole!(target role account)boolWhether an account holds a role on an AccessControl contract (string roles) or an AccessManager (numeric role ids).
@acl:operationId!(manager caller target signature [params])bytes32Operation id of an AccessManager call (hashOperation of caller, target and calldata), for use with @acl:operationSchedule.
@acl:operationSchedule!(manager operationId)numberTimestamp at which a scheduled AccessManager operation becomes executable (0 when unset, expired or already executed).
@acl:owner!(contract)addressCurrent owner of an Ownable contract.
@acl:pendingDefaultAdmin!(contract)addressPending default admin of an AccessControlDefaultAdminRules contract (the zero address when no transfer is in progress). Reads the pending admin of the pair, not the accept schedule.
@acl:pendingOwner!(contract)addressPending owner of an Ownable2Step contract (the zero address when no transfer is in progress).
@acl:roleAdmin!(target role)bytes32numberAdmin role that controls a role: a bytes32 value on AccessControl contracts, a role id on AccessManagers.

Add load proxies at the top of the script. This module is experimental, so its helpers may change between versions.

HelperReturnsWhat it reads or computes
@proxies:admin!(proxy)addressAdmin of a transparent ERC-1967 proxy (the ProxyAdmin contract on OpenZeppelin v5 proxies). Calls admin() on the proxy, not the ERC-1967 slot: it reverts where the proxy hides it and trusts what the proxy answers.
@proxies:beacon!(proxy)addressBeacon address of an ERC-1967 beacon proxy. Calls beacon() on the proxy, not the ERC-1967 slot: it reverts where the proxy hides it and trusts what the proxy answers.
@proxies:implementation!(proxy)addressImplementation address of an ERC-1967 proxy, following the beacon when the proxy is a beacon proxy. Resolves through an implementation() call or the beacon hop, so a slot-only proxy has no on-chain form and reverts.
@proxies:predictClone!(implementation salt [deployer])addressPredicted address of a deterministic ERC-1167 clone deployed with proxies:clone --salt. Pure computation, no chain read. The salt may be a live value (a counter or registry read); the implementation and deployer shape the creation code and must be constants.

Add load safe at the top of the script. This module is experimental, so its helpers may change between versions.

HelperReturnsWhat it reads or computes
@safe:guard!([safe] module)addressTransaction guard address of a Safe, or with module:true its module guard (the zero address when none is set; module guards need Safe v1.5.0 or later, so it is always zero on older Safes).
@safe:isOwner!(owner [safe])boolWhether an address is an owner of a Safe.
@safe:modules!([safe] [pageSize])arrayEnabled module addresses of a Safe. Reads one getModulesPaginated page, so a Safe with more modules than pageSize (default 100) is truncated.
@safe:nonce!([safe])numberCurrent nonce of a Safe.
@safe:owners!([safe])arrayOwner addresses of a Safe.
@safe:threshold!([safe])numberSignature threshold of a Safe.

Add load governor at the top of the script. This module is experimental, so its helpers may change between versions.

HelperReturnsWhat it reads or computes
@governor:proposalId!(governor targets values calldatas description)numberProposal id of a Governor proposal, derived from its targets, values, calldatas and description. Prefer the optional variable of governor:propose when creating the proposal in the same script. Tries getProposalId and falls back to hashProposal, whichever derivation the governor exposes.
@governor:proposalState!(governor proposalId)stringCurrent state of a Governor proposal: Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired or Executed. Returns the raw uint8 enum (0 Pending, 1 Active, 2 Canceled, 3 Defeated, 4 Succeeded, 5 Queued, 6 Expired, 7 Executed), not the name.
@governor:timelockMinDelay!(timelock)numberMinimum delay in seconds a TimelockController enforces on new operations.
@governor:timelockOperationState!(timelock operationId)stringState of a TimelockController operation: Unset, Waiting, Ready or Done. Returns the numeric OperationState (0 Unset, 1 Waiting, 2 Ready, 3 Done), not the name.

Both state helpers return a number when they run in an assertion:

Helper States
@governor:proposalState! 0 Pending, 1 Active, 2 Canceled, 3 Defeated, 4 Succeeded, 5 Queued, 6 Expired, 7 Executed
@governor:timelockOperationState! 0 Unset, 1 Waiting, 2 Ready, 3 Done