컨트랙트 정책 관리

advanced intermediate

PCL 프리컴파일을 통해 특정 컨트랙트에 부착된 PolicySet을 갱신, 교체, 제거하거나 admin 권한을 이전하는 방법을 다룹니다. 컨트랙트 재배포나 전역 거버넌스를 거치지 않고 수행할 수 있습니다.

사전 요구사항

  • 이미 ContractPolicyConfig가 등록된 배포된 컨트랙트
  • 해당 ContractPolicyConfig에 기록된 admin 주소를 보유한 지갑

먼저 현재 설정을 읽습니다

changeContractPolicies는 설정을 한 번에 통째로 교체합니다. 정책별 delta API는 없습니다. 변경 전에 반드시 기존 설정을 조회해서 로컬에서 policies를 재구성해야 유지하려는 항목이 사라지지 않습니다.
import { createPublicClient, http } from "viem";

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

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

console.log("admin:",    current.admin);
console.log("policies:", current.policies.length);
주의: `policies` 배열을 불완전하게 담아 `changeContractPolicies`를 제출하면 누락된 PolicySet이 온체인에서 사라집니다. 항상 read-then-write로 진행합니다.

PolicySet 추가 또는 교체

항목을 추가하거나 편집하거나 제거한 policies 배열을 로컬에서 재구성한 뒤 현재 admin으로 제출합니다. config.admin에 자기 주소를 넣어 권한을 유지합니다.
import { encodeAbiParameters } from "viem";

// 새 OKRW 전송 상한: 전송당 10,000,000 OKRW.
const limitBytes = encodeAbiParameters(
  [
    { type: "address", name: "easContract" },
    { type: "address", name: "indexContract" },
    { type: "bytes32", name: "schemaUid" },
    { type: "uint256", name: "transferLimitAmount" },
  ],
  [EAS_ADDR, INDEX_ADDR, schemaUID, 10_000_000n * 10n ** 18n],
);

const nextPolicies = [
  ...current.policies,
  {
    templateId: "OKRW_EAS_TRANSFER_LIMIT_POLICY",
    policy:     limitBytes,
    selector:   "0x",
  },
];

await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "changeContractPolicies",
  args: [{
    _contract: tokenAddress,
    admin:     current.admin,   // 현재 admin 유지
    policies:  nextPolicies,
  }],
});

admin 권한 이전

전용 transfer 메서드는 없습니다. 권한 이전은 changeContractPolicies를 통해 이루어집니다. 현재 admin이 config.admin에 새 admin의 주소를 담아 호출을 제출합니다. 트랜잭션이 확정되면 새 주소만이 해당 컨트랙트의 정책을 변경하거나 제거할 수 있고, 이전 admin의 후속 호출은 Unauthorized로 revert됩니다. 권한 이전과 정책 편집은 동일한 호출에서 원자적으로 처리되므로, policies는 그대로 두고 admin만 바꾸는 패턴도 자주 사용합니다.
// 현재 admin이 호출합니다.
const newAdmin = "0xCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCc"; // 프로덕션 전에 교체합니다

await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "changeContractPolicies",
  args: [{
    _contract: tokenAddress,
    admin:     newAdmin,             // 권한 이전: authority가 이동합니다
    policies:  current.policies,     // 정책은 그대로 유지
  }],
});

// 이 시점 이후로 이전 admin은 tokenAddress의 정책을 관리할 수 없습니다.
참고: 제출 전에 새 admin 주소를 오프체인에서 확인합니다. 권한 이전은 일방적으로 되돌릴 수 없으며, 새 admin만이 권한을 다시 넘길 수 있습니다.

admin을 해제하지 않고 정책만 비우기

removeContractPolicies는 컨트랙트의 PolicySet[]을 비우지만 admin 바인딩은 삭제하지 않습니다. 의도된 설계이며, admin 슬롯은 외부 호출로 해제할 수 없습니다. 제거 후에도 동일한 admin이 changeContractPolicies를 다시 호출해 정책을 재작성하거나 권한을 이전할 수 있습니다.
// 현재 admin이 호출합니다. tokenAddress의 모든 PolicySet을 비웁니다.
await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "removeContractPolicies",
  args: [tokenAddress],
});

// tokenAddress의 정책 목록은 비었지만 admin은 동일하게 유지됩니다.
// 새 PolicySet이 부착되기 전까지는 GlobalPolicyConfig만 적용됩니다.
ESC
검색어를 입력하세요