@@ -16,14 +16,15 @@ import {
1616import { NAMESPACE_STORAGE_CONTRACT_ADDRESS } from '../storage/constants' ;
1717import { getBaseStorageSlot } from '../storage/slot' ;
1818import type { AnyData } from '../types' ;
19- import { COMPOSABILITY_MODULE_ABI_V1_1_0 } from './abis' ;
19+ import { COMPOSABILITY_MODULE_ABI_V1_1_0 , CONSTRAINT_TUPLE_ABI } from './abis' ;
2020import {
2121 encodeAddress ,
2222 encodeRuntimeFunctionData ,
2323 getFunctionContextFromAbi ,
2424} from './runtimeAbiEncoding' ;
2525import {
2626 type Capture ,
27+ type ChildConstraint ,
2728 type ComposableCall ,
2829 type Constraint ,
2930 type ConstraintField ,
@@ -89,6 +90,11 @@ export const lessThanOrEqualToSigned = (value: AnyData): ConstraintField => {
8990 return { type : ConstraintType . LTE_SIGNED , value } ;
9091} ;
9192
93+ // value is ConstraintField[] — the sub-constraints that will be ABI-encoded as OR's referenceData
94+ export const orConstraint = ( subConstraints : ConstraintField [ ] ) : ConstraintField => {
95+ return { type : ConstraintType . OR , value : subConstraints } ;
96+ } ;
97+
9298export const runtimeParamViaCustomStaticCall = ( {
9399 targetContractAddress,
94100 functionAbi,
@@ -218,60 +224,91 @@ const SIGNED_CONSTRAINT_TYPES = new Set<ConstraintType>([
218224 ConstraintType . LTE_SIGNED ,
219225] ) ;
220226
221- const ALLOWED_CONSTRAINT_TYPES = new Set < ConstraintType > ( [
227+ // Child types: all constraint types that can appear standalone or inside an OR.
228+ // OR itself is not here — it is handled separately and cannot be nested.
229+ const CHILD_CONSTRAINT_TYPES = new Set < ConstraintType > ( [
222230 ConstraintType . EQ ,
223231 ConstraintType . GTE ,
224232 ConstraintType . LTE ,
225233 ConstraintType . GTE_SIGNED ,
226234 ConstraintType . LTE_SIGNED ,
227235] ) ;
228236
237+ /**
238+ * Validates and encodes a single child constraint (non-OR).
239+ * Throws if the type is OR — nested OR is not supported by the contract.
240+ */
241+ const validateAndProcessChildConstraint = ( constraint : ConstraintField ) : Constraint => {
242+ if ( constraint . type === ConstraintType . OR ) {
243+ throw new Error ( 'Nested OR constraints are not supported' ) ;
244+ }
245+
246+ if ( ! CHILD_CONSTRAINT_TYPES . has ( constraint . type ) ) {
247+ throw new Error ( 'Invalid constraint type' ) ;
248+ }
249+
250+ const isSigned = SIGNED_CONSTRAINT_TYPES . has ( constraint . type ) ;
251+
252+ if ( isSigned ) {
253+ // Signed constraints only accept bigint (including negative)
254+ if ( typeof constraint . value !== 'bigint' ) {
255+ throw new Error ( 'Invalid constraint value: signed constraints require bigint' ) ;
256+ }
257+
258+ // Encode as int256 to preserve two's-complement representation
259+ const valueHex = encodeAbiParameters ( [ { type : 'int256' } ] , [ constraint . value ] ) ;
260+ const encodedConstraintValue = encodeAbiParameters ( [ { type : 'bytes32' } ] , [ valueHex as Hex ] ) ;
261+ return prepareConstraint ( constraint . type , encodedConstraintValue ) ;
262+ }
263+
264+ // Unsigned / address / bool / hex path
265+ if (
266+ typeof constraint . value !== 'bigint' &&
267+ typeof constraint . value !== 'boolean' &&
268+ ! isHex ( constraint . value ) &&
269+ ! isAddress ( constraint . value )
270+ ) {
271+ throw new Error ( 'Invalid constraint value' ) ;
272+ }
273+
274+ if ( typeof constraint . value === 'bigint' && constraint . value < BigInt ( 0 ) ) {
275+ throw new Error ( 'Invalid constraint value' ) ;
276+ }
277+
278+ const valueHex = toBytes32 ( constraint . value ) ;
279+ const encodedConstraintValue = encodeAbiParameters ( [ { type : 'bytes32' } ] , [ valueHex as Hex ] ) ;
280+ return prepareConstraint ( constraint . type , encodedConstraintValue ) ;
281+ } ;
282+
229283export const validateAndProcessConstraints = ( constraints : ConstraintField [ ] ) : Constraint [ ] => {
230284 const constraintsToAdd : Constraint [ ] = [ ] ;
231285
232- if ( constraints . length > 0 ) {
233- for ( const constraint of constraints ) {
234- if ( ! ALLOWED_CONSTRAINT_TYPES . has ( constraint . type ) ) {
235- throw new Error ( 'Invalid constraint type' ) ;
286+ for ( const constraint of constraints ) {
287+ if ( constraint . type === ConstraintType . OR ) {
288+ // value must be ConstraintField[] — the sub-constraints to OR together
289+ if ( ! Array . isArray ( constraint . value ) || constraint . value . length === 0 ) {
290+ throw new Error ( 'OR constraint must have at least one sub-constraint' ) ;
236291 }
237292
238- const isSigned = SIGNED_CONSTRAINT_TYPES . has ( constraint . type ) ;
293+ // Process each sub-constraint through the child path — nested OR is rejected inside
294+ const processedSubs : Constraint [ ] = ( constraint . value as ConstraintField [ ] ) . map (
295+ validateAndProcessChildConstraint ,
296+ ) ;
239297
240- if ( isSigned ) {
241- // Signed constraints only accept bigint (including negative)
242- if ( typeof constraint . value !== 'bigint' ) {
243- throw new Error ( 'Invalid constraint value: signed constraints require bigint' ) ;
244- }
298+ // Encode the Constraint[] array as abi.encode(Constraint[]) — what the contract decodes
299+ const encodedSubs = encodeAbiParameters (
300+ [ CONSTRAINT_TUPLE_ABI ] ,
301+ [
302+ processedSubs . map ( ( c ) => ( {
303+ constraintType : c . constraintType ,
304+ referenceData : c . referenceData as Hex ,
305+ } ) ) ,
306+ ] ,
307+ ) ;
245308
246- // Encode as int256 to preserve two's-complement representation
247- const valueHex = encodeAbiParameters ( [ { type : 'int256' } ] , [ constraint . value ] ) ;
248- const encodedConstraintValue = encodeAbiParameters (
249- [ { type : 'bytes32' } ] ,
250- [ valueHex as Hex ] ,
251- ) ;
252- constraintsToAdd . push ( prepareConstraint ( constraint . type , encodedConstraintValue ) ) ;
253- } else {
254- // Handle value validation appropriate to runtime function
255- if (
256- typeof constraint . value !== 'bigint' &&
257- typeof constraint . value !== 'boolean' &&
258- ! isHex ( constraint . value ) &&
259- ! isAddress ( constraint . value )
260- ) {
261- throw new Error ( 'Invalid constraint value' ) ;
262- }
263-
264- if ( typeof constraint . value === 'bigint' && constraint . value < BigInt ( 0 ) ) {
265- throw new Error ( 'Invalid constraint value' ) ;
266- }
267-
268- const valueHex = toBytes32 ( constraint . value ) ;
269- const encodedConstraintValue = encodeAbiParameters (
270- [ { type : 'bytes32' } ] ,
271- [ valueHex as Hex ] ,
272- ) ;
273- constraintsToAdd . push ( prepareConstraint ( constraint . type , encodedConstraintValue ) ) ;
274- }
309+ constraintsToAdd . push ( prepareConstraint ( ConstraintType . OR , encodedSubs ) ) ;
310+ } else {
311+ constraintsToAdd . push ( validateAndProcessChildConstraint ( constraint ) ) ;
275312 }
276313 }
277314
@@ -280,15 +317,19 @@ export const validateAndProcessConstraints = (constraints: ConstraintField[]): C
280317
281318/**
282319 * Maps the user-facing RuntimeConstraint format to the internal ConstraintField format.
283- * Handles gte, lte, and eq constraint types.
284320 */
321+ const toChildConstraintField = ( c : ChildConstraint ) : ConstraintField => {
322+ if ( 'gte' in c ) return greaterThanOrEqualTo ( c . gte ) ;
323+ if ( 'lte' in c ) return lessThanOrEqualTo ( c . lte ) ;
324+ if ( 'gteSigned' in c ) return greaterThanOrEqualToSigned ( c . gteSigned ) ;
325+ if ( 'lteSigned' in c ) return lessThanOrEqualToSigned ( c . lteSigned ) ;
326+ return equalTo ( c . eq ) ;
327+ } ;
328+
285329export const toConstraintFields = ( constraints : RuntimeConstraint [ ] ) : ConstraintField [ ] =>
286330 constraints . map ( ( c ) => {
287- if ( 'gte' in c ) return greaterThanOrEqualTo ( c . gte ) ;
288- if ( 'lte' in c ) return lessThanOrEqualTo ( c . lte ) ;
289- if ( 'gteSigned' in c ) return greaterThanOrEqualToSigned ( c . gteSigned ) ;
290- if ( 'lteSigned' in c ) return lessThanOrEqualToSigned ( c . lteSigned ) ;
291- return equalTo ( c . eq ) ;
331+ if ( 'or' in c ) return orConstraint ( c . or . map ( toChildConstraintField ) ) ;
332+ return toChildConstraintField ( c ) ;
292333 } ) ;
293334
294335export const prepareTargetAndValueInputParams = (
0 commit comments