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 금액으로 단위는 atokrw입니다. 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됩니다. 조합 정책 안에서는 조합 정책이 결과를 정합니다. 정량자가 Or인 LOGICAL_POLICY 아래에서는 모든 자식이 실패할 때만 이 오류가 AnyOfRejected의 자식 중 하나로 나타납니다. 테스트넷의 전역 정책은 일일 상한을 principal의 에이전트 소유자마다(소유자가 없으면 principal 자신에게) 적용하고 그 계정의 EAS attestation과 Or로 묶어 두므로, 테스트넷에서 상한을 넘으면 그 attestation 검사를 통과하지 않는 한 AnyOfRejected로 revert됩니다. |
EasNoAttestationReceived | EasNoAttestationReceived | 대상의 EAS_POLICY가 설정된 스키마로 principal이 받은 attestation을 EAS Indexer에서 찾지 못할 때 revert됩니다. 발급된 적이 없거나, 발급되었지만 인덱싱되지 않은 경우입니다(Indexer의 indexAttestation(uid)로 인덱싱합니다). |
EasAttestationRevoked | EasAttestationRevoked | principal이 그 스키마로 받은 attestation 중 가장 최근에 인덱싱된 것이 폐기되었을 때 revert됩니다. 더 오래된 유효한 attestation은 인정되지 않습니다. |
EasAttestationExpired | EasAttestationExpired | 그 attestation의 expirationTime이 지났을 때 revert됩니다. |
EasAttestationLookupFailed | EasAttestationLookupFailed | EAS Indexer가 principal에 대해 알려 준 attestation을 정책의 EAS 컨트랙트(easContract)에서 읽지 못할 때 revert됩니다. 그 주소를 확인합니다. |
EasAttestationRequired | EasAttestationRequired | 같은 오류 집합에 선언되어 있지만 이 오류를 발생시키는 정책은 없습니다. 인덱싱된 attestation이 없는 principal은 EasNoAttestationReceived로 revert됩니다. |
예제
PCL 프록시가 자기 대상에 대해 preCall 호출
고전적인 PCL 프록시 훅 경로입니다. 프록시 자기 주소가 contractAddress와 일치해야 하며, 실제 사용자는 principal로 전달됩니다.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.28;
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 { BaseError, ContractFunctionRevertedError, createPublicClient, http, decodeErrorResult } from "viem";
import { iPclAbi } from "@maroo-chain/contracts/abi/precompiles/pcl/IPcl";
const PCL = "0x1000000000000000000000000000000000000005" 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) {
// viem keeps the revert data on the ContractFunctionRevertedError in the cause chain, not on err.
const revert = err instanceof BaseError ? err.walk((e) => e instanceof ContractFunctionRevertedError) : null;
if (revert instanceof ContractFunctionRevertedError && revert.raw) {
// iPclAbi lists every IPcl ReasonCode and the IPrecompile errors it inherits. A composite (an Or) that
// rejects — how the testnet's global caps reject — arrives as AnyOfRejected, carrying each child's revert.
const decoded = decodeErrorResult({ abi: iPclAbi, data: revert.raw });
console.error("PCL preCall rejected:", decoded.errorName, decoded.args);
} else {
throw err; // not a revert (a wrong ABI, a network error): let it surface
}
}