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
  }
}
ESC
검색어를 입력하세요