컨트랙트 정책 관리하기
특정 컨트랙트에 부착된 PolicySet을 PCL 프리컴파일로 업데이트하거나 교체·제거하는 방법을 안내합니다. 컨트랙트를 재배포하거나 글로벌 거버넌스를 거치지 않고도 정책을 갱신할 수 있습니다.
사전 요구사항
- 이미
ContractPolicyConfig가 등록된 배포된 컨트랙트 - 해당 ContractPolicyConfig에 기록된
admin주소를 보유한 지갑
Surface — 사용할 세 가지 호출
IPcl.sol에서:registerContractPolicies(ContractPolicyConfig calldata policy)— 최초 등록에 사용하며, 해당 컨트랙트 주소에 이미 설정이 있으면 revert됩니다.changeContractPolicies(ContractPolicyConfig calldata policy)—PolicySet[]을 전체 교체합니다(admin회전을 선택할 수 있습니다). 호출자는 현재admin이어야 합니다.removeContractPolicies(address contractAddress)— 설정을 완전히 삭제합니다. 호출자는 현재admin이어야 합니다.
"PolicySet 하나 추가" 또는 "하나 삭제" 같은 호출은 없습니다. 의도적으로
change…가 전체 배열을 교체하도록 설계해, 서명한 내용과 온체인 설정이 일치하게 유지됩니다.interface IPcl {
function registerContractPolicies(ContractPolicyConfig calldata policy) external;
function changeContractPolicies(ContractPolicyConfig calldata policy) external;
function removeContractPolicies(address contractAddress) external;
function contractPolicies(address contractAddress)
external view returns (ContractPolicyConfig memory);
} 새 템플릿으로 교체
템플릿 자체는 등록 후 불변입니다(감사 가능성 확보). 규칙을 "업그레이드"하려면 새
PolicySet[]을 빌드해 changeContractPolicies로 제출하며, 이전 배열은 원자적으로 교체됩니다.import { encodeAbiParameters } from "viem";
const PCL = "0x1000000000000000000000000000000000000005";
// 건당 0..10,000,000 OKRW를 허용하는 새 VOLUME_POLICY
const volumePolicy = encodeAbiParameters(
[
{ type: "string[]", name: "tokens" },
{
type: "tuple[]", name: "limits",
components: [
{ type: "uint256", name: "minLimit" },
{ type: "uint256", name: "maxLimit" },
],
},
],
[["aokrw"], [{ minLimit: 0n, maxLimit: 10_000_000n * 10n ** 18n }]],
);
await walletClient.writeContract({
address: PCL,
abi: pclAbi,
functionName: "changeContractPolicies",
args: [{
_contract: tokenAddress,
admin: currentAdmin, // 유지하거나 회전을 위해 새 주소
policies: [{
templateId: "VOLUME_POLICY",
policy: volumePolicy,
selector: "0x",
}],
}],
}); 팁: 컴플라이언스 규칙을 강화하기 전에 사용자에게 알립니다 — 지갑은 마지막으로 받은 ReasonCode 집합을 캐시하므로 실패 UI가 새 코드를 인식하지 못할 수 있습니다.
admin 회전
admin은 ContractPolicyConfig의 한 필드이므로, 회전도 정책 변경과 동일한 흐름을 따릅니다. 새 admin과 (기존 또는 갱신된) policies로 changeContractPolicies를 호출하며, 현재 admin이 서명해야 합니다. 제출 후에는 새 admin만 change… / remove…를 호출할 수 있습니다.await walletClient.writeContract({
address: PCL,
abi: pclAbi,
functionName: "changeContractPolicies",
args: [{
_contract: tokenAddress,
admin: newAdmin, // 회전
policies: existingPolicies, // 그대로 전달
}],
}); 주의: 별도의 `transferAdmin` 호출은 없습니다 — admin 키를 잃으면 설정 변경/삭제 능력을 잃습니다. 회전은 신중히 계획합니다.
설정 제거
컨트랙트를 다시 비규제 주소처럼(GlobalPolicyConfig만 적용되도록) 되돌리려면 ContractPolicyConfig를 완전히 삭제합니다.
await walletClient.writeContract({
address: PCL,
abi: pclAbi,
functionName: "removeContractPolicies",
args: [tokenAddress],
}); 주의: 이는 컨트랙트 스코프 규칙만 제거합니다. 체인 전역 `GlobalPolicyConfig` (denylist, KYC 계층 등)은 여전히 모든 트랜잭션에 적용됩니다.
현재 활성 상태 확인
contractPolicies view로 현재 설정을 다시 읽어옵니다. 지갑의 정책 검사 UI나 방금 설정한 내용을 확인할 때 활용합니다.const cfg = await publicClient.readContract({
address: PCL,
abi: pclAbi,
functionName: "contractPolicies",
args: [tokenAddress],
});
console.log(cfg.admin, cfg.policies.length, cfg.policies.map(p => p.templateId));