PCL 조합 정책 — LogicalPolicy와 ForEachPolicy

mechanism compliance

리프 정책 템플릿을 AND / OR 트리로 조합하거나, 단일 자식 정책을 주체 집합(예: 모든 에이전트 소유자)에 걸쳐 확장할 수 있습니다. pass / fail / skip 단계 결과에 대해 명확한 결과 축약 규칙을 적용합니다.

LogicalPolicyForEachPolicy는 단일 규칙을 직접 강제하는 대신 다른 정책들을 조합하는 구조적 정책 템플릿입니다. LogicalPolicy는 자식 정책을 AND 또는 OR 수식자로 묶고, ForEachPolicy는 하나의 자식 정책을 주체 집합(예: 트랜잭션 호출자에 대해 해석된 에이전트 소유자들)에 걸쳐 확장합니다. 각 자식은 pass, fail, skip 중 하나의 단계 결과를 내며, 조합 정책의 결과 축약 동작은 이 세 결과에 대해 정확히 정의됩니다. 자식 정책이 적용 대상이 아닐 때(예: 트랜잭션 자산과 일치하지 않는 VOLUME_POLICY)의 pass / skip 구분은 트리를 설계할 때 중요합니다.

구조체 형태

두 조합 정책은 모두 IPcl.sol에 정의되어 있으며, 리프 템플릿과 동일한 PolicySet 래핑 규약을 따릅니다.
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;
}

단계 결과 — pass, fail, skip

모든 리프 평가는 세 가지 단계 결과 중 하나를 반환합니다.

  • pass — 이 연산에 정책이 적용되었고 수용되었습니다.
  • fail — 이 연산에 정책이 적용되었고 거절되었습니다(타입이 있는 ReasonCode 포함).
  • skip — 정책이 이 연산에 적용되지 않으며 판단 결과가 없습니다(예: validate-only 정책의 실행 후 record 단계).


현재 자산이나 금액에 적용 대상이 아닌 자식이 어떤 결과가 되는지는 보편 규칙이 아니라 정책별로 다릅니다. VOLUME_POLICY는 적용 대상이 아닌 연산을 pass로 평가합니다. 예를 들어 "uother"에 바인딩된 VOLUME_POLICYaokrw 트랜잭션을 만나면 pass가 되며, 대상 자산에 대해 금액이 0인 이체를 만나도 pass가 됩니다. 반면 PERIODIC_VOLUME_POLICY는 자산 불일치나 0 값에 대해 skip을 냅니다. 이 구분은 OR 조합에서 중요합니다. pass는 OR 그룹을 수용으로 귀결시키지만, skip은 그렇지 않습니다.

AND 의미론

LogicalQuantifier.And에서는 모든 자식이 수용되거나 skip되어야 합니다.

자식 결과그룹 결과
모두 pass (또는 passskip 혼합)pass
모두 skipskip
하나라도 failfail — 첫 번째 결정적 실패와 그 ReasonCode를 반환합니다

모든 자식을 평가한 뒤 결과를 축약합니다. 자식 중 하나라도 실패하면 그룹이 fail이 되며, 첫 번째 fail에서 평가가 멈추지 않습니다.

OR 의미론

LogicalQuantifier.Or에서는 자식 하나가 수용되면 충분합니다.

자식 결과그룹 결과
하나라도 passpass — 그룹이 수용으로 귀결됩니다
모두 failfail — 자식 revert들을 묶은 AnyOfRejected
모두 skipskip
skipfail 혼합 (pass 없음)skip — 그룹에 판단 결과가 없으며, skip하는 자식이 있으면 OR가 깔끔하게 거절할 수 없습니다

중요한 함의는 다음과 같습니다. [EAS_POLICY, DENYLIST_POLICY]의 OR에서 EAS_POLICY가 skip되고(예: 호출자의 attestation 조회 컨텍스트가 없는 경우) DENYLIST_POLICY가 fail되면 OR는 skip을 반환하고, 강제 계층은 skip 조합을 차단하지 않는 것으로 처리합니다. OR 브랜치는 의도한 호출자를 명확히 수용(pass)하는 자식이 최소한 하나 있도록 설계합니다.

ForEachPolicy — Any vs Every

ForEachPolicy는 단일 자식 정책을 주체 집합의 각 원소에 대해 실행합니다(현재는 AgentOwners — 트랜잭션 호출자에 연결된 에이전트 아이덴티티의 소유자들). 수식자가 원소별 결과를 어떻게 합칠지 결정합니다.

  • Any — 자식이 어떤 주체 원소에 대해서든 pass하면 pass입니다.
  • Every — 자식이 모든 주체 원소에 대해 pass할 때에만 pass입니다.


subject = AgentOwnersForEachPolicy를 사용하면 트랜잭션을 시작한 에이전트 지갑 자체가 아니라, 그 에이전트 지갑의 모든 소유자에게 리프 정책(예: KYC attestation을 요구하는 EAS_POLICY)을 적용할 수 있습니다.

실전 예시

유효한 EAS attestation 또는 호출자가 OKRW 주기 거래량 상한을 통과할 것을 요구하되, 브랜치 중 하나가 구체적인 거절을 낼 때에만 거절합니다.
// EAS_POLICY OR OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY 조합.
// 현재 호출에 대해 둘 다 적용 대상이 아니면 OR는 skip합니다(차단하지 않음).
// 둘 중 하나가 수용하면 OR는 pass합니다.
LogicalPolicy memory outer = LogicalPolicy({
    quantifier: LogicalQuantifier.Or,
    children: new PolicySet[](2)
});

EasPolicy memory eas = EasPolicy({
    easContract:   EAS_ADDR,
    indexContract: INDEX_ADDR,
    schemaUid:     KYC_SCHEMA_UID
});
outer.children[0] = PolicySet({
    templateId: "EAS_POLICY",
    policy:     abi.encode(eas),
    selector:   ""
});

OkrwEasPeriodicVolumeLimitPolicy memory cap = OkrwEasPeriodicVolumeLimitPolicy({
    easContract:        EAS_ADDR,
    indexContract:      INDEX_ADDR,
    schemaUid:          KYC_SCHEMA_UID,
    maxAmount:          10_000_000 ether,
    resetPeriodSeconds: 86_400
});
outer.children[1] = PolicySet({
    templateId: "OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY",
    policy:     abi.encode(cap),
    selector:   ""
});

PolicySet memory root = PolicySet({
    templateId: "LOGICAL_POLICY",
    policy:     abi.encode(outer),
    selector:   ""
});

깊이와 중첩 제한

조합 정책은 중첩할 수 있지만(LogicalPolicy의 자식이 다시 LogicalPolicy 또는 ForEachPolicy가 될 수 있습니다), 체인은 최대 평가 깊이를 강제합니다. 초과하면 MaxDepthExceeded(maxDepth)로 revert됩니다. 트리는 얕게 유지합니다. 실제 운영 정책은 대부분 두 계층까지만 조합합니다(외곽 OR / AND가 소수의 리프를 감싸는 형태). 중첩된 자식 PolicySetselector는 비어 있어야 합니다. selector는 중첩된 자식이 아니라 바깥(최상위) PolicySet에 붙이며, ChildSelectorNotEmpty가 이 규칙에 대해 선언된 오류입니다.
소스: maroo
ESC
검색어를 입력하세요