PCL 내장 정책 템플릿
마루가 기본 제공하는 다섯 가지 정책 템플릿을 다룹니다. denylist, 트랜잭션별 거래량 밴드, 누적 주기 상한, EAS attestation 게이트, 에이전트별 전송 한도를 포함합니다.
마루의 PCL은 가장 흔한 규제 및 비즈니스 컴플라이언스 게이트를 다섯 가지 내장 정책 템플릿으로 제공합니다. 각 템플릿은 IPcl.sol에 파라미터 struct를 정의하며(PolicySet.policy에 abi.encode로 인코딩됩니다), 평가 규칙은 GlobalPolicyConfig의 경우 AnteHandler, ContractPolicyConfig의 경우 PCL 프록시 훅 경로에서 실행됩니다. 관리자는 템플릿을 PolicySet으로 인스턴스화하여 설정에 부착합니다. 특정 네트워크에서 실제로 등록되어 있는 템플릿은 온체인 정책 관리자가 결정하므로, 가정하지 말고 런타임에 IPcl.policyTemplate(templateId)로 확인합니다.
다섯 가지 내장 템플릿
다음 표는
IPcl.sol이 기본 제공하는 모든 템플릿과 PolicySet.policy bytes를 채우는 파라미터 struct를 정리합니다. 이전에 있었던 두 템플릿 OKRW_EAS_TRANSFER_LIMIT_POLICY와 OKRW_EAS_PERIODIC_VOLUME_LIMIT_POLICY는 체인에서 제거되었습니다. 동일한 동작은 VOLUME_POLICY, PERIODIC_VOLUME_POLICY, EAS_POLICY를 LogicalPolicy / ForEachPolicy로 감싸 조합 정책으로 표현할 수 있습니다.| templateId | 파라미터 struct | 목적 |
|---|---|---|
EAS_POLICY | EasPolicy(address easContract, address indexContract, bytes32 schemaUid) | 지정 스키마에 대한 유효 EAS attestation을 호출자에게 요구합니다. |
DENYLIST_POLICY | DenylistPolicy(address[] addresses) | 호출자(또는 해석된 에이전트 소유자)가 주소 목록에 있으면 거절합니다. |
VOLUME_POLICY | VolumePolicy(string[] tokens, VolumeUnitPolicy[] limits) | 토큰 denom별 트랜잭션 단위 최소·최대 금액 밴드를 검사합니다. |
PERIODIC_VOLUME_POLICY | PeriodicVolumePolicy(string[] tokens, UnitPeriodicVolumePolicy[] limits) | resetPeriodSeconds 윈도 동안 denom별 누적 상한을 강제합니다. |
AGENT_OKRW_TRANSFER_LIMIT_POLICY | AgentOkrwTransferLimitPolicy(uint256 reserved) | 에이전트 온체인 TransferLimit 메타데이터에서 읽은 전송 상한을 강제합니다. Solidity가 빈 struct를 허용하지 않아 struct 필드가 존재하며, 체인은 이 필드를 무시합니다. |
템플릿별 실제 검사 대상
EAS_POLICY— EAS Indexer에서(schemaUid, 호출자)를 조회하고, 유효한 attestation이 없으면 EAS 계열 ReasonCode(EasNoAttestationReceived,EasAttestationLookupFailed,EasAttestationRevoked,EasAttestationExpired,EasAttestationRequired)로 거절합니다. 호출자가 에이전트 지갑이면 PCL이 하위 에이전트 소유자들을 해석하고, 소유자 중 한 명이라도 attestation을 갖고 있으면 통과합니다.DENYLIST_POLICY— 멤버십 검사만 수행합니다. attestation 조회나 거래량 누적 없이 동작합니다.VOLUME_POLICY— 트랜잭션 단위 밴드를 검사하며,VolumeBelowMinLimit또는VolumeAboveMaxLimit으로 거절합니다.PERIODIC_VOLUME_POLICY—(scope, contract, sender, selector, asset, resetPeriodSeconds)를 키로 누적 추적하고, 현재 주기의resetAt을 포함한ExceededPeriodicVolume으로 거절합니다.AGENT_OKRW_TRANSFER_LIMIT_POLICY— 에이전트 메타데이터의TransferLimit을 읽고ExceededAgentTransferLimit또는AgentTransferLimitMetadataInvalid로 거절합니다. 호출자 지갑이 에이전트인 경우에만 의미가 있습니다.
attestation을 평가하는 템플릿은
EAS_POLICY 하나뿐입니다. attestation 검사와 거래량 상한이나 denylist를 함께 요구해야 하는 규칙은 조합 정책으로 표현합니다(pcl-composite-policies 참고).복합 규칙을 위한 조합 정책
제거된
OKRW_EAS_* 템플릿이 표현하던 비즈니스 규칙은 이제 위의 리프 템플릿을 LogicalPolicy / ForEachPolicy 트리로 감싸 표현합니다. 예를 들어 "미인증 발신자는 24시간에 1,000만 OKRW로 제한" 규칙은 OR 조합으로 표현합니다. attestation을 갖거나, 주기 상한 이내에 머무르면 통과합니다. 조합 구조 정책(LogicalPolicy, ForEachPolicy)에는 자체 깊이·중첩 규칙이 있습니다(pcl-composite-policies 참고).import {
EasPolicy, PolicySet, LogicalPolicy, ForEachPolicy, LogicalQuantifier, ForEachQuantifier,
ForEachSubject, VolumePolicy, VolumeUnitPolicy, PeriodicVolumePolicy, UnitPeriodicVolumePolicy,
ContractPolicyConfig, GlobalPolicyConfig, DenylistPolicy
} from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
// 조합: 호출자가 attestation을 갖거나, 24시간 동안 1,000만 OKRW 상한 내에 머물러야 합니다.
EasPolicy memory eas = EasPolicy({
easContract: 0x1000000000000000000000000000000000000007,
indexContract: 0x1000000000000000000000000000000000000008,
schemaUid: 0x5f3a2b0e1d5c9a7f6e3b4a1d2c8f7b6a5e4d3c2b1a0f9e8d7c6b5a4938271605
});
string[] memory tokens = new string[](1);
tokens[0] = "aokrw";
UnitPeriodicVolumePolicy[] memory limits = new UnitPeriodicVolumePolicy[](1);
limits[0] = UnitPeriodicVolumePolicy({
maxAmount: 10_000_000 ether, // 1,000만 OKRW를 aokrw로 표시
resetPeriodSeconds: 86_400 // 24시간
});
PeriodicVolumePolicy memory cap = PeriodicVolumePolicy({ tokens: tokens, limits: limits });
PolicySet[] memory children = new PolicySet[](2);
children[0] = PolicySet({ templateId: "EAS_POLICY", policy: abi.encode(eas), selector: "" });
children[1] = PolicySet({ templateId: "PERIODIC_VOLUME_POLICY", policy: abi.encode(cap), selector: "" });
LogicalPolicy memory anyOf = LogicalPolicy({
quantifier: LogicalQuantifier.Or,
children: children
});
// `anyOf`를 templateId "LOGICAL_POLICY"인 `PolicySet`으로 감싸 대상 설정에 부착합니다. 등록된 템플릿 확인
실제로 활성화된 템플릿은 컴파일 시점 목록이 아니라 온체인 상태입니다.
"DENYLIST_POLICY"의 PolicySet을 빌드하기 전에 IPcl.policyTemplate("DENYLIST_POLICY")를 호출합니다. 등록되어 있으면 디스크립터를 반환하고, 아니면 타입 지정 오류 PolicyTemplateNotFound(string templateId)로 revert됩니다. 여러 네트워크에서 실행할 스크립트를 검증하는 표준 방식입니다. 네트워크마다 정책 관리자가 등록 템플릿을 독립적으로 결정합니다.import { createPublicClient, http, decodeErrorResult } from "viem";
const PCL = "0x1000000000000000000000000000000000000005" as const;
const client = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });
try {
const t = await client.readContract({
address: PCL,
abi: pclAbi,
functionName: "policyTemplate",
args: ["DENYLIST_POLICY"],
});
console.log("registered:", t.templateId);
} catch (err: any) {
const decoded = decodeErrorResult({ abi: pclAbi, data: err.data });
// decoded.errorName === "PolicyTemplateNotFound"
console.error("이 네트워크에 등록되지 않은 템플릿입니다:", decoded.args);
}