PCL 이중 트랙 트랜잭션 모델

mechanism compliance

트랜잭션을 개방형 무허가 컨텍스트 또는 규제 준수 컨텍스트에서 실행합니다. 규제 트랙에서 발생한 내부 전송은 발신자 범위 정책 검사에서 원래 호출자에게 귀속됩니다.

마루의 PCL은 스마트 컨트랙트 상호작용에 두 가지 실행 컨텍스트를 제공합니다. 개방형 트랙은 표준 EVM 호출로, 컨트랙트 범위 정책 검사를 우회합니다(전역 정책은 여전히 적용됩니다). 규제 트랙은 IPcl.runOnPcl(...) 또는 PCL 래핑 프록시를 통한 선택적 흐름이며, 래핑된 호출을 실행하기 전에 적용 가능한 모든 전역 및 컨트랙트 범위 정책을 강제합니다. 규제 프레임 내부에서는 대상 컨트랙트가 발생시킨 내부 전송도 관찰하며, 발신자 범위 전역 검사(예: EAS attestation)에서 이 내부 전송을 원래 호출자에게 귀속시킵니다. 따라서 컨트랙트가 자금을 대는 지급이라도 원래 principal이 발신자 컴플라이언스 요건을 충족해야 합니다.

두 트랙, 하나의 강제 엔진

직접 EVM 호출에 대해 규제 트랙을 선택하지 않아도 전역 정책은 모든 트랜잭션에 적용됩니다. 규제 트랙을 선택하면 그 위에 컨트랙트 범위 정책과 호출 이후 내부 전송 관찰이 추가됩니다. 진입점은 두 가지로, 일회성 규제 실행에 사용하는 IPcl.runOnPcl(target, data, value)와 항상 규제 컨텍스트로 호출되어야 하는 컨트랙트를 위한 PCL 래핑 프록시(deployPclProxy / PclTransparentUpgradeableProxy)가 있습니다.
// 개방형 트랙 — 직접 호출입니다. 전역 정책은 적용되지만 컨트랙트 범위 정책은 적용되지 않습니다.
IERC20(token).transfer(recipient, 10_000_000 ether);

// 규제 트랙 — PCL을 통해 라우팅합니다. 전역 + 컨트랙트 범위 정책이 모두 적용되며,
// PCL이 `target`에서 발생하는 내부 전송을 관찰합니다.
IPcl(0x1000000000000000000000000000000000000005).runOnPcl(
    target,
    abi.encodeCall(IPayout.pay, (recipient, 10_000_000 ether)),
    0
);

내부 전송과 호출자 귀속

규제 프레임 내부에서 PCL은 래핑된 호출 동안 대상 컨트랙트가 발생시킨 모든 네이티브 및 ERC-20 전송을 수집합니다. 각 전송은 관찰된 그대로(from = 실제 발신자, to = 실제 수신자) 컨트랙트 범위 정책에 대해 평가합니다. 발신자 범위 전역 정책(발신자 denylist, 발신자 EAS attestation, 발신자 주기 거래량 상한)에서는 PCL이 내부 전송을 중간 컨트랙트가 아닌 원래 호출자(규제 프레임에 진입한 EOA 또는 AA principal)에게 귀속시킵니다. 이 재귀속이 없으면, 인증된 사용자가 지급 컨트랙트를 호출할 때 컨트랙트가 나가는 송금 구간에 자금을 대는 경우마다 attestation 검사에 실패하게 됩니다. 컨트랙트 자체는 인증되어 있지 않기 때문입니다.

자금 출처에 따른 누적 차이

구체적으로, 호출자가 msg.value 또는 transferFrom(caller, …)를 통해 자금을 대면 그 전송은 호출자의 출금으로 간주되어 주기 거래량 상태 기록을 포함해 전부 반영됩니다. 대상 컨트랙트가 자체 잔고로 전송에 자금을 대면, 그 전송도 마치 호출자가 발신자인 것처럼 발신자 범위 전역 정책에 대해 평가되지만, 주기 거래량 상태는 호출자에게 누적되지 않습니다. 실제 호출자의 출금만 누적됩니다.
// 호출자가 자금을 댄 출금 — 주기 거래량 누적을 포함해 전부 반영됩니다.
// PCL은 모든 정책 범위에서 이를 호출자 → 수신자로 봅니다.
runOnPcl(payout, abi.encodeCall(IPayout.payFromSender, (recipient, 10_000_000 ether)), 10_000_000 ether);

// 컨트랙트가 자금을 댄 출금 — 지급 컨트랙트가 자체 잔고로 recipient.call{value: X}(...)를 호출합니다.
// PCL은 발신자 범위 전역 정책(denylist, EAS attestation, 주기 상한)을 마치 호출자가 발신자인 것처럼
// 여전히 평가합니다. 다만 이 금액을 호출자의 주기 거래량 카운터에는 더하지 않습니다.

어떤 트랙을 선택할지

전역 정책 준수만 필요한 상호작용에는 개방형 트랙을 선택합니다. 대부분의 일반 DeFi 호출, 읽기 경로, 규제되지 않은 주소 간 일반 ERC-20 전송이 여기에 해당합니다. 대상 컨트랙트가 강제하려는 ContractPolicyConfig를 가지는 경우(KYC 게이팅 풀, 규제 오프램프, 규제 스테이블코인 지급) 또는 컨트랙트가 나가는 송금 구간에 자금을 댈 때에도 PCL이 내부 전송을 관찰하고 발신자 범위 전역 규칙을 principal에 대해 강제하기를 원할 때 규제 트랙을 선택합니다. dApp UI는 사용자가 어떤 정책이 트랜잭션을 게이팅하는지 이해할 수 있도록 각 작업이 어느 트랙을 사용하는지 표시해야 합니다.

runOnPcl 시뮬레이션으로 클라이언트 측 검증

전송 전 시뮬레이션 UX에는 IPcl.runOnPcl 자체를 eth_call로 시뮬레이션합니다. 오프체인 시뮬레이션은 아무것도 기록하지 않으므로, 전역 + 컨트랙트 범위 평가 전체를 수행해 사용자가 만나게 될 정확한 ReasonCode를 gas 소비 없이 보여줍니다(contract-pcl-run-simulation 참고). 이 용도로 preCall / postCall을 직접 호출하지 마세요. 둘은 PCL 프록시 훅의 내부 페어이며, 직접 호출자가 등록된 PCL 프록시가 아니면 preCallUnauthorized로 revert됩니다(contract-pcl-pre-call 참고).
import { createPublicClient, http } from "viem";

const PCL = "0x1000000000000000000000000000000000000005";
const publicClient = createPublicClient({ transport: http(MAROO_RPC) });

try {
  // eth_call 시뮬레이션: 전체 정책 평가, 기록 없음, gas 소비 없음.
  await publicClient.simulateContract({
    address: PCL,
    abi: pclAbi,
    functionName: "runOnPcl",
    args: [targetContract, callData, valueWei],
    account: principal,
    value: valueWei,
  });
  // 정책 통과 — 실제 트랜잭션을 보내도 안전합니다.
} catch (err) {
  // revert 데이터에서 타입이 있는 PCL ReasonCode를 디코드해 사용자에게 표시합니다.
}
소스: maroo
ESC
검색어를 입력하세요