컨트랙트 정책 관리
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만 적용됩니다.