PCL 프리컴파일
프로그래머블 컴플라이언스 레이어의 EVM 표면으로, 주소는 0x1000…0005입니다. 정책 그래프 조회, 템플릿 및 설정의 등록·변경, PCL 래핑 프록시 배포, 규제 프록시가 매 호출마다 사용하는 preCall/postCall 훅 경로를 제공합니다.
PCL 프리컴파일은 고정된 EVM 주소에 위치한 상태 저장 프리컴파일로, Solidity 코드와 마루의 컴플라이언스 엔진을 연결합니다. 조회 뷰(getParams, globalPolicies, contractPolicies, policyTemplate, 주기 거래량 조회)와 관리자 전용 쓰기(registerPolicyTemplate, setGlobalPolicies, changeContractPolicies 및 각각의 제거 함수)를 제공합니다. 규제 실행 경로도 함께 소유합니다. deployPclProxy는 표준 PCL 래핑 프록시를 배포하며, 이 프록시의 훅 경로가 실제 실행 전후로 preCall과 postCall을 호출합니다. 프록시를 거치는 컨트랙트의 컨트랙트 범위 정책은 이 훅에서 발동합니다. 신뢰된 ERC-4337 진입점도 preCall / postCall을 호출할 수 있으며, 정책 인식 프리컴파일인 Privacy 프리컴파일은 자체 경계에서 자신의 컨트랙트 정책을 평가합니다.
아키텍처
flowchart LR
User[EOA / dApp]:::evm --> Proxy[PCL Proxy<br/>Transparent or UUPS]:::evm
Proxy --> Pre[IPcl.preCall]:::precompile
Pre --> Logic[Logic Contract]:::evm
Logic --> Post[IPcl.postCall]:::precompile
Post --> Result[Return / Revert<br/>with ReasonCode]:::evm
Admin[Contract Admin]:::evm -.-> ChangeCfg[IPcl.changeContractPolicies]:::precompile
Reader[Any address]:::evm -.-> Views[IPcl views<br/>contractPolicies / globalPolicies / policyTemplate]:::precompile
classDef evm fill:#0096AA,stroke:#0096AA,color:#fff;
classDef precompile fill:#FF8C50,stroke:#FF8C50,color:#fff; 규제 대상 실행은 PCL 프록시를 경유하며, 각 호출을 preCall과 postCall로 감쌉니다. 컨트랙트 정책 설정은 컨트랙트 admin이 changeContractPolicies로 수행하고, 정책 그래프 조회는 누구에게나 열려 있습니다.
주소와 인터페이스
@maroo-chain/contracts의 IPcl이며, 같은 패키지의 상수 PCL_PRECOMPILED_ADDRESS가 주소를 고정해 컨트랙트가 하드코딩할 필요를 없앱니다.// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.28;
import {IPcl, PCL_CONTRACT, PCL_PRECOMPILED_ADDRESS} from
"@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
contract Example {
IPcl constant pcl = PCL_CONTRACT; // 0x1000…0005
address constant PCL = PCL_PRECOMPILED_ADDRESS; // 리터럴 형태의 동일 주소
} 메서드 그룹
- 파라미터와 템플릿 —
getParams,policyAdmin,policyTemplate로 조회하고,registerPolicyTemplate과removePolicyTemplate로 템플릿을 등록·해제합니다. - 정책 설정 —
globalPolicies와contractPolicies로 현재 설정을 읽고,setGlobalPolicies·removeGlobalPolicies·changeContractPolicies·removeContractPolicies로 바꿉니다. - 주기 거래량 조회 —
globalPeriodicVolume,contractPeriodicVolume,globalPeriodicList,contractPeriodicList으로 주기 상한에 대한 현재 사용량을 읽습니다. - 규제 실행 —
deployPclProxy로 프록시를 배포하고pclProxy로 조회하며, PCL에 등록된 프록시가 매 사용자 호출을 감쌀 때 훅 메서드preCall과postCall을 호출합니다.
PCL 프리컴파일에는 정책 검사와 대상 함수 실행을 한 호출로 처리하는 진입점이 없습니다. 프록시를 거치는 컨트랙트의 컨트랙트 범위 강제는 프록시의 훅 경로를 통해 흐릅니다. Privacy 같은 정책 인식 프리컴파일은 자체 컨트랙트 정책을 자체 경계에서 직접 평가합니다.
강제 지점
preCall / postCall 훅을 호출할 수 있으며, 정책 인식 프리컴파일인 Privacy 프리컴파일은 자체 상태 변경 전후에 자체 경계에서 자신의 컨트랙트 정책을 평가합니다. privacy-policy-aware-precompile 참고).1. 트랜잭션 진입과 종료 — 대상과 무관하게 모든 EVM 트랜잭션에 전역
GlobalPolicyConfig가 평가됩니다. 단, 정책 관리자가 PCL에 직접 보내는 registerPolicyTemplate / removePolicyTemplate / setGlobalPolicies / removeGlobalPolicies 호출은 이 평가를 건너뜁니다(pcl-policy-admin 참고). 실행 전(RPC 제출 시점과 체인의 실행 전 검사)에 평가되고, 실행이 성공한 뒤 발신자와 트랜잭션이 수행한 모든 호출에 대해 다시 평가됩니다. 실행 후 위반이 발생하면 트랜잭션은 실패합니다. 상태는 되돌려지지만 블록에는 포함되며 가스비도 부과됩니다.2. 프록시 훅 경로 — 호출이 PCL 등록 프록시를 대상으로 할 때(
pcl-proxy-hook 참고), 프록시는 실제 실행 전에 IPcl.preCall(...)을, 실행 후에 IPcl.postCall(...)을 호출합니다. 프록시의 ContractPolicyConfig는 여기서 발동합니다.프록시를 우회해 컨트랙트 구현체를 직접 호출하는 트랜잭션은 전역 설정만 평가받습니다. 따라서 사용자 대상 진입점으로 프록시 주소를 공개하는 것이 규제 트랙을 실제로 작동시키는 방법입니다.
현재 상태 조회
eth_call을 통한 오프체인 호출 시 가스가 들지 않습니다. 온체인 호출자(자신의 실행 중에 PCL 상태를 읽는 컨트랙트)는 일반 가스를 소모합니다.import { createPublicClient, http } from "viem";
const PCL = "0x1000000000000000000000000000000000000005" as const;
const pclAbi = [
{ type: "function", name: "policyAdmin", stateMutability: "view",
inputs: [], outputs: [{ type: "address" }] },
{ type: "function", name: "globalPolicies", stateMutability: "view",
inputs: [], outputs: [{ type: "tuple", components: [
{ name: "policies", type: "tuple[]", components: [
{ name: "templateId", type: "string" },
{ name: "policy", type: "bytes" },
{ name: "selector", type: "bytes" },
] },
] }] },
] as const;
const client = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });
const admin = await client.readContract({ address: PCL, abi: pclAbi, functionName: "policyAdmin" });
const global = await client.readContract({ address: PCL, abi: pclAbi, functionName: "globalPolicies" });
console.log("정책 관리자:", admin, "활성 전역 정책 수:", global.policies.length); 실패 표면
IPcl의 타입 지정 커스텀 오류로 나타납니다. 흔한 ReasonCode는 EasNoAttestationReceived, EasAttestationRevoked, EasAttestationExpired, InDenylist, ExceededPeriodicVolume, VolumeAboveMaxLimit, ExceededAgentTransferLimit입니다. 설정 및 관리자 가드 실패는 Unauthorized, UnknownPolicyType(알려진 정책 유형이 아닌 templateId), ContractPolicyNotRegistered, PclProxyNotRegistered를 사용합니다. PolicyTemplateNotFound는 등록되지 않은 ID를 policyTemplate(templateId) 뷰로 조회할 때만 반환됩니다. 단일 ContractPolicyConfig(또는 전역 설정) 안에서 selector가 중복되는 경우도 타입 지정 오류 InvalidParameter(bytes input)로 revert됩니다. 이때 input은 해당 selector를 텍스트로 담은 값으로, 0x 없는 소문자 16진수이며 빈 selector는 __all__입니다.