IPcl.preCall
preCall(
address contractAddress,
address principal,
bytes calldata data,
uint256 value
) external returns (bytes32 sessionId) 규제 대상 호출에 대해 PCL 강제 세션을 엽니다. 직접 호출자(msg.sender)는 두 종류 중 하나여야 합니다. 자기 자신을 대상으로 호출하는 PCL 등록 프록시(contractAddress == msg.sender)이거나, 거버넌스가 신뢰하는 ERC-4337 EntryPoint가 번들을 실행하는 경우입니다. 두 경우 모두 PCL은 전달된 principal(호출을 시작한 EOA 또는 스마트 계정)을 기준으로 전역·컨트랙트 범위 정책을 평가하며, 데이터에서 추출한 4바이트 selector와 value를 사용합니다. 실행 전 정책 중 하나라도 실패하면 해당 PCL ReasonCode로 revert됩니다. 통과하면 PCL은 내부 트레이서를 설치하고, 파생된 sessionId를 키로 세션을 등록한 뒤 반환합니다. 이어지는 postCall 호출은 동일한 sessionId를 제시해 세션을 닫고 실행 후 평가를 수행해야 합니다. staticcall(readonly) 실행에서는 영값 bytes32를 반환하며 세션을 열지 않습니다. 정책 상태는 캐시된 컨텍스트에서 평가된 뒤 폐기됩니다.
파라미터
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
contractAddress | address | ✓ | 규제 대상 호출의 대상 컨트랙트입니다. 호출자가 PCL 프록시일 때는 반드시 msg.sender와 같아야 합니다. 그렇지 않으면 Unauthorized로 revert됩니다. 호출자가 신뢰하는 EntryPoint일 때는 이 제약이 완화됩니다. EntryPoint가 SmartAccount로 디스패치하고 SmartAccount가 대상을 호출하기 때문입니다. |
principal | address | ✓ | 의미상 관련 있는 호출자로, 호출을 시작한 EOA 또는 ERC-4337 스마트 계정 주소입니다. 프록시 또는 EntryPoint가 이 값을 그대로 전달해야 하며, PCL은 이를 컨트랙트 범위 denylist·주기 거래량 검사의 발신자로 사용합니다. PCL이 직접 호출자(프록시 또는 EntryPoint)만 본다면 발신자 측 규칙이 실제 사용자와 매칭될 수 없습니다. |
data | bytes | ✓ | 위임되는 콜데이터입니다. PCL은 앞 4바이트를 함수 selector로 잘라 selector 범위 정책 매칭에 사용합니다. 4바이트보다 짧으면 selector가 없는 것(빈 selector)으로 간주합니다. |
value | uint256 | ✓ | 실제 호출에 첨부되는 네이티브 OKRW 금액으로 단위는 aokrw입니다. 0이 아닌 값은 네이티브 denom에 대한 VOLUME_POLICY / PERIODIC_VOLUME_POLICY 평가에 반영됩니다. |
반환값
bytes32 호출 메타데이터와 트랜잭션 내 시퀀스에서 파생된 세션 식별자입니다. 이어지는 postCall이 이 값을 그대로 되돌려주어야 합니다. staticcall에서는 영 해시를 반환합니다.
에러
| 코드 | 이름 | 설명 |
|---|---|---|
Unauthorized | Unauthorized | 직접 호출자가 등록된 PCL 프록시도 아니고 신뢰하는 EntryPoint도 아닐 때, 또는 PCL 프록시 호출자가 자기 주소와 다른 contractAddress를 넘길 때 revert됩니다. |
InDenylist | InDenylist | 해결된 principal(또는 대상 컨트랙트)이 컨트랙트 정책 또는 전역 정책의 denylist 항목과 일치할 때 revert됩니다. |
VolumeAboveMaxLimit | VolumeAboveMaxLimit | 첨부된 네이티브 값이 단독으로 VOLUME_POLICY의 최대 한도를 초과할 때 revert됩니다. |
ExceededPeriodicVolume | ExceededPeriodicVolume | 첨부된 네이티브 값이 현재 윈도의 PERIODIC_VOLUME_POLICY 한도를 principal이 초과하도록 만들 때 revert됩니다. |
EasAttestationRequired | EasAttestationRequired | EAS_POLICY가 바인딩된 대상이 principal에 대한 attestation을 요구하지만 존재하지 않을 때 revert됩니다. |
예제
PCL 프록시가 자기 대상에 대해 preCall 호출
고전적인 PCL 프록시 훅 경로입니다. 프록시 자기 주소가 contractAddress와 일치해야 하며, 실제 사용자는 principal로 전달됩니다.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import {IPcl, PCL_CONTRACT} from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
contract RegulatedProxy {
address internal immutable implementation;
constructor(address impl) { implementation = impl; }
/// @notice A PCL-wrapped proxy dispatches user calls through preCall/postCall.
/// msg.sender at the PCL precompile will be `address(this)` — the proxy itself.
function forward(bytes calldata data) external payable returns (bytes memory) {
// The proxy passes itself as `contractAddress` (must equal msg.sender at the precompile),
// and the real caller as `principal`.
bytes32 sessionId = PCL_CONTRACT.preCall(address(this), msg.sender, data, msg.value);
(bool ok, bytes memory ret) =
implementation.delegatecall(data);
// Any implementation revert is surfaced to postCall via `workable=false`.
PCL_CONTRACT.postCall(sessionId, address(this), msg.sender, data, msg.value, ok);
require(ok, "impl reverted");
return ret;
}
} 클라이언트에서 preCall revert 디코드
모든 preCall 실패는 IPcl의 타입 지정 오류입니다. 인터페이스 ABI로 디코드해서 구체적인 ReasonCode에 따라 UX를 분기합니다.
import { createPublicClient, http, decodeErrorResult } from "viem";
const PCL = "0x1000000000000000000000000000000000000005" as const;
const pclAbi = [
{ type: "function", name: "preCall", stateMutability: "nonpayable",
inputs: [
{ name: "contractAddress", type: "address" },
{ name: "principal", type: "address" },
{ name: "data", type: "bytes" },
{ name: "value", type: "uint256" },
], outputs: [{ type: "bytes32" }] },
{ type: "error", name: "Unauthorized", inputs: [] },
{ type: "error", name: "InDenylist", inputs: [{ name: "sender", type: "address" }] },
{ type: "error", name: "ExceededPeriodicVolume",
inputs: [
{ name: "maxLimit", type: "uint256" },
{ name: "value", type: "uint256" },
{ name: "resetAt", type: "uint256" },
] },
] as const;
const publicClient = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });
try {
await publicClient.simulateContract({
address: "0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62", // TODO: replace with real proxy address
abi: [/* proxy ABI */],
functionName: "forward",
args: ["0x"],
});
} catch (err: any) {
if (err?.data) {
const decoded = decodeErrorResult({ abi: pclAbi, data: err.data });
console.error("PCL preCall rejected:", decoded.errorName, decoded.args);
}
}