|
| 1 | +--- |
| 2 | +title: "Policy configuration in code" |
| 3 | +description: "Read, audit, create, update, and bind B20 PolicyRegistry policies from Solidity." |
| 4 | +--- |
| 5 | + |
| 6 | +B20 policy configuration has two parts: |
| 7 | + |
| 8 | +1. Read each token scope with `token.policyId(scope)`. |
| 9 | +2. Read or write the pointed-to policy in the singleton PolicyRegistry. |
| 10 | + |
| 11 | +## Audit a token's policy scopes |
| 12 | + |
| 13 | +```solidity |
| 14 | +// SPDX-License-Identifier: MIT |
| 15 | +pragma solidity ^0.8.20; |
| 16 | +
|
| 17 | +import {Script, console2} from "forge-std/Script.sol"; |
| 18 | +import {IB20} from "base-std/interfaces/IB20.sol"; |
| 19 | +import {IPolicyRegistry} from "base-std/interfaces/IPolicyRegistry.sol"; |
| 20 | +import {B20Constants} from "base-std/lib/B20Constants.sol"; |
| 21 | +import {StdPrecompiles} from "base-std/StdPrecompiles.sol"; |
| 22 | +
|
| 23 | +contract AuditB20Policies is Script { |
| 24 | + bytes32[5] internal scopes = [ |
| 25 | + B20Constants.TRANSFER_SENDER_POLICY, |
| 26 | + B20Constants.TRANSFER_RECEIVER_POLICY, |
| 27 | + B20Constants.TRANSFER_EXECUTOR_POLICY, |
| 28 | + B20Constants.MINT_RECEIVER_POLICY, |
| 29 | + B20Constants.SEIZE_HOLDER_POLICY |
| 30 | + ]; |
| 31 | +
|
| 32 | + function run(address tokenAddress, address accountToCheck) external view { |
| 33 | + IB20 token = IB20(tokenAddress); |
| 34 | + IPolicyRegistry registry = StdPrecompiles.POLICY_REGISTRY; |
| 35 | +
|
| 36 | + for (uint256 i; i < scopes.length; i++) { |
| 37 | + uint64 id = token.policyId(scopes[i]); |
| 38 | + console2.logBytes32(scopes[i]); |
| 39 | + console2.log("policyId", id); |
| 40 | + console2.log("exists", id == 0 || registry.policyExists(id)); |
| 41 | + console2.log("authorized", registry.isAuthorized(id, accountToCheck)); |
| 42 | + console2.log("admin", registry.policyAdmin(id)); |
| 43 | + console2.log("pendingAdmin", registry.pendingPolicyAdmin(id)); |
| 44 | + } |
| 45 | + } |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +Interpretation: |
| 50 | + |
| 51 | +| Value | Meaning | |
| 52 | +|---|---| |
| 53 | +| `0` | `ALWAYS_ALLOW`; the scope is wide open. | |
| 54 | +| Top byte `0` | `BLOCKLIST`; empty/uncreated behaves authorized by default. | |
| 55 | +| Top byte `1` | `ALLOWLIST`; empty/uncreated behaves denied by default. | |
| 56 | +| Top byte `2` | `UNION`; composite policy. | |
| 57 | +| Top byte `3` | `INTERSECT`; composite policy. | |
| 58 | + |
| 59 | +<Warning> |
| 60 | +Validate `policyExists(policyId)` before binding a scope. `isAuthorized` does not revert for missing IDs. |
| 61 | +</Warning> |
| 62 | + |
| 63 | +## Create and bind a simple policy |
| 64 | + |
| 65 | +```solidity |
| 66 | +IPolicyRegistry registry = StdPrecompiles.POLICY_REGISTRY; |
| 67 | +IB20 token = IB20(tokenAddress); |
| 68 | +
|
| 69 | +address[] memory initialMembers = new address[](1); |
| 70 | +initialMembers[0] = treasury; |
| 71 | +
|
| 72 | +uint64 mintAllowlist = registry.createPolicyWithAccounts( |
| 73 | + policyAdmin, |
| 74 | + IPolicyRegistry.PolicyType.ALLOWLIST, |
| 75 | + initialMembers |
| 76 | +); |
| 77 | +
|
| 78 | +require(registry.policyExists(mintAllowlist), "policy missing"); |
| 79 | +token.updatePolicy(B20Constants.MINT_RECEIVER_POLICY, mintAllowlist); |
| 80 | +``` |
| 81 | + |
| 82 | +## Update membership |
| 83 | + |
| 84 | +```solidity |
| 85 | +address[] memory accounts = new address[](2); |
| 86 | +accounts[0] = alice; |
| 87 | +accounts[1] = bob; |
| 88 | +
|
| 89 | +// ALLOWLIST: true adds authorization; false removes it. |
| 90 | +registry.updateAllowlist(mintAllowlist, true, accounts); |
| 91 | +
|
| 92 | +// BLOCKLIST: true blocks; false unblocks. |
| 93 | +registry.updateBlocklist(transferBlocklist, true, accounts); |
| 94 | +``` |
| 95 | + |
| 96 | +## Create a composite policy |
| 97 | + |
| 98 | +```solidity |
| 99 | +uint64[] memory children = new uint64[](2); |
| 100 | +children[0] = kycAllowlist; |
| 101 | +children[1] = sanctionsBlocklist; |
| 102 | +
|
| 103 | +uint64 policyId = registry.createCompositePolicy( |
| 104 | + policyAdmin, |
| 105 | + IPolicyRegistry.PolicyType.INTERSECT, |
| 106 | + children |
| 107 | +); |
| 108 | +
|
| 109 | +token.updatePolicy(B20Constants.TRANSFER_RECEIVER_POLICY, policyId); |
| 110 | +``` |
| 111 | + |
| 112 | +## Transfer or freeze policy administration |
| 113 | + |
| 114 | +```solidity |
| 115 | +// Current admin stages a transfer. |
| 116 | +registry.stageUpdateAdmin(policyId, newAdmin); |
| 117 | +
|
| 118 | +// Pending admin accepts it. |
| 119 | +vm.prank(newAdmin); |
| 120 | +registry.finalizeUpdateAdmin(policyId); |
| 121 | +
|
| 122 | +// Irreversible: freezes policy membership forever. |
| 123 | +registry.renounceAdmin(policyId); |
| 124 | +``` |
| 125 | + |
| 126 | +<CardGroup cols={2}> |
| 127 | + <Card title="Policies & scopes" href="/base-chain/specs/upgrades/beryl/b20/specification/concepts/policies-and-scopes" /> |
| 128 | +</CardGroup> |
0 commit comments