IPcl.changeContractPolicies

changeContractPolicies(ContractPolicyConfig calldata policy) external

Upserts the ContractPolicyConfig for a target address. The target must be either a PCL-registered proxy (deployed via deployPclProxy) OR a binary-supported policy-aware precompile — currently only the Privacy precompile at 0x100000000000000000000000000000000000000b. On the first call for a target, msg.sender may be anyone and the payload's admin becomes the future gatekeeper; on subsequent calls msg.sender must equal the currently stored admin, and the new admin value in the payload replaces the old one atomically with the policy replacement. Passing an empty policies array to a policy-aware precompile is rejected — those targets must always carry at least one PolicySet. Ordinary PCL proxies may carry an empty policies array (equivalent to no contract-scope rules). The chain-wide policyAdmin cannot mutate these configs unless it also happens to be the stored contract admin.

Parameters

Name Type Required Description
policy ContractPolicyConfig The new configuration: _contract is the target address (a registered PCL proxy or a policy-aware precompile address), admin is the next gatekeeper for this target's policy config, and policies is the full replacement PolicySet[]. If _contract is neither a registered PCL proxy nor a policy-aware precompile, the call reverts with PclProxyNotRegistered(contractAddress). If _contract is a policy-aware precompile and policies is empty, the call reverts with CannotEmpty("policy-aware precompile policies").

Returns

Type: void

No return value. Emits ContractPoliciesChanged(contractAddress, admin, policies) on success.

Errors

Code Name Description
Unauthorized Unauthorized Reverts when msg.sender is not the currently stored admin for the target and a prior config exists. The chain-wide policyAdmin does not bypass this check.
PclProxyNotRegistered PclProxyNotRegistered Reverts when _contract is neither a PCL-registered proxy nor a policy-aware precompile. Encoded as PclProxyNotRegistered(address contractAddress).
CannotEmpty CannotEmpty Reverts when the target is a policy-aware precompile but policies is empty. Encoded as CannotEmpty(string field) with the field value policy-aware precompile policies. Policy-aware precompiles must always carry at least one PolicySet.
InvalidAddress InvalidAddress Reverts when _contract or admin cannot be decoded as a valid address. Inherited from IPrecompile as InvalidAddress(string bad).
PolicyTemplateNotFound PolicyTemplateNotFound Reverts when any PolicySet.templateId in policies is not a registered template on this network. Encoded as PolicyTemplateNotFound(string templateId).

Examples

Bind a denylist policy to a PCL-wrapped ERC20 proxy

Standard usage against a PCL-registered proxy. msg.sender must equal the stored admin from the previous config (or be the first caller for this proxy).

import { createWalletClient, http, encodeAbiParameters, toHex } from "viem";
import { privateKeyToAccount } from "viem/accounts";

const PCL = "0x1000000000000000000000000000000000000005" as const;

const wallet = createWalletClient({
  account: privateKeyToAccount(process.env.ADMIN_KEY as `0x${string}`),
  transport: http("https://rpc-testnet.maroo.io"),
});

// TODO: replace with the real proxy and admin addresses before production.
const proxyAddress = "0x8F3ac2B1d9E74c05A6B18FE27Dc4913e5A0F7b62";
const adminAddress = "0x2c7f09B81a6D3FF1e5A0d4c6bC2a8f7E19dc3a4B";

const denylistBytes = encodeAbiParameters(
  [{ type: "address[]", name: "addresses" }],
  [[
    "0x5aB7c1e40b8dA46f9c7e29D3fA614e97b8f0Ac21",
    "0x6bC8d2a03e91FfA5b8c4E0F3Bd7A0E15C24bDa8E",
  ]],
);

await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "changeContractPolicies",
  args: [{
    _contract: proxyAddress,
    admin:     adminAddress,
    policies:  [{
      templateId: "DENYLIST_POLICY",
      policy:     denylistBytes,
      selector:   toHex("", { size: 0 }),
    }],
  }],
});

Attach a policy set to the Privacy precompile (policy-aware precompile)

Policy-aware precompiles use the same entry point as PCL proxies, but the target address is the precompile itself. Empty policy sets are rejected — swap the rules by submitting a new non-empty PolicySet[] rather than clearing and re-adding.

import { encodeAbiParameters, toHex } from "viem";

const PCL     = "0x1000000000000000000000000000000000000005" as const;
const PRIVACY = "0x100000000000000000000000000000000000000b" as const;

// TODO: replace with the real privacy-admin multisig before production.
const privacyAdmin = "0x2c7f09B81a6D3FF1e5A0d4c6bC2a8f7E19dc3a4B";

// Example: a DENYLIST_POLICY applied to every Privacy call.
const denylistBytes = encodeAbiParameters(
  [{ type: "address[]", name: "addresses" }],
  [["0x5aB7c1e40b8dA46f9c7e29D3fA614e97b8f0Ac21"]],
);

await wallet.writeContract({
  address: PCL,
  abi: pclAbi,
  functionName: "changeContractPolicies",
  args: [{
    _contract: PRIVACY,
    admin:     privacyAdmin,
    policies:  [{
      templateId: "DENYLIST_POLICY",
      policy:     denylistBytes,
      selector:   toHex("", { size: 0 }),
    }],
  }],
});

// NOTE: passing an empty policies array here reverts with
// CannotEmpty("policy-aware precompile policies"). To rotate rules,
// always submit a non-empty replacement set.

Decode the CannotEmpty revert when clearing is attempted

The revert data is ABI-encoded with the CannotEmpty(string field) selector; the payload distinguishes this case from other empty-field violations elsewhere in PCL.

import { decodeErrorResult } from "viem";

try {
  await wallet.writeContract({
    address: PCL,
    abi: pclAbi,
    functionName: "changeContractPolicies",
    args: [{
      _contract: PRIVACY,
      admin:     privacyAdmin,
      policies:  [],  // empty — will revert for policy-aware precompiles
    }],
  });
} catch (err: any) {
  if (err?.data) {
    const decoded = decodeErrorResult({ abi: pclAbi, data: err.data });
    // decoded.errorName === "CannotEmpty"
    // decoded.args[0] === "policy-aware precompile policies"
    console.error(`changeContractPolicies reverted: ${decoded.errorName}`, decoded.args);
  } else {
    throw err;
  }
}
ESC
Type to search