IPcl.deployPclProxy
deployPclProxy(
PclProxyKind kind,
uint256 value,
bytes calldata initData
) external returns (address proxy) 요청한 kind의 PCL 래핑 프록시를 체인 바이너리에 내장된 표준 바이트코드로 배포하고, 온체인 프록시 레지스트리에 등록하며, 직접 호출자(msg.sender)를 초기 정책 관리자로 설정합니다. 반환된 proxy 주소가 dApp이 표준 컨트랙트 주소로 공개해야 할 주소입니다. 이 등록된 프록시를 경유하는 호출만 컨트랙트 범위 PCL 강제 경로(preCall / postCall)를 발동시키기 때문입니다. 배포 후 IPcl.pclProxy(proxy)는 프록시의 kind, 현재 admin, 프록시 주소 자체를 담은 레지스트리 엔트리를 반환합니다. changeContractPolicies로 컨트랙트 관리자를 인계하면 이 admin 필드도 함께 갱신됩니다.
파라미터
| 이름 | 타입 | 필수 | 설명 |
|---|---|---|---|
kind | PclProxyKind | ✓ | Transparent(1), UUPS(2), Beacon(3) 중 하나입니다. Unspecified(0)를 전달하면 거절된 kind 바이트를 담은 InvalidParameter(bytes input)로 revert됩니다. Diamond는 예약되어 있으나 구현되어 있지 않습니다. |
value | uint256 | ✓ | 프록시 생성자에 전달할 네이티브 OKRW 금액(aokrw 단위)입니다. 프록시 초기화 함수가 payable로 값을 받지 않는다면 보통 0을 전달합니다. |
initData | bytes | ✓ | kind별 ABI 인코딩된 생성자 인수입니다. Transparent는 abi.encode(logic, initialOwner, initializer), UUPS는 abi.encode(logic, initializer) 형태입니다. initializer는 배포 이후 로직 컨트랙트에 대해 실행할 calldata이며, 초기화가 필요 없으면 "0x"를 전달합니다. |
반환값
address 새로 배포되고 등록된 PCL 래핑 프록시 주소입니다. 사용자 트랜잭션이 이 주소를 대상으로 호출하며, changeContractPolicies로 정책을 이 주소에 바인딩합니다.
에러
| 코드 | 이름 | 설명 |
|---|---|---|
InvalidParameter | InvalidParameter(bytes input) | kind가 Unspecified(0)이거나 체인에 표준 프록시 바이트코드가 없는 값일 때 revert됩니다. input은 거절된 kind 바이트입니다. 이 검사는 initData 디코딩보다 먼저 수행되므로, 지원되지 않는 kind는 디코딩 실패가 아니라 이 오류로 드러납니다. |
AbiDecodeFailed | AbiDecodeFailed | initData가 요청한 kind가 기대하는 레이아웃으로 디코딩되지 않을 때 revert됩니다. |
InternalError | InternalError | 체인 레이어에서 프록시 배포 또는 레지스트리 쓰기에 실패했을 때 revert됩니다. |
예제
Transparent 프록시 배포 및 admin 확인
레지스트리 엔트리는 프록시 배포와 원자적으로 기록되므로, deployPclProxy가 반환한 직후 entry.admin이 deployer.address와 같습니다. 이후 admin을 인계하려면 changeContractPolicies를 사용하며, 이때 이 레지스트리 필드도 함께 갱신됩니다.
import { createWalletClient, createPublicClient, http, encodeAbiParameters } from "viem";
import { privateKeyToAccount } from "viem/accounts";
const PCL = "0x1000000000000000000000000000000000000005" as const;
const pclAbi = [
{ type: "function", name: "deployPclProxy", stateMutability: "payable",
inputs: [
{ name: "kind", type: "uint8" },
{ name: "value", type: "uint256" },
{ name: "initData", type: "bytes" },
],
outputs: [{ name: "proxy", type: "address" }] },
{ type: "function", name: "pclProxy", stateMutability: "view",
inputs: [{ name: "proxy", type: "address" }],
outputs: [{ type: "tuple", components: [
{ name: "kind", type: "uint8" },
{ name: "admin", type: "address" },
{ name: "proxy", type: "address" },
]}] },
] as const;
const deployer = privateKeyToAccount(process.env.DEPLOYER_KEY as `0x${string}`);
const wallet = createWalletClient({ account: deployer, transport: http("https://rpc-testnet.maroo.io") });
const pub = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });
// TODO: replace with the real ERC20 implementation address before production.
const implAddress = "0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62";
// TODO: replace with the real proxy admin (multisig) before production.
const initialOwner = "0x2c7F09b81A6D3fF1e5A0D4c6Bc2A8f7E19dC3a4B";
const initData = encodeAbiParameters(
[{ type: "address" }, { type: "address" }, { type: "bytes" }],
[implAddress, initialOwner, "0x"],
);
const proxyAddress = await wallet.writeContract({
address: PCL,
abi: pclAbi,
functionName: "deployPclProxy",
args: [1 /* Transparent */, 0n, initData],
});
// The initial admin is msg.sender at deployment time — the `deployer` EOA here.
const entry = await pub.readContract({
address: PCL, abi: pclAbi, functionName: "pclProxy", args: [proxyAddress],
});
console.log(`kind=${entry.kind} admin=${entry.admin} proxy=${entry.proxy}`); 팩토리가 admin이 되는 경우 — changeContractPolicies로 인계
초기 admin이 직접 호출자(msg.sender)이므로, 사용자를 대신해 프록시를 배포하는 팩토리 컨트랙트가 admin이 됩니다. 같은 트랜잭션에서 changeContractPolicies로 원래 의도한 admin으로 인계할 수 있으며, 프록시 레지스트리 엔트리의 admin 필드도 자동으로 동기화됩니다.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import {
IPcl,
PclProxyKind,
ContractPolicyConfig,
PolicySet
} from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";
contract TokenFactory {
IPcl constant pcl = IPcl(0x1000000000000000000000000000000000000005);
/// @notice Deploy a PCL-wrapped proxy and immediately hand admin to `endUser`.
/// @dev The factory itself becomes the initial admin because msg.sender is the
/// immediate caller of the PCL precompile. `changeContractPolicies` (the
/// admin-rotation path) can then transfer authority in the same tx.
function deployAndHandoff(
bytes calldata initData,
address endUser,
PolicySet[] calldata policies
) external returns (address proxy) {
proxy = pcl.deployPclProxy(PclProxyKind.Transparent, 0, initData);
// Factory is currently the admin -> rotate it to `endUser` while
// installing the initial policy set.
pcl.changeContractPolicies(ContractPolicyConfig({
_contract: proxy,
admin: endUser,
policies: policies
}));
}
}