PCL 조합 정책 — LogicalPolicy와 ForEachPolicy
리프 템플릿을 AND/OR 트리로 조합하거나, 해석된 에이전트 소유자들에게 하나의 규칙을 적용하는 구조 결합자를 다룹니다.
LogicalPolicy와 ForEachPolicy는 자체 리프 규칙을 담지 않고 다른 PolicySet들을 트리로 감싸는 구조 정책 템플릿입니다. LogicalPolicy는 자식들을 AND(Every) 또는 OR(Any) 정량자로 결합합니다. ForEachPolicy는 하나의 자식을 취해 해석된 대상마다 한 번씩 적용합니다. 현재 대상은 호출자의 에이전트 소유자 집합입니다. 두 구조 모두 최대 깊이(MaxDepthExceeded)와 검증 오류(LogicalPolicyChildrenEmpty, ForEachChildAbsent, QuantifierUnspecified, ChildSelectorNotEmpty)의 제한을 받으며, 등록 시점에 잘못된 트리를 거절합니다.
구조 정책 struct
IPcl.sol 그대로입니다.enum LogicalQuantifier { Unspecified, And, Or }
enum ForEachQuantifier { Unspecified, Any, Every }
enum ForEachSubject { Unspecified, AgentOwners }
struct LogicalPolicy {
LogicalQuantifier quantifier;
PolicySet[] children;
}
struct ForEachPolicy {
ForEachQuantifier quantifier;
ForEachSubject subject;
PolicySet child;
} LogicalPolicy — AND/OR 결합자
LogicalPolicy는 두 개 이상의 PolicySet 자식을 모아 하나의 정량자로 평가합니다.And— 모든 자식이 통과해야 합니다. 첫 실패에서 그 자식의 ReasonCode가 노출됩니다.Or— 최소 한 자식이 통과해야 합니다. 모든 자식이 실패하면 각 실패의 ABI 인코딩 revert 페이로드를 담은AnyOfRejected(bytes[] childReverts)가 노출되며, 클라이언트는 이를 풀어 사용자에게 보여줄 메시지를 선택합니다.
LogicalPolicy 안의 각 자식 PolicySet은 selector가 비어 있어야 합니다. selector는 상위 PolicySet에 한 번만 지정합니다. 자식에 비어 있지 않은 selector를 넣어 등록하면 ChildSelectorNotEmpty로 revert됩니다. children 배열이 비어 있으면 LogicalPolicyChildrenEmpty, 배열의 nil 항목은 LogicalPolicyChildNil(uint256 index)로 revert됩니다. 자식으로 어떤 리프 템플릿이든 사용할 수 있으며, 중첩할 수 없는 템플릿에 대한 런타임 제한은 없습니다.ForEachPolicy — 대상 집합에 규칙을 확장
ForEachPolicy는 단일 child 정책을 해석된 대상 집합의 원소마다 한 번씩 적용합니다. 현재 구현된 대상은 AgentOwners뿐이며, 호출자 지갑에 바인딩된 모든 에이전트의 온체인 소유자 집합으로 확장됩니다. 정량자는 대상별 결과를 어떻게 결합할지 결정합니다. Every는 모든 소유자가 통과해야 하고, Any는 하나만 통과하면 됩니다. 대표 사례는 "이 에이전트 소유자 중 최소 한 명이 attestation을 갖고 있어야 합니다."입니다. EAS_POLICY를 ForEachPolicy(subject: AgentOwners, quantifier: Any)로 감싸 표현합니다.등록 시점에 트리를 즉시 검증합니다. subject가 지정되지 않으면
ForEachSubjectUnspecified, 정량자가 지정되지 않으면 QuantifierUnspecified, child가 없으면 ForEachChildAbsent로 revert됩니다. 알 수 없는 subject 열거값은 UnknownForEachSubject(uint8 subject)로 revert됩니다.// "이 호출자의 에이전트 소유자 중 최소 한 명이 KYC attestation을 보유해야 합니다."
EasPolicy memory eas = EasPolicy({
easContract: 0x1000000000000000000000000000000000000007,
indexContract: 0x1000000000000000000000000000000000000008,
schemaUid: 0x5f3a2b0e1d5c9a7f6e3b4a1d2c8f7b6a5e4d3c2b1a0f9e8d7c6b5a4938271605
});
PolicySet memory child = PolicySet({
templateId: "EAS_POLICY",
policy: abi.encode(eas),
selector: "" // 필수 — 구조 자식은 selector가 비어 있어야 합니다.
});
ForEachPolicy memory forOwners = ForEachPolicy({
quantifier: ForEachQuantifier.Any,
subject: ForEachSubject.AgentOwners,
child: child
}); 깊이 제한과 진단
구조 정책은 고정된 최대 깊이의 대상입니다. 등록 시 이를 초과하면
MaxDepthExceeded(uint8 maxDepth)로 revert됩니다. AND 안에 OR을, 다시 그 안에 OR을 중첩하면 한계에 빠르게 도달합니다. Or 결합자가 거절되면 revert 데이터는 AnyOfRejected(bytes[] childReverts)입니다. childReverts를 순회하며 각 항목을 decodeErrorResult로 해제하고, 사용자에게 보여줄 항목을 선택합니다. 다른 구조 오류(LogicalPolicyChildrenEmpty, LogicalPolicyChildNil, ForEachChildAbsent, ForEachSubjectUnspecified, QuantifierUnspecified, UnknownForEachSubject, ChildSelectorNotEmpty)는 changeContractPolicies 또는 setGlobalPolicies 시점에 노출되므로, 올바르게 구성된 설정은 실행 시점에 이 오류들이 발생하지 않습니다.