Skip to Content

Conditions

Zodiac Roles implements a robust conditions system that allows for defining complex conditions over calldata and ether value.

A condition is represented by a tree structure. Each node in the tree defines a condition operator and specifies an encoding for the value in scope. Evaluating the condition tree on transaction calldata starts at the root node. As the tree is traversed the calldata is decoded and specific ranges are plucked according to the encoding definitions specified with the nodes.

Node Properties

Each condition node is defined by the following properties:

encoding

Specifies how to decode and pluck the parameter value from calldata. It can be one of the following:

ValueDescription
StaticInterpret the word currently in scope as a static value, i.e., a value with a fixed length padded to 32 bytes.
DynamicInterpret the word currently in scope as an offset to a dynamic length value, i.e., bytes or string.
TupleDecode the value as a tuple. The children condition nodes inform the types of the tuple’s fields.
ArrayInterpret the word as an offset to a dynamic length array. The children condition nodes inform the array element type.
AbiEncodedDecode the value using standard ABI decoding. The children condition nodes inform the individual field types.
EtherValueReferences the ether value sent with the transaction. Used with value-targeting operators like WithinAllowance.
NoneUsed with logical expressions. The children condition nodes inform how to interpret the current scope.

children

An array of sub conditions, used for any of the following purposes:

  • Inform the decoding of complex types by defining types of their fields or elements (required for Encoding.AbiEncoded, Encoding.Tuple, Encoding.Array)
  • Set conditions on specific fields of a tuple type value (via Operator.Matches)
  • Set conditions on elements of an array type value (via Operator.ArraySome, Operator.ArrayEvery, Operator.ArrayTailMatches)
  • Define branch conditions for n-ary logical operators (Operator.And, Operator.Or)
  • Define parallel comparison patterns across multiple arrays (via Operator.ZipSome, Operator.ZipEvery)
  • Define extraction patterns for byte ranges (via Operator.Slice) and back-references to extracted values (via Operator.Pluck)

operator

Defines the conditional operation to apply to the value plucked from calldata. (Refer to the Operators section for a list of available operators.)

compValue

Defines a comparison or configuration value associated with the node. Used by comparison operators (e.g., EqualTo, GreaterThan) to specify the value to compare against, and by extraction operators (e.g., Slice, Pluck) to encode extraction parameters.

Operators

Matches

Allows setting conditions on selected fields of tuple type parameters. Used with Encoding.AbiEncoded it allows setting conditions on specific parameters of the function call or on selected ABI encoded variables within a bytes value.

Empty

Passes if the calldata is empty (i.e., the transaction has no calldata beyond the function selector, or no selector at all). Useful for selector-only checks or for validating plain ether transfers.

Comparisons

EqualTo

Checks equality of the plucked value to a given comparison value.

EqualToAvatar

Equivalent to using EqualTo with the avatar address as compValue.

This extra operator is provided for convenience and optimization, since comparing with the avatar address is a common operation. The avatar address is available to the condition evaluation function and therefore does not need to be stored as a comparison value.

GreaterThan

Checks that the plucked uint value is greater than the comparison value.

LessThan

Checks that the plucked uint value is less than the comparison value.

SignedIntGreaterThan

Checks that the plucked int value is greater than the comparison value.

SignedIntLessThan

Checks that the plucked int value is less than the comparison value.

Bitmask

Checks that bytes value in scope matches the bitmask encoded in the comparison value.

The bitmask is packed into a bytes32 compValue in the following way:

<bytes2 offset><bytes15 mask><bytes15 expected value>
  • offset specifies the offset in bytes from the start of the bytes value to the first byte that should be masked.
  • mask specifies which bytes should be checked. A 1 bit means that the bit at the same position should be checked, a 0 bit means that the bit should be ignored.
  • expected value specifies the expected values of the bits under the mask.

WithinRatio

Validates that the ratio between two plucked values falls within an acceptable range. Typically used in combination with Pluck to reference previously extracted values, enabling slippage protection for swap operations. A price adapter can be used to normalize the values to a common denomination before comparison.

Array Expressions

ArraySome

Checks that at least one element of the array matches the condition.

ArrayEvery

Checks that every element of the array matches the condition.

ArrayTailMatches

Matches trailing elements of an array against a condition pattern. This is useful when the meaningful elements are at the end of an array and the length of the prefix is not known ahead of time.

Extraction Operators

Slice

Extracts a range of bytes from the value currently in scope. The extracted bytes are made available for subsequent conditions in the tree to reference. This enables fine-grained inspection of packed or concatenated data.

Pluck

References a value that was previously extracted (e.g., by Slice or from a prior node in the tree) by its index. This allows conditions deeper in the tree to compare or validate against values encountered earlier during evaluation.

Zip Operators

Zip operators enable parallel comparison across multiple arrays that were previously extracted using Pluck. They iterate over the arrays element-by-element, applying a child condition to each corresponding tuple of elements.

ZipSome

Checks that at least one position across the zipped arrays satisfies the child condition.

ZipEvery

Checks that every position across the zipped arrays satisfies the child condition.

Logical Expressions

Logical expressions are n-ary operators that take an array of child conditions and combine their evaluation results. Generally having an encoding of None, logical expressions do not change the evaluation scope, meaning that every child condition will address the same calldata range.

And

Checks that every child condition evaluates to true.

Or

Checks that at least one of the child conditions evaluates to true.

Allowance Expressions

Allowances serve as a mechanism for tracking quotas. Each allowance expression references a specific allowance in the compValue field by its bytes32 key. After a call passes the condition and has been successfully executed, all allowances consumed within the condition will be updated. If the condition evaluates to false or executing the call reverts, allowances will not be updated.

WithinAllowance

Checks that the uint value in scope does not exceed the given allowance and consumes that amount. For limiting ether value, use Encoding.EtherValue with WithinAllowance to reference the transaction’s ether value.

CallWithinAllowance

Useful for tracking function call quotas, checks that the given allowance is not depleted and decrements it by 1.

Pass

Allows any value. Used for condition nodes that are required solely for informing how to decode complex type value.

For example, defining the types of all fields of a tuple is necessary for correct decoding.

Custom

Checks the value in scope against a custom condition defined in the compValue field. The compValue can contain unlimited extra bytes, allowing custom conditions to receive arbitrary configuration data.

Examples

Balancer swap

The following condition enforces that the Balancer single swap function must only be used to swap WETH for DAI and vice versa. Additionally, the sender and recipient for the swap must be set to the avatar address.

ABI inputs
condition
✅ example call data (valid)
⛔ example call data (violation)
0x
52bbbe2900000000000000000000000000000000000000000000000000000000000000e00000000000000000000000004f2083f5fbede34c2714affb3105539775f7fe6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000004f2083f5fbede34c2714affb3105539775f7fe640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
      sender: avatar address    recipient: avatar address        WETH/DAI pool ID    WETH token address  DAI token address        

The root condition node generally targets the entire calldata range after the 4 bytes function selector. It uses an Encoding.AbiEncoded matches structure defining how to decode the bytes in that range.

We define Operator.Matches checks on both tuples.

  • For singleSwap an exact match on the poolId fields is required.
  • For the assetIn and assetOut fields, the values must be either WETH or DAI using Operator.Or.
  • For swapInfo the sender and recipient fields must be set to the address of the avatar using Operator.EqualToAvatar.

This example shows Operator.Matches being used to set conditions on specific fields of a tuple type value.

Custom conditions

Custom conditions can be defined by implementing the ICustomCondition interface. For an example of a custom condition, refer to the AvatarIsOwnerOfERC721.sol contract.

Importantly, custom conditions must not maintain a state that is updated for every transaction passing through the check. This is because, at the level of the custom condition, there is no way to determine whether or not the transaction will ultimately be executed.

Last updated on