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 a parameter type 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 parameter type definitions specified with the nodes.
Node Properties
Each condition node is defined by the following properties:
paramType
Specifies how to decode and pluck the parameter value from calldata. It can be one of the following:
Value | Description |
---|---|
Static | Interpret the word currently in scope as a static value, i.e., a value with a fixed length padded to 32 bytes. |
Dynamic | Interpret the word currently in scope as an offset to a dynamic length value, i.e., bytes or string . |
Tuple | Decode the value as a tuple. The children condition nodes inform the types of the tuple's fields. |
Array | Interpret the word as an offset to a dynamic length array. The children condition nodes inform the array element type. |
AbiEncoded | Decode the value using standard ABI decoding. The children condition nodes inform the individual field types. |
Calldata | Decode the value like AbiEncoded , but skip the first 4 bytes (function selector). |
None | Used 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
ParamType.AbiEncoded
,ParamType.Calldata
,ParamType.Tuple
,ParamType.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.ArraySubset
) - Define branch conditions for n-ary logical operators (
Operator.And
,Operator.Or
,Operator.Nor
)
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 the value to compare against (only used with comparison operators).
Operators
Matches
Allows setting conditions on selected fields of tuple type parameters.
Used with ParamType.Calldata
it allows setting conditions on specific parameters of the function call.
Used with ParamType.AbiEncoded
it allows setting conditions on selected ABI encoded variables within a bytes value.
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 greater 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 thebytes
value to the first byte that should be masked.mask
specifies which bytes should be checked. A1
bit means that the bit at the same position should be checked, a0
bit means that the bit should be ignored.expected value
specifies the expected values of the bits under the mask.
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.
ArraySubset
Checks that every element of the array passes at least one child condition. Every child condition must only be taken into account once, meaning that if multiple elements are supposed to match the same condition that condition must appear multiple times in the children.
Logical Expressions
Logical expressions are n-ary operators that take an array of child conditions and combine their evaluation results.
Generally having a paramType
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
.
Nor
Checks that every child condition evaluates to false
.
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.
EtherWithinAllowance
Checks that the transaction's ether value does not exceed the given allowance and consumes that amount.
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
fields.
Examples
Balancer swap
The following condition enforces that the Balancer single swap (opens in a new tab) 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.
- singleSwap
tuple
- funds
tuple
- limit
uint256
- deadline
uint256
- Matches
Calldata
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 a ParamType.Calldata
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 thepoolId
fields is required. - For the
assetIn
andassetOut
fields, the values must be eitherWETH
orDAI
usingOperator.Or
. - For
swapInfo
thesender
andrecipient
fields must be set to the address of the avatar usingOperator.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.