M-1: Address OneOf Merkle proof is embedded in the constraints hash, limiting the constraint to a single value
Summary:
The OneOf constraint type for address parameters is intended to allow any address from a Merkle-tree-based allowlist. However, the Merkle proof required for verification is included inside the constraints data that gets hashed into the allowed functions Merkle tree, effectively locking the constraint to a single provable address.
Description:
In LibPolicyContractInteraction._isFunctionAllowedByPolicy(), the constraints bytes are hashed to produce the constraintsHash, which is then combined with the function selector to form the leaf that must exist in the allowed functions Merkle tree:
bytes32 constraintsHash = keccak256(constraints);
bytes32 funcLeaf = _computeFunctionLeaf(selector, constraintsHash);
return MerkleProof.verify(functionProof, policy.roots.allowedFunctionsRoot, funcLeaf);
The constraints bytes are an ABI-encoded array of ParameterConstraint structs. Each ParameterConstraint contains a paramValueInListProof field, which holds the Merkle proof used by _isAddressParameterAllowedByConstraint() to verify that the actual address value is a member of the allowed addresses tree.
Because paramValueInListProof is part of the ABI-encoded constraints, and the hash of the full constraints is committed in the allowed functions Merkle tree, only one specific proof (and therefore one specific address from the allowlist) can satisfy both the function-level Merkle verification and the address-level Merkle verification at the same time. Submitting a different proof for a different allowed address would change the constraints hash, causing the function-level Merkle verification to fail.
This reduces the OneOf constraint to behave identically to an Exact constraint, defeating its purpose of allowing any address from a predefined set.
Impact:
Medium. Policies that use OneOf address constraints for contract interaction parameters will silently restrict transactions to a single address instead of the full allowlist. This limits the expressiveness of the policy system and may force administrators to create redundant policies for each allowed address.
Recommendation:
Move the paramValueInListProof outside of the data that gets hashed into the allowed functions Merkle tree. One approach is to pass the proofs as a separate parameter alongside the constraints, so that changing the proof does not affect the constraints hash used for function-level verification.