PCL 정책 구조
PCL 정책은 3계층 구조로 구성됩니다. PolicyTemplate(등록된 템플릿 타입)이 PolicySet(템플릿, ABI 인코딩 파라미터, 선택적 selector로 구성)으로 인스턴스화되고, 다시 PolicyConfig(적용되는 PolicySet 묶음)에 묶입니다.
PCL은 컴플라이언스 규칙을 JSON 객체가 아니라 Solidity로 정의된 ABI 튜플로 저장합니다. 구조는 3계층으로, PolicyTemplate(정책 관리자가 등록하는 규칙 타입)이 PolicySet(타입 ID와 ABI 인코딩 파라미터 blob, 선택적 함수 selector)으로 인스턴스화되고, PolicyConfig(전역 또는 컨트랙트별 설정)에 묶입니다. PolicyTemplate 자체는 메타데이터 전용으로 templateId, name, description만 가지므로, 각 템플릿의 파라미터 형태는 IPcl.sol의 템플릿별 struct에서 확인해야 합니다.
Solidity struct
IPcl.sol 그대로입니다.struct PolicyTemplate {
string templateId; // 예: "DENYLIST_POLICY"
string name;
string description;
}
struct PolicySet {
string templateId; // 어느 템플릿의 인스턴스인지
bytes policy; // abi.encode(<템플릿별 struct>)
bytes selector; // 선택적 4바이트 함수 selector; empty bytes = 모든 호출에 적용
}
struct GlobalPolicyConfig {
PolicySet[] policies;
}
struct ContractPolicyConfig {
address _contract; // 적용 대상 컨트랙트
address admin; // 향후 이 설정 변경 권한자
PolicySet[] policies;
} 핵심 필드는
PolicySet.policy이며, 템플릿별 파라미터 struct의 ABI 인코딩을 담는 bytes입니다. JSON으로 만들지 마십시오. Solidity / ethers / viem 측에서 abi.encode(<struct>)를 사용합니다. 각 PolicySet은 정확히 하나의 정책 인스턴스만 담습니다. 같은 범위에 여러 규칙을 적용하려면 상위 PolicyConfig.policies 배열에 여러 PolicySet 항목을 넣습니다. PolicyTemplate은 순수 디스크립터입니다. templateId(기계가 읽는 ID), 사람이 읽는 name, description만 가지므로, 각 templateId의 파라미터 형태는 IPcl.sol의 템플릿별 struct에서 확인합니다.호출자가 PolicySet을 만드는 법
각 템플릿은 자체 파라미터 struct를 정의합니다(전체 목록은
1. 템플릿별 struct를 구체 값으로 빌드합니다.
2.
3. 템플릿 id와 (선택적) 4바이트 함수 selector와 함께
예시로 두 주소를 denylist에 등록합니다.
pcl-policy-templates 참고). 정책 등록 절차는 다음과 같습니다.1. 템플릿별 struct를 구체 값으로 빌드합니다.
2.
abi.encode로 bytes를 인코딩합니다.3. 템플릿 id와 (선택적) 4바이트 함수 selector와 함께
PolicySet으로 래핑합니다.예시로 두 주소를 denylist에 등록합니다.
import { IPcl, PolicySet, ContractPolicyConfig, DenylistPolicy } from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
DenylistPolicy memory dl = DenylistPolicy({
addresses: new address[](2)
});
dl.addresses[0] = 0x8F3ac2B1d9E74c05A6B18FE27Dc4913e5A0F7b62;
dl.addresses[1] = 0x2c7f09B81a6D3FF1e5A0d4c6bC2a8f7E19dc3a4B;
PolicySet memory ps = PolicySet({
templateId: "DENYLIST_POLICY",
policy: abi.encode(dl),
selector: "" // empty → 대상 컨트랙트의 모든 호출에 적용
}); 이후
ContractPolicyConfig에 첨부하고 IPcl.changeContractPolicies(...)로 제출합니다. 상위 설정 하나에 여러 PolicySet을 담을 수 있으나, 같은 설정 안에서 selector 값은 유일해야 합니다. 하나의 설정에서 같은 selector를 두 번 사용하면 평문 문자열 duplicate selector: <selector>로 revert됩니다. 타입 지정 오류가 아닙니다.전역 정책과 컨트랙트 정책
범위는 두 가지입니다.
트랜잭션 진입 시 PCL은 전역 설정에서 적용 대상인 정책을 평가합니다(어떤 항목이 적용되는지는 아래 selector 절 참고). 호출 대상이 ContractPolicyConfig가 등록된 컨트랙트이며 규제 경로를 사용하면, 그 컨트랙트 설정에서도 적용 대상인 정책을 평가합니다. 어느 하나라도 실패하면 해당 ReasonCode와 함께 전체 트랜잭션이 거절됩니다.
GlobalPolicyConfig— 체인의 모든 트랜잭션에 적용됩니다. 체인 전역 정책 관리자(pcl-policy-admin참고)가 관리합니다. 전형적인 내용은 denylist, 미인증 사용자 주기 거래량 상한 등입니다.ContractPolicyConfig— 특정 컨트랙트 주소를 대상으로 하는 트랜잭션이 PCL 래핑 프록시 훅 경로를 통과할 때만 적용됩니다. 각 컨트랙트 설정은 자체admin을 가지므로 컨트랙트 소유자가 체인 전역 admin과 무관하게 자기 정책을 갱신할 수 있습니다.
트랜잭션 진입 시 PCL은 전역 설정에서 적용 대상인 정책을 평가합니다(어떤 항목이 적용되는지는 아래 selector 절 참고). 호출 대상이 ContractPolicyConfig가 등록된 컨트랙트이며 규제 경로를 사용하면, 그 컨트랙트 설정에서도 적용 대상인 정책을 평가합니다. 어느 하나라도 실패하면 해당 ReasonCode와 함께 전체 트랜잭션이 거절됩니다.
selector 필드
각
PolicySet은 선택적 selector(4바이트 함수 selector를 bytes로 인코딩)를 가집니다. 비어 있지 않으면 그 selector를 호출하는 함수에만 정책이 적용됩니다. 단일 범위가 함수별로 다른 규칙을 적용할 수 있습니다.import {
EasPolicy, PolicySet, LogicalPolicy, ForEachPolicy, LogicalQuantifier, ForEachQuantifier,
ForEachSubject, VolumePolicy, VolumeUnitPolicy, PeriodicVolumePolicy, UnitPeriodicVolumePolicy,
ContractPolicyConfig, GlobalPolicyConfig, DenylistPolicy
} from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
// 이 selector 규칙이 실어 나르는 정책(필드는 VOLUME_POLICY 페이지 참고):
string[] memory toks = new string[](1);
toks[0] = "aokrw";
VolumeUnitPolicy[] memory lims = new VolumeUnitPolicy[](1);
lims[0] = VolumeUnitPolicy({ minLimit: 0, maxLimit: 1_000_000 ether });
VolumePolicy memory volumePolicy = VolumePolicy({ tokens: toks, limits: lims });
// `foo(uint256)` 호출에만 적용:
bytes memory fooSel = abi.encodePacked(bytes4(keccak256("foo(uint256)")));
PolicySet memory ps = PolicySet({
templateId: "VOLUME_POLICY",
policy: abi.encode(volumePolicy),
selector: fooSel
}); Empty
selector 매칭은 두 범위 모두에 적용됩니다.
순서가 중요합니다. 빈 selector 항목이 먼저 실행되므로, 함수별 규칙을 검사하기 전에 광범위한 체인 전역 검사(예: denylist)가 단축 평가로 트랜잭션을 종료시킬 수 있습니다.
selector("")는 이 범위의 모든 호출을 의미합니다. 하나의 설정 안에서 모든 PolicySet은 서로 다른 selector 값을 가져야 합니다. 두 항목이 같은 selector를 사용하면 평문 문자열 duplicate selector: <selector>로 revert됩니다. PCL의 대부분 실패와 달리 타입 지정 오류가 아니므로 decodeErrorResult가 아니라 문자열로 디코드해야 합니다.selector 매칭은 두 범위 모두에 적용됩니다.
- 전역 설정 — PCL은 먼저 빈 selector
PolicySet(예: 체인 전역 denylist 같은 범용 규칙)을 평가한 뒤, 트랜잭션의 4바이트 함수 selector와 일치하는 항목이 있으면 그 항목을 평가합니다. 트랜잭션당 selector 매칭 전역 항목은 최대 하나만 실행됩니다(selectorbytes가msg.data[:4]와 같은 항목). - 컨트랙트 설정 — 대상 프록시로 범위가 좁혀질 뿐, 동일한 규칙이 적용됩니다. 빈 selector 항목이 먼저 실행되고 그 뒤 selector 매칭 항목이 실행됩니다.
순서가 중요합니다. 빈 selector 항목이 먼저 실행되므로, 함수별 규칙을 검사하기 전에 광범위한 체인 전역 검사(예: denylist)가 단축 평가로 트랜잭션을 종료시킬 수 있습니다.