PCL 컨트랙트 Admin 바인딩

mechanism compliance

모든 컨트랙트 범위 정책 집합에는 온체인 admin이 존재합니다. 이 바인딩은 프록시에 대한 첫 ContractPolicyConfig가 기록될 때 한 번 생성되며, 이후에는 이양만 가능하고 재초기화되지 않습니다.

PCL은 컨트랙트 범위 정책을 오직 PCL에 등록된 프록시, 즉 IPcl.deployPclProxy로 배포된 컨트랙트에 대해서만 강제합니다. 이러한 프록시는 각각 정확히 하나의 컨트랙트 정책 admin을 가지며, 이 주소만 changeContractPoliciesremoveContractPolicies를 호출할 수 있습니다. 이 바인딩은 단일 할당입니다. 특정 프록시에 대한 최초 성공 changeContractPolicies 호출이 policy.admin을 admin으로 저장하며, 이후 모든 호출은 저장된 admin이 서명해야 합니다. 권한을 회전하려면 현재 admin이 페이로드의 admin 값을 다른 주소로 바꾸어 changeContractPolicies를 다시 호출합니다. 정책 교체와 admin 이양이 같은 호출 안에서 원자적으로 이루어집니다.

아키텍처

flowchart LR
  D["dApp / factory"] -->|"deployPclProxy(kind, 0, initData)"| P{"PCL precompile"}
  P -->|"deploy proxy bytecode"| X["PCL-wrapped proxy"]
  P -->|"write admin=msg.sender"| R[("Contract-admin record")]
  P -->|"sync projection"| E[("pclProxy registry entry<br/>kind + admin + proxy")]
  A["Current admin"] -->|"changeContractPolicies(newAdmin, ...)"| P
  P -->|"update admin"| R
  P -->|"sync projection"| E
  classDef evm fill:#0096AA,stroke:#0096AA,color:#fff;
  classDef precompile fill:#FF8C50,stroke:#FF8C50,color:#fff;
  class D,A,X,R,E evm;
  class P precompile;

컨트랙트 관리자 레코드가 기본 진실 원본이며, 프록시 레지스트리 엔트리는 PCL 래핑 프록시에 대해 admin을 반영하고 admin이 변경될 때마다 갱신됩니다.

PCL 등록 프록시만 ContractPolicyConfig를 가질 수 있습니다

changeContractPoliciesremoveContractPolicies는 admin이나 정책 상태를 건드리기 전에, 대상 주소가 PCL에 등록된 프록시인지 먼저 확인합니다. 기본 구현체 주소, EOA, 다른 방식으로 배포된 컨트랙트를 대상으로 호출하면 타입 지정 오류 PclProxyNotRegistered(address contractAddress)로 revert됩니다. 먼저 IPcl.deployPclProxy로 컨트랙트를 프록시 뒤에 배포하십시오. dApp이 사용자에게 공개하는 주소이자 이후 모든 정책 관리 호출이 겨냥해야 하는 주소는 반환된 프록시 주소입니다.

초기화는 단일 할당입니다

admin 바인딩은 특정 프록시에 대한 최초 성공 changeContractPolicies 호출 시점에 생성됩니다. 이 최초 호출 시에는 admin 슬롯이 비어 있어 누구든 값을 설정할 수 있으며, policy.admin이 향후 게이트키퍼가 됩니다. 이후 모든 관리 호출은 저장된 admin이 서명해야 하며, 그렇지 않으면 Unauthorized로 revert됩니다. 이미 바인딩된 프록시를 다시 초기화하는 것은 같은 admin으로도 허용되지 않습니다. 저수준 InitializeContractPolicyAdmin 경로는 admin이 이미 저장되어 있으면 PolicyAlreadyRegistered(contractAddress)로 revert됩니다. 이 인수인계 방지 보장은 의도된 것으로, 컨트랙트가 파괴된 뒤 다른 배포자가 같은 CREATE2 주소에 재배포하더라도 현재 admin이 명시적으로 권한을 이양하지 않는 한 새 배포자는 접근할 수 없습니다.

changeContractPolicies를 통한 이양

별도의 transferAdmin 메서드는 없습니다. admin을 회전하려면 현재 admin이 자기 키로, 페이로드의 admin 필드를 새 주소로 지정하여 changeContractPolicies를 호출합니다. 이 호출은 PolicySet[] 전체 교체와 새 admin 기록을 하나의 상태 전이로 처리하므로, 어느 시점에도 프록시에 admin이 둘 존재하지 않습니다.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;

import {
    IPcl,
    ContractPolicyConfig,
    PolicySet
} from "@maroo-chain/contracts/precompiles/pcl/IPcl.sol";

contract PolicyOwner {
    IPcl constant PCL = IPcl(0x1000000000000000000000000000000000000005);

    /// @notice `proxy`의 정책 권한을 기존 정책을 유지한 채 `newAdmin`으로 이양합니다.
    ///         현재 admin인 이 컨트랙트만 호출할 수 있습니다.
    function handOver(
        address proxy,
        address newAdmin,
        PolicySet[] calldata keepPolicies
    ) external {
        PCL.changeContractPolicies(ContractPolicyConfig({
            _contract: proxy,
            admin:     newAdmin,
            policies:  keepPolicies
        }));
    }
}

removeContractPolicies는 정책만 비우고 권한은 유지합니다

현재 admin이 removeContractPolicies(proxy)를 호출하면 proxy에 바인딩된 모든 PolicySet이 제거되지만 admin 바인딩은 유지됩니다. 이후 같은 admin이 changeContractPolicies를 호출하면 별도의 재등록 절차 없이 정책을 다시 채울 수 있습니다. PCL 등록 전제 조건은 여기에도 동일하게 적용됩니다. 대상이 등록된 PCL 프록시가 아니면 removeContractPolicies 역시 PclProxyNotRegistered(address)로, 호출자가 현재 admin이 아니면 Unauthorized로 revert됩니다.

현재 admin 조회

contractPolicies(address)는 순수 view입니다. 대상 주소의 현재 ContractPolicyConfig(admin + 정책)를 반환하며, 한 번도 바인딩되지 않은 주소에 대해서는 revert 대신 admin이 0이고 policies가 빈 배열인 struct를 반환합니다. 정책 바인딩을 시도하기 전에 프록시가 이미 admin의 통제 아래에 있는지 확인할 때 사용합니다.
import { createPublicClient, http } from "viem";

const PCL = "0x1000000000000000000000000000000000000005" as const;
// TODO: 실제 프록시 주소로 교체하십시오.
const proxyAddress = "0x8f3aC2b1D9e74C05a6B18Fe27dC4913E5A0f7b62";

const publicClient = createPublicClient({ transport: http("https://rpc-testnet.maroo.io") });

const { admin, policies } = await publicClient.readContract({
  address: PCL,
  abi: pclAbi,
  functionName: "contractPolicies",
  args: [proxyAddress],
});

if (admin === "0x0000000000000000000000000000000000000000") {
  console.log("프록시에 아직 정책 admin이 없습니다. 다음 changeContractPolicies 호출자가 admin이 됩니다.");
} else {
  console.log("현재 admin:", admin, "정책 개수:", policies.length);
}
ESC
검색어를 입력하세요