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