PCL 정책 강제
PCL이 모든 트랜잭션에 대해 활성 정책을 평가하는 방식을 설명합니다. 전역 규칙은 실행 전에, 컨트랙트 범위 규칙은 규제 EVM 경로에서 평가됩니다.
마루의 모든 트랜잭션은 상태 변경 로직이 실행되기 전에 PCL을 통과합니다. 정책 관리자가 설정한 GlobalPolicyConfig의 전역 정책은 모든 트랜잭션에 대해 실행 전에(블록에 포함되기 전에) 평가됩니다. 컨트랙트 관리자가 등록한 ContractPolicyConfig의 컨트랙트 범위 정책은 규제 EVM 호출 경로(runOnPcl / preCall + postCall)로 해당 컨트랙트를 대상으로 하는 트랜잭션에만 적용됩니다. ERC-4337 userOp는 PCL이 번들러가 아니라 실제 principal(UserOperation을 발행한 스마트 계정)을 보도록 언랩됩니다.
아키텍처
flowchart TD
A[Client submits tx] --> B{Pre-execution global check}
B -->|Evaluate GlobalPolicyConfig| C{Global policies pass?}
C -->|No| R1[Broadcast reject<br/>typed ReasonCode]
C -->|Yes| D{ERC-4337 handleOps?}
D -->|Yes| E[Unwrap userOp<br/>principal = userOp.sender]
D -->|No| F[principal = tx signer]
E --> G{Regulated EVM path?}
F --> G
G -->|runOnPcl / preCall| H[Evaluate<br/>ContractPolicyConfig]
G -->|Direct call| I[Skip contract policies]
H -->|Fail| R2[On-chain revert<br/>typed ReasonCode<br/>status=0]
H -->|Pass| J[Execute target]
I --> J
classDef evm fill:#0096AA,stroke:#0096AA,color:#fff;
classDef precompile fill:#FF8C50,stroke:#FF8C50,color:#fff;
class A,C,D,E,F,G,I,J,R1,R2 evm;
class B,H precompile; 두 가지 강제 지점을 보여줍니다. 실행 전 전역 검사는 브로드캐스트 전에 전역 정책을 실행하고, 규제 EVM 경로는 실행 시점에 컨트랙트 범위 정책을 실행합니다. ERC-4337 userOp은 언랩되어 번들러가 아니라 스마트 계정 principal이 정책 대상이 됩니다.
두 가지 강제 지점
PCL은 트랜잭션 생명주기의 서로 다른 두 지점에서 실행됩니다.
- 실행 전 전역 검사 —
GlobalPolicyConfig를 검사합니다. 전역 정책 중 하나라도 거절하면 트랜잭션은 블록에 진입하지 못합니다. 지갑은 오류 페이로드에 ABI 인코딩된 ReasonCode가 담긴 브로드캐스트 수준 거절을 받습니다. - 규제 EVM 경로(
runOnPcl/preCall+postCall) — 대상 컨트랙트의ContractPolicyConfig를 검사합니다. 컨트랙트 범위 정책은 호출별 opt-in이므로 이 경로를 통과하는 트랜잭션에만 적용됩니다. 컨트랙트 소유자는registerContractPolicies로 정책을 부착합니다.
ERC-4337(계정 추상화) principal 해석
ERC-4337
결과: 스마트 계정(
handleOps 번들에서 PCL은 번들러를 호출자로 취급하지 않습니다. 각 UserOperation을 언랩하여 userOp의 sender(호출을 실행하는 스마트 계정)를 principal로 삼아 정책을 평가합니다. 이는 전역 검사와 컨트랙트 범위 검사 모두에 적용됩니다.결과: 스마트 계정(
userOp.sender)이 전역 denylist에 있으면 번들 트랜잭션 전체가 브로드캐스트 전에 실행 전 전역 검사에서 거절됩니다. 지갑은 온체인 영수증 없이 브로드캐스트 수준 거절을 받으며, 이는 직접 denylist된 EOA와 동일합니다. userOp이 일반 호출을 하든, ERC20 전송을 하든, runOnPcl을 사용하든, 내부 호출을 하든 동일하게 적용됩니다. 이 거절이 발생하기 위해 번들러 자체가 denylist될 필요는 없습니다.// viem으로 AA 브로드캐스트 수준 denylist 거절 감지
import { decodeErrorResult } from "viem";
try {
await walletClient.writeContract({
address: entryPoint, // ERC-4337 EntryPoint
abi: entryPointAbi,
functionName: "handleOps",
args: [[userOp], beneficiary],
});
} catch (err: any) {
// 브로드캐스트 거절: 영수증 없음. revert 페이로드는 ABI 인코딩된 IPcl 오류입니다.
// 타입이 있는 오류로 디코드합니다 — 오류 메시지 문자열 매칭은 사용하지 않습니다.
const decoded = decodeErrorResult({ abi: pclAbi, data: err.data });
if (decoded.errorName === "InDenylist") {
console.log("userOp.sender가 전역 denylist에 있어 번들이 mempool을 벗어나지 못했습니다.");
} else {
console.log("PCL ReasonCode:", decoded.errorName, decoded.args);
}
} 실행 전 전역 평가
전역 평가는 실행 전에 수행됩니다 — 전역 정책에 걸린 트랜잭션은 블록에 포함되기 전에 타입이 있는 ReasonCode로 거부됩니다. PCL은 현재
GlobalPolicyConfig를 로드하여 그 안의 모든 PolicySet을 트랜잭션에 대해 평가합니다. 단 하나의 정책이라도 거절하면 트랜잭션은 IPcl의 대응되는 typed 오류와 함께 폐기됩니다(pcl-reason-codes 참고). 클라이언트는 영수증을 받지 못하며, 거절은 페이로드에 ABI 인코딩된 IPcl 오류가 담긴 브로드캐스트 오류로 노출됩니다.runOnPcl을 통한 컨트랙트 범위 평가
컨트랙트 범위 정책은 호출이 규제 EVM 경로로 대상에 도달할 때만 평가됩니다. 진입 형태는 두 가지입니다.
이 경로를 우회하여 컨트랙트를 직접 호출하면 해당 컨트랙트의 PolicySet은 적용되지 않지만, 전역 설정은 여전히 적용됩니다.
runOnPcl(contractAddress, data, value)— EOA 또는 컨트랙트에서 일회성 규제 호출입니다. PCL이 대상의ContractPolicyConfig를 평가한 뒤 호출을 대상으로 전달합니다.preCall/postCall(PCL 래핑 프록시가 사용) — 래핑된 프록시 패턴에서는 프록시의 hook이 실제 principal을 명시적으로 전달합니다(pcl-proxy-hook참고). PCL은 직접 호출자인msg.sender가 아니라principal을 발신자 측 검사 대상으로 삼습니다.
이 경로를 우회하여 컨트랙트를 직접 호출하면 해당 컨트랙트의 PolicySet은 적용되지 않지만, 전역 설정은 여전히 적용됩니다.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import { IPcl } from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
contract RegulatedCaller {
address constant PCL = 0x1000000000000000000000000000000000000005;
// `token.transfer(to, amount)` 호출을 규제 경로로 라우팅하여
// 전역 정책과 컨트랙트 범위 정책 모두 적용되도록 합니다.
function compliantTransfer(address token, address to, uint256 amount) external {
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount);
IPcl(PCL).runOnPcl(token, data, 0);
}
} 결합 시맨틱
임의의 트랜잭션에 대해:
1. 실행 전 전역 검사가
2. 트랜잭션이
3. ERC-4337 번들에서는 1단계와 2단계가 번들러가 아니라 userOp의 principal(스마트 계정)을 발신자 기반 검사 대상으로 삼습니다.
1단계와 2단계는 AND 조건입니다. 호출이 성공하려면 적용 가능한 모든 정책을 통과해야 합니다. 문제되는 정책을 등록 해제하지 않고는 우회하거나 override할 방법이 없습니다.
1. 실행 전 전역 검사가
GlobalPolicyConfig의 모든 정책을 평가합니다. 하나라도 실패하면 실행 전에 트랜잭션이 typed ReasonCode와 함께 거절됩니다.2. 트랜잭션이
ContractPolicyConfig가 등록된 컨트랙트를 대상으로 하고 규제 경로로 진입하면, 그 설정의 모든 정책도 평가됩니다. 하나라도 실패하면 호출 전체가 typed ReasonCode와 함께 revert됩니다.3. ERC-4337 번들에서는 1단계와 2단계가 번들러가 아니라 userOp의 principal(스마트 계정)을 발신자 기반 검사 대상으로 삼습니다.
1단계와 2단계는 AND 조건입니다. 호출이 성공하려면 적용 가능한 모든 정책을 통과해야 합니다. 문제되는 정책을 등록 해제하지 않고는 우회하거나 override할 방법이 없습니다.
클라이언트 측 감지
처리해야 할 실패 표면은 두 가지입니다.
가스 소비와 브로드캐스트 거절 리스크 없이 사전 시뮬레이션을 하려면
- 브로드캐스트 수준(실행 전) 거절 — 영수증도 트랜잭션 해시도 없습니다. revert 데이터에 타입이 있는 ReasonCode가 담깁니다. 전역 정책 위반과, principal이 전역 denylist에 있는 AA 번들 모두 이 경로로 표면화됩니다.
- 온체인 revert —
runOnPcl을 통해 도달한 컨트랙트 범위 거절은status = 0인 영수증을 남기며, revert 데이터에 typed IPcl 오류가 포함됩니다.decodeErrorResult로 IPcl ABI에 대해 디코드합니다.
가스 소비와 브로드캐스트 거절 리스크 없이 사전 시뮬레이션을 하려면
runOnPcl을 eth_call로 호출합니다(simulating-pcl-checks 참고).