@chainlink/ace 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/.foundry-version +1 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/workflows/auto-release-version.yml +107 -0
  4. package/.github/workflows/create-version-pr.yml +95 -0
  5. package/.github/workflows/forge-docs.yml +90 -0
  6. package/.github/workflows/forge-test.yml +59 -0
  7. package/.solhint-test.json +18 -0
  8. package/.solhint.json +16 -0
  9. package/.solhintignore +3 -0
  10. package/.solhintignore-test +2 -0
  11. package/Glossary.md +141 -0
  12. package/LICENSE +59 -0
  13. package/README.md +218 -0
  14. package/assets/chainlink-logo.svg +21 -0
  15. package/chainlink-ace-License-grants +2 -0
  16. package/foundry.toml +33 -0
  17. package/getting_started/GETTING_STARTED.md +477 -0
  18. package/getting_started/MyVault.sol +48 -0
  19. package/getting_started/advanced/.env.example +36 -0
  20. package/getting_started/advanced/GETTING_STARTED_ADVANCED.md +431 -0
  21. package/getting_started/advanced/SanctionsList.sol +25 -0
  22. package/getting_started/advanced/SanctionsPolicy.sol +58 -0
  23. package/package.json +41 -0
  24. package/packages/cross-chain-identity/README.md +148 -0
  25. package/packages/cross-chain-identity/docs/API_GUIDE.md +120 -0
  26. package/packages/cross-chain-identity/docs/API_REFERENCE.md +271 -0
  27. package/packages/cross-chain-identity/docs/CONCEPTS.md +253 -0
  28. package/packages/cross-chain-identity/docs/CREDENTIAL_FLOW.md +195 -0
  29. package/packages/cross-chain-identity/docs/SECURITY.md +70 -0
  30. package/packages/cross-chain-identity/src/CredentialRegistry.sol +245 -0
  31. package/packages/cross-chain-identity/src/CredentialRegistryIdentityValidator.sol +339 -0
  32. package/packages/cross-chain-identity/src/CredentialRegistryIdentityValidatorPolicy.sol +71 -0
  33. package/packages/cross-chain-identity/src/IdentityRegistry.sol +123 -0
  34. package/packages/cross-chain-identity/src/TrustedIssuerRegistry.sol +140 -0
  35. package/packages/cross-chain-identity/src/interfaces/ICredentialDataValidator.sol +30 -0
  36. package/packages/cross-chain-identity/src/interfaces/ICredentialRegistry.sol +170 -0
  37. package/packages/cross-chain-identity/src/interfaces/ICredentialRequirements.sol +192 -0
  38. package/packages/cross-chain-identity/src/interfaces/ICredentialValidator.sol +37 -0
  39. package/packages/cross-chain-identity/src/interfaces/IIdentityRegistry.sol +85 -0
  40. package/packages/cross-chain-identity/src/interfaces/IIdentityValidator.sol +18 -0
  41. package/packages/cross-chain-identity/src/interfaces/ITrustedIssuerRegistry.sol +61 -0
  42. package/packages/cross-chain-identity/test/CredentialRegistry.t.sol +220 -0
  43. package/packages/cross-chain-identity/test/CredentialRegistryIdentityValidator.t.sol +554 -0
  44. package/packages/cross-chain-identity/test/CredentialRegistryIdentityValidatorPolicy.t.sol +114 -0
  45. package/packages/cross-chain-identity/test/IdentityRegistry.t.sol +106 -0
  46. package/packages/cross-chain-identity/test/IdentityValidator.t.sol +969 -0
  47. package/packages/cross-chain-identity/test/TrustedIssuerRegistry.t.sol +123 -0
  48. package/packages/cross-chain-identity/test/helpers/BaseProxyTest.sol +112 -0
  49. package/packages/cross-chain-identity/test/helpers/MockCredentialDataValidator.sol +26 -0
  50. package/packages/cross-chain-identity/test/helpers/MockCredentialRegistryReverting.sol +131 -0
  51. package/packages/policy-management/README.md +197 -0
  52. package/packages/policy-management/docs/API_GUIDE.md +290 -0
  53. package/packages/policy-management/docs/API_REFERENCE.md +173 -0
  54. package/packages/policy-management/docs/CONCEPTS.md +156 -0
  55. package/packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md +195 -0
  56. package/packages/policy-management/docs/POLICY_ORDERING_GUIDE.md +91 -0
  57. package/packages/policy-management/docs/SECURITY.md +57 -0
  58. package/packages/policy-management/src/core/Policy.sol +124 -0
  59. package/packages/policy-management/src/core/PolicyEngine.sol +382 -0
  60. package/packages/policy-management/src/core/PolicyFactory.sol +92 -0
  61. package/packages/policy-management/src/core/PolicyProtected.sol +126 -0
  62. package/packages/policy-management/src/extractors/ComplianceTokenForceTransferExtractor.sol +57 -0
  63. package/packages/policy-management/src/extractors/ComplianceTokenFreezeUnfreezeExtractor.sol +54 -0
  64. package/packages/policy-management/src/extractors/ComplianceTokenMintBurnExtractor.sol +61 -0
  65. package/packages/policy-management/src/extractors/ERC20ApproveExtractor.sol +57 -0
  66. package/packages/policy-management/src/extractors/ERC20TransferExtractor.sol +62 -0
  67. package/packages/policy-management/src/extractors/ERC3643ForcedTransferExtractor.sol +56 -0
  68. package/packages/policy-management/src/extractors/ERC3643FreezeUnfreezeExtractor.sol +55 -0
  69. package/packages/policy-management/src/extractors/ERC3643MintBurnExtractor.sol +51 -0
  70. package/packages/policy-management/src/extractors/ERC3643SetAddressFrozenExtractor.sol +51 -0
  71. package/packages/policy-management/src/interfaces/IExtractor.sol +17 -0
  72. package/packages/policy-management/src/interfaces/IMapper.sol +17 -0
  73. package/packages/policy-management/src/interfaces/IPolicy.sol +61 -0
  74. package/packages/policy-management/src/interfaces/IPolicyEngine.sol +264 -0
  75. package/packages/policy-management/src/interfaces/IPolicyProtected.sol +48 -0
  76. package/packages/policy-management/src/policies/AllowPolicy.sol +104 -0
  77. package/packages/policy-management/src/policies/BypassPolicy.sol +90 -0
  78. package/packages/policy-management/src/policies/IntervalPolicy.sol +223 -0
  79. package/packages/policy-management/src/policies/MaxPolicy.sol +73 -0
  80. package/packages/policy-management/src/policies/OnlyAuthorizedSenderPolicy.sol +84 -0
  81. package/packages/policy-management/src/policies/OnlyOwnerPolicy.sol +35 -0
  82. package/packages/policy-management/src/policies/PausePolicy.sol +82 -0
  83. package/packages/policy-management/src/policies/README.md +632 -0
  84. package/packages/policy-management/src/policies/RejectPolicy.sol +89 -0
  85. package/packages/policy-management/src/policies/RoleBasedAccessControlPolicy.sol +162 -0
  86. package/packages/policy-management/src/policies/SecureMintPolicy.sol +271 -0
  87. package/packages/policy-management/src/policies/VolumePolicy.sol +133 -0
  88. package/packages/policy-management/src/policies/VolumeRatePolicy.sol +192 -0
  89. package/packages/policy-management/test/PolicyEngine.t.sol +368 -0
  90. package/packages/policy-management/test/PolicyFactory.t.sol +114 -0
  91. package/packages/policy-management/test/PolicyProtectedToken.t.sol +75 -0
  92. package/packages/policy-management/test/extractors/ComplianceTokenForceTransferExtractor.t.sol +59 -0
  93. package/packages/policy-management/test/extractors/ComplianceTokenFreezeUnfreezeExtractor.t.sol +74 -0
  94. package/packages/policy-management/test/extractors/ComplianceTokenMintBurnExtractor.t.sol +92 -0
  95. package/packages/policy-management/test/extractors/ERC20ApproveExtractor.t.sol +58 -0
  96. package/packages/policy-management/test/extractors/ERC3643ForcedTransferExtractor.t.sol +59 -0
  97. package/packages/policy-management/test/extractors/ERC3643FreezeUnfreezeExtractor.t.sol +74 -0
  98. package/packages/policy-management/test/extractors/ERC3643MintBurnExtractor.t.sol +73 -0
  99. package/packages/policy-management/test/extractors/ERC3643SetAddressFrozenExtractor.t.sol +56 -0
  100. package/packages/policy-management/test/helpers/BaseProxyTest.sol +75 -0
  101. package/packages/policy-management/test/helpers/CustomMapper.sol +26 -0
  102. package/packages/policy-management/test/helpers/DummyExtractor.sol +11 -0
  103. package/packages/policy-management/test/helpers/ExpectedParameterPolicy.sol +39 -0
  104. package/packages/policy-management/test/helpers/MockAggregatorV3.sol +51 -0
  105. package/packages/policy-management/test/helpers/MockToken.sol +66 -0
  106. package/packages/policy-management/test/helpers/MockTokenExtractor.sol +34 -0
  107. package/packages/policy-management/test/helpers/PolicyAlwaysAllowed.sol +45 -0
  108. package/packages/policy-management/test/helpers/PolicyAlwaysContinue.sol +23 -0
  109. package/packages/policy-management/test/helpers/PolicyAlwaysRejected.sol +23 -0
  110. package/packages/policy-management/test/helpers/PolicyFailingRun.sol +22 -0
  111. package/packages/policy-management/test/policies/AllowPolicy.t.sol +174 -0
  112. package/packages/policy-management/test/policies/BypassPolicy.t.sol +159 -0
  113. package/packages/policy-management/test/policies/IntervalPolicy.t.sol +307 -0
  114. package/packages/policy-management/test/policies/MaxPolicy.t.sol +54 -0
  115. package/packages/policy-management/test/policies/OnlyAuthorizedSenderPolicy.t.sol +95 -0
  116. package/packages/policy-management/test/policies/OnlyOwnerPolicy.t.sol +47 -0
  117. package/packages/policy-management/test/policies/PausePolicy.t.sol +75 -0
  118. package/packages/policy-management/test/policies/RejectPolicy.t.sol +182 -0
  119. package/packages/policy-management/test/policies/RoleBasedAccessControlPolicy.t.sol +223 -0
  120. package/packages/policy-management/test/policies/SecureMintPolicy.t.sol +442 -0
  121. package/packages/policy-management/test/policies/VolumePolicy.t.sol +158 -0
  122. package/packages/policy-management/test/policies/VolumeRatePolicy.t.sol +165 -0
  123. package/packages/tokens/erc-20/src/ComplianceTokenERC20.sol +345 -0
  124. package/packages/tokens/erc-20/src/ComplianceTokenStoreERC20.sol +29 -0
  125. package/packages/tokens/erc-20/test/ComplianceTokenERC20.t.sol +556 -0
  126. package/packages/tokens/erc-20/test/helpers/BaseProxyTest.sol +75 -0
  127. package/packages/tokens/erc-3643/README.md +24 -0
  128. package/packages/tokens/erc-3643/src/ComplianceTokenERC3643.sol +564 -0
  129. package/packages/tokens/erc-3643/src/ComplianceTokenStoreERC3643.sol +30 -0
  130. package/packages/tokens/erc-3643/test/ComplianceTokenERC3643.t.sol +815 -0
  131. package/packages/tokens/erc-3643/test/helpers/BaseProxyTest.sol +76 -0
  132. package/packages/tokens/erc-3643/test/helpers/ExpectedContextPolicy.sol +32 -0
  133. package/packages/vendor/erc-3643/compliance/modular/IModularCompliance.sol +220 -0
  134. package/packages/vendor/erc-3643/registry/interface/IClaimTopicsRegistry.sol +101 -0
  135. package/packages/vendor/erc-3643/registry/interface/IIdentityRegistry.sol +251 -0
  136. package/packages/vendor/erc-3643/registry/interface/IIdentityRegistryStorage.sol +191 -0
  137. package/packages/vendor/erc-3643/registry/interface/ITrustedIssuersRegistry.sol +161 -0
  138. package/packages/vendor/erc-3643/token/IToken.sol +457 -0
  139. package/packages/vendor/onchain-id/interface/IClaimIssuer.sol +53 -0
  140. package/packages/vendor/onchain-id/interface/IERC734.sol +110 -0
  141. package/packages/vendor/onchain-id/interface/IERC735.sol +105 -0
  142. package/packages/vendor/onchain-id/interface/IIdentity.sol +26 -0
  143. package/packages/vendor/onchain-id/interface/IImplementationAuthority.sol +21 -0
  144. package/remappings.txt +6 -0
  145. package/script/DeployComplianceTokenERC20.s.sol +191 -0
  146. package/script/DeployComplianceTokenERC3643.s.sol +208 -0
  147. package/script/DeploySimpleComplianceToken.s.sol +38 -0
  148. package/script/getting_started/DeployGettingStarted.s.sol +74 -0
  149. package/script/getting_started/advanced/DeployAdvancedGettingStarted.s.sol +332 -0
  150. package/script/getting_started/advanced/DeploySanctionsList.s.sol +26 -0
@@ -0,0 +1,290 @@
1
+ # API Guide: Policy Management
2
+
3
+ This guide provides practical, task-oriented examples for the most common operations you'll perform with the Policy Management component.
4
+
5
+ ## 1. Protecting a Contract Function
6
+
7
+ To protect a smart contract function with the policy system, you must perform two steps:
8
+
9
+ 1. In your contract's code, add the `runPolicy` modifier to the function.
10
+ 2. Connect your contract instance to a `PolicyEngine` instance.
11
+
12
+ ### Step 1.1: Applying the `runPolicy` Modifier
13
+
14
+ In your contract, import `PolicyProtected`, inherit from it, and add `runPolicy` to any function you want to secure.
15
+
16
+ ```solidity
17
+ import { PolicyProtected } from "@chainlink/policy-management/core/PolicyProtected.sol";
18
+
19
+ contract MyGuardedContract is PolicyProtected {
20
+ event ActionCompleted(address indexed caller);
21
+
22
+ // This function is now protected by the Policy Engine.
23
+ function protectedAction() public runPolicy {
24
+ emit ActionCompleted(msg.sender);
25
+ }
26
+
27
+ function unprotectedAction() public {
28
+ // This function is not protected and can be called freely.
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### Step 1.2: Connecting the Policy Engine
34
+
35
+ After deploying your contract, you must attach it to a `PolicyEngine`. This is typically done in the contract's `initialize` function.
36
+
37
+ Here is how you would write the deployment script code:
38
+
39
+ ```solidity
40
+ // --- In your deployment script ---
41
+ PolicyEngine policyEngine = new PolicyEngine();
42
+ bool defaultAllow = true;
43
+ policyEngine.initialize(defaultAllow, address(this)); // Recommended default
44
+
45
+ MyGuardedContract myContract = new MyGuardedContract();
46
+
47
+ // The initialize function calls __PolicyProtected_init to set the engine
48
+ myContract.initialize(OWNER_ADDRESS, address(policyEngine));
49
+ ```
50
+
51
+ ## 2. Configuring Policies for a Function
52
+
53
+ Once the engine is attached, you can add one or more policies to any protected function. A policy can either be self-contained or it can be designed to receive parameters extracted from the function call.
54
+
55
+ ### Step 2.1: Adding a Policy Without Parameters
56
+
57
+ **The Goal:** You want to add a policy that does not require any data from the function it is protecting (e.g., checking the `msg.sender` against an internal access list).
58
+
59
+ **The Action:** In your deployment script, deploy the policy and use `policyEngine.addPolicy()`. The final argument, `policyParameterNames`, will be an empty array.
60
+
61
+ ```solidity
62
+ // --- In your deployment script ---
63
+
64
+ // Deploy a policy that doesn't need parameters, like OnlyOwnerPolicy
65
+ OnlyOwnerPolicy onlyOwnerPolicy = new OnlyOwnerPolicy();
66
+ onlyOwnerPolicy.initialize(address(policyEngine), OWNER_ADDRESS, "");
67
+
68
+ // Get the function selector
69
+ bytes4 selector = myContract.protectedAction.selector;
70
+
71
+ // Add the policy to the engine's registry for that function
72
+ policyEngine.addPolicy(
73
+ address(myContract), // The target contract
74
+ selector, // The function selector
75
+ address(onlyOwnerPolicy), // The policy contract address
76
+ new bytes32[](0) // An empty array for parameter names
77
+ );
78
+ ```
79
+
80
+ ### Step 2.2: Adding a Policy That Requires Parameters
81
+
82
+ **The Goal:** You want to add a policy that makes a decision based on the arguments of the function call (e.g., checking the `value` of an ERC20 `transfer`).
83
+
84
+ **The Actions:** This is a two-step process in your deployment script.
85
+
86
+ 1. First, you must register an `Extractor` contract that knows how to parse the parameters for that function's signature.
87
+ 2. Second, when you call `addPolicy`, you must specify which of the extracted parameters your policy needs.
88
+
89
+ Here is how you would write the deployment script code:
90
+
91
+ ```solidity
92
+ // --- In your deployment script ---
93
+
94
+ // Action 1: Set the Extractor for the transfer function
95
+ ERC20TransferExtractor transferExtractor = new ERC20TransferExtractor();
96
+ bytes4 transferSelector = myToken.transfer.selector;
97
+ policyEngine.setExtractor(transferSelector, address(transferExtractor));
98
+
99
+ // Action 2: Add a policy that uses an extracted parameter
100
+ VolumePolicy volumePolicy = new VolumePolicy();
101
+ // ... initialize volumePolicy with a volume limit ...
102
+
103
+ // Create a list of the parameter names this policy needs.
104
+ // These names MUST match the names defined in the ERC20TransferExtractor.
105
+ bytes32[] memory policyParams = new bytes32[](1);
106
+ policyParams[0] = transferExtractor.PARAM_VALUE(); // e.g., keccak256("value")
107
+
108
+ // Add the policy and specify the parameters it requires
109
+ policyEngine.addPolicy(
110
+ address(myToken),
111
+ transferSelector,
112
+ address(volumePolicy),
113
+ policyParams
114
+ );
115
+ ```
116
+
117
+ The `PolicyEngine` will now automatically call the `ERC20TransferExtractor`, find the `value` parameter, and pass it to the `VolumePolicy` for every `transfer` call.
118
+
119
+ ### Step 2.3: Reusing a Policy on a Different Function
120
+
121
+ A powerful feature of the system is policy reuse. You can apply the same policy contract to multiple different functions, even if those functions have different parameters.
122
+
123
+ **The Goal:** Imagine you have a [`SanctionsPolicy`](../../../getting_started/advanced/SanctionsPolicy.sol) already checking the `to` address on `transfer` calls. Now, you also want to block minting directly to a sanctioned address.
124
+
125
+ **The Action:** You will call `addPolicy` again, this time targeting the `mint` selector. You will provide the name of the parameter from the `mint` function's extractor that corresponds to the address you want to check.
126
+
127
+ ```solidity
128
+ // --- In your deployment script ---
129
+
130
+ // Assume `sanctionsPolicy` and `mintBurnExtractor` are already deployed.
131
+
132
+ // 1. Define WHICH parameter the SanctionsPolicy needs for THIS attachment.
133
+ // The mint function's extractor calls the recipient parameter "account".
134
+ bytes32[] memory mintSanctionsParams = new bytes32[](1);
135
+ mintSanctionsParams[0] = mintBurnExtractor.PARAM_ACCOUNT(); // This is keccak256("account")
136
+
137
+ // 2. Add the SAME sanctionsPolicy to the MINT selector.
138
+ policyEngine.addPolicy(
139
+ address(myToken),
140
+ myToken.mint.selector, // Applying to the MINT function
141
+ address(sanctionsPolicy), // Re-using the same deployed policy instance
142
+ mintSanctionsParams // Specifying the "account" parameter is needed here
143
+ );
144
+ ```
145
+
146
+ The same `sanctionsPolicy` instance now protects both the `transfer` and `mint` functions, receiving the correct address to check from two different extractors, all configured through the `PolicyEngine`.
147
+
148
+ ## 3. Setting a Default Result
149
+
150
+ **The Goal:** You want to define the engine's behavior for the case where a transaction passes through all of a function's policies without any of them returning a definitive `Allowed` or reverting `PolicyRejected`.
151
+
152
+ **The Recommendation:** For most development and production scenarios, it is **recommended to set the default to `Allowed`**.
153
+
154
+ **The Action:** In your deployment script, call `policyEngine.setDefaultPolicyAllow()`.
155
+
156
+ ```solidity
157
+ // --- In your deployment script ---
158
+
159
+ // Set the default result for the entire engine.
160
+ policyEngine.setDefaultPolicyAllow(true);
161
+ ```
162
+
163
+ ### Rationale and Alternative Approaches
164
+
165
+ There are two primary philosophies for the default behavior of a policy system.
166
+
167
+ 1. **Default `Allowed` is `true` (Recommended):**
168
+
169
+ - **Pros:** This approach maintains the regular, expected behavior of your contract's functions until you decide which specific protections are required. It allows you to start with a working system and progressively layer on restrictions (reject policies that revert) as needed.
170
+ - **Cons:** You must be diligent in protecting all new, sensitive functions. If you add a protected function but forget to add a policy, it will be open by default.
171
+
172
+ 2. **Default `Allowed` is `false` (Alternative "Fail-Safe" Approach):**
173
+ - **Pros:** This is a more aggressive security posture. It ensures that no transaction can pass unless a policy explicitly permits it, which prevents new or misconfigured functions from being accidentally exposed.
174
+ - **Cons:** This model forces you to have at least one policy in every chain that returns `Allowed`, which can add complexity.
175
+
176
+ ## 4. Using the `context` Parameter
177
+
178
+ **The Goal:** You need to pass arbitrary, transaction-specific data (like an offchain signature or Merkle proof) to a policy for validation.
179
+
180
+ **The Actions:** There are two methods to achieve this, depending on your use case. The recommended method is to pass the `context` directly as a function argument.
181
+
182
+ ### Step 4.1: The Custom Policy (Common to Both Methods)
183
+
184
+ First, write a policy that is designed to receive and decode data from the `context` argument of its `run()` function. This `SignaturePolicy` is a common example.
185
+
186
+ ```solidity
187
+ // Assumes usage of OpenZeppelin's ECDSA library
188
+ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
189
+ import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
190
+
191
+ contract SignaturePolicy is Policy {
192
+ address public requiredSigner;
193
+
194
+ // ... constructor to set the requiredSigner ...
195
+
196
+ function run(
197
+ address caller,
198
+ address, // subject
199
+ bytes4, // selector
200
+ bytes[] calldata, // parameters
201
+ bytes calldata context
202
+ ) public view virtual override returns (IPolicyEngine.PolicyResult) {
203
+ require(context.length > 0, "SignaturePolicy: context cannot be empty");
204
+
205
+ // Create the same message hash that was signed offchain
206
+ bytes32 messageHash = keccak256(abi.encodePacked("I approve this transaction for:", caller));
207
+ bytes32 signedHash = ECDSA.toEthSignedMessageHash(messageHash);
208
+
209
+ address recoveredSigner = ECDSA.recover(signedHash, context);
210
+
211
+ if (recoveredSigner != requiredSigner) {
212
+ revert IPolicyEngine.PolicyRejected("SignaturePolicy: invalid signature");
213
+ }
214
+
215
+ return IPolicyEngine.PolicyResult.Continue;
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### Step 4.2: Passing Context Directly (Recommended Pattern)
221
+
222
+ For your own custom functions, the cleanest and safest pattern is to pass the `context` directly as a function argument using the `runPolicyWithContext` modifier.
223
+
224
+ **The Action:** Create a custom function that accepts `context` as its final argument and protect it with the `runPolicyWithContext` modifier.
225
+
226
+ ```solidity
227
+ // In your custom contract...
228
+ contract MyCustomContract is PolicyProtected {
229
+ // ...
230
+ function doSomethingSpecialWithApproval(
231
+ uint256 arg1,
232
+ bytes calldata context // The extra data is a direct argument
233
+ )
234
+ public
235
+ runPolicyWithContext(context) // The modifier receives the context directly
236
+ {
237
+ // Your core logic here...
238
+ }
239
+ }
240
+
241
+ // In your calling transaction (e.g., a script or front-end):
242
+ // The context (e.g., a signature) is prepared offchain
243
+ bytes memory signature = getSignatureFromOffChainService(sender);
244
+
245
+ // Make a single call to the protected function
246
+ myCustomContract.doSomethingSpecialWithApproval(123, signature);
247
+ ```
248
+
249
+ This approach is recommended for new, custom functions as it is more explicit and gas-efficient.
250
+
251
+ ### Step 4.3: Passing Context via `setContext` (For Standard Interfaces)
252
+
253
+ If you need to protect a function with a standard, unchangeable signature (like ERC20 `transfer`), you **must** use the two-step `setContext` method.
254
+
255
+ **The Action:** The offchain client or calling contract must set the context for the `msg.sender` and then call the protected function within the same transaction.
256
+
257
+ ```solidity
258
+ // In a test or script, using a multicall pattern:
259
+
260
+ // 1. Offchain: A trusted party signs a message approving the transaction for the sender.
261
+ // (This signature is the `context` bytes)
262
+ bytes memory signature = getSignatureFromOffChainService(sender);
263
+
264
+ // 2. Onchain: In a single atomic transaction:
265
+ // a. Set the context for the sender
266
+ myContract.setContext(signature);
267
+ // b. Call the protected function
268
+ myContract.protectedAction();
269
+ ```
270
+
271
+ The `PolicyProtected` contract provides the `setContext` and `clearContext` functions. After a transaction that uses `setContext`, it is the responsibility of the caller or a subsequent process to clear the context to prevent it from being reused.
272
+
273
+ > **Security Note:** This method stores the context on a per-sender basis. It is **strongly recommended** to set and consume the context in the same atomic transaction to avoid potential race conditions or stale context being reused. **[Learn more about the security implications.](./SECURITY.md#4-context-handling-and-race-conditions)**
274
+
275
+ ## Common Design Patterns
276
+
277
+ The Policy Management component enables several powerful, high-level design patterns for building robust and flexible systems.
278
+
279
+ ### Pattern: Securing Core Infrastructure with Policies
280
+
281
+ The `PolicyEngine` is not just for protecting your main application contract (e.g., your token). You can and should use it to govern the administrative functions of the other components in your system, such as the `IdentityRegistry` and `CredentialRegistry` (learn more about the [Cross-Chain Identity](../../cross-chain-identity/README.md) component).
282
+
283
+ This creates a unified, consistent security model for your entire dApp ecosystem.
284
+
285
+ - **The Goal:** Ensure that only authorized addresses can add or remove credentials in the `CredentialRegistry`.
286
+ - **The Implementation:**
287
+ 1. When you deploy the `CredentialRegistry`, you set its owner to be the `PolicyEngine` contract. This means all administrative actions must now pass through the engine.
288
+ 2. You deploy a policy, such as `OnlyAuthorizedSenderPolicy`, which maintains a list of trusted addresses (e.g., your KYC provider).
289
+ 3. In the `PolicyEngine`, you use `addPolicy` to apply this `OnlyAuthorizedSenderPolicy` to the administrative functions of the `CredentialRegistry` (e.g., `registerCredential`, `removeCredential`).
290
+ 4. **The Result:** The `CredentialRegistry` is now fully protected. No one, not even the original deployer, can manage credentials unless they are explicitly added to the authorized senders list in the policy, creating a robust and auditable separation of roles.
@@ -0,0 +1,173 @@
1
+ > **Note for Developers:** This file provides a complete, auto-generated list of all functions and interfaces. For a more practical, task-oriented guide with code examples, please see the **[API Guide](API_GUIDE.md)** first.
2
+
3
+ # API Reference: Policy Management
4
+
5
+ This document provides the complete, formal interface specifications for the Policy Management component.
6
+
7
+ ## Core Interfaces
8
+
9
+ ### IPolicyEngine
10
+
11
+ **File:** [`src/interfaces/IPolicyEngine.sol`](../src/interfaces/IPolicyEngine.sol)
12
+
13
+ The `IPolicyEngine` interface defines the core functions for the central orchestrator that manages and executes policies.
14
+
15
+ #### Enums and Structs
16
+
17
+ ```solidity
18
+ enum PolicyResult {
19
+ None,
20
+ Allowed,
21
+ Continue
22
+ }
23
+
24
+ struct Parameter {
25
+ bytes32 name;
26
+ bytes value;
27
+ }
28
+
29
+ struct Payload {
30
+ bytes4 selector;
31
+ address sender;
32
+ bytes data;
33
+ bytes context;
34
+ }
35
+ ```
36
+
37
+ #### Key Functions
38
+
39
+ ```solidity
40
+ interface IPolicyEngine is IERC165 {
41
+ function attach() external;
42
+ function detach() external;
43
+
44
+ function setExtractor(bytes4 selector, address extractor) external;
45
+ function setExtractors(bytes4[] calldata selectors, address extractor) external;
46
+ function getExtractor(bytes4 selector) external view returns (address);
47
+
48
+ function setPolicyMapper(address policy, address mapper) external;
49
+ function getPolicyMapper(address policy) external view returns (address);
50
+
51
+ function addPolicy(address target, bytes4 selector, address policy, bytes32[] calldata policyParameterNames) external;
52
+ function addPolicyAt(address target, bytes4 selector, address policy, bytes32[] calldata policyParameterNames, uint256 position) external;
53
+ function removePolicy(address target, bytes4 selector, address policy) external;
54
+ function getPolicies(address target, bytes4 selector) external view returns (address[] memory);
55
+
56
+ function setDefaultPolicyAllow(bool defaultAllow) external;
57
+ function setTargetDefaultPolicyAllow(address target, bool defaultAllow) external;
58
+
59
+ function check(Payload calldata payload) external view;
60
+ function run(Payload calldata payload) external;
61
+ }
62
+ ```
63
+
64
+ ### IPolicy
65
+
66
+ **File:** [`src/interfaces/IPolicy.sol`](../src/interfaces/IPolicy.sol)
67
+
68
+ The `IPolicy` interface is the standard for all policy contracts. Each policy must implement this interface to be compatible with the `PolicyEngine`.
69
+
70
+ #### Key Functions
71
+
72
+ ```solidity
73
+ interface IPolicy is IERC165 {
74
+ function run(
75
+ address caller,
76
+ address subject,
77
+ bytes4 selector,
78
+ bytes[] calldata parameters,
79
+ bytes calldata context
80
+ ) external view returns (IPolicyEngine.PolicyResult);
81
+
82
+ function postRun(
83
+ address caller,
84
+ address subject,
85
+ bytes4 selector,
86
+ bytes[] calldata parameters,
87
+ bytes calldata context
88
+ ) external;
89
+
90
+ function onInstall(bytes4 selector) external;
91
+ function onUninstall(bytes4 selector) external;
92
+ }
93
+ ```
94
+
95
+ ### IPolicyProtected
96
+
97
+ **File:** [`src/interfaces/IPolicyProtected.sol`](../src/interfaces/IPolicyProtected.sol)
98
+
99
+ The `IPolicyProtected` interface must be implemented by any contract that wishes to be protected by a `PolicyEngine`.
100
+
101
+ #### Key Functions
102
+
103
+ ```solidity
104
+ interface IPolicyProtected is IERC165 {
105
+ function attachPolicyEngine(address policyEngine) external;
106
+ function getPolicyEngine() external view returns (address);
107
+ function setContext(bytes calldata context) external;
108
+ function getContext() external view returns (bytes memory);
109
+ function clearContext() external;
110
+ }
111
+ ```
112
+
113
+ ## Helper Interfaces
114
+
115
+ ### IExtractor
116
+
117
+ **File:** [`src/interfaces/IExtractor.sol`](../src/interfaces/IExtractor.sol)
118
+
119
+ The `IExtractor` interface is the standard for contracts that parse transaction calldata into named parameters for the `PolicyEngine`.
120
+
121
+ #### Key Functions
122
+
123
+ ```solidity
124
+ interface IExtractor is IERC165 {
125
+ function extract(IPolicyEngine.Payload calldata payload) external view returns (IPolicyEngine.Parameter[] memory);
126
+ }
127
+ ```
128
+
129
+ ### IMapper
130
+
131
+ **File:** [`src/interfaces/IMapper.sol`](../src/interfaces/IMapper.sol)
132
+
133
+ The `IMapper` interface is the standard for custom mapper contracts. A custom mapper is only needed for advanced scenarios where parameter data needs to be transformed before being passed to a policy.
134
+
135
+ #### Key Functions
136
+
137
+ ```solidity
138
+ interface IMapper is IERC165 {
139
+ function map(
140
+ IPolicyEngine.Parameter[] calldata extractedParameters
141
+ ) external view returns (bytes[] memory mappedParameters);
142
+ }
143
+ ```
144
+
145
+ ## Events
146
+
147
+ ```solidity
148
+ event TargetAttached(address indexed target);
149
+ event TargetDetached(address indexed target);
150
+ event PolicyAdded(address indexed target, bytes4 indexed selector, address policy);
151
+ event PolicyRemoved(address indexed target, bytes4 indexed selector, address policy);
152
+ event ExtractorSet(bytes4 indexed selector, address indexed extractor);
153
+ event PolicyParametersSet(address indexed policy, bytes[] parameters);
154
+ event DefaultPolicyAllowSet(bool defaultAllow);
155
+ event TargetDefaultPolicyAllowSet(address indexed target, bool defaultAllow);
156
+ ```
157
+
158
+ ## Errors
159
+
160
+ ```solidity
161
+ error TargetNotAttached(address target);
162
+ error TargetAlreadyAttached(address target);
163
+ error PolicyEngineUndefined();
164
+ error PolicyRunRejected(bytes4 selector, address policy, string rejectReason);
165
+ error PolicyRejected(string rejectReason);
166
+ error PolicyMapperError(address policy, bytes errorReason);
167
+ error PolicyRunError(bytes4 selector, address policy, bytes errorReason);
168
+ error PolicyRunUnauthorizedError(address account);
169
+ error PolicyPostRunError(bytes4 selector, address policy, bytes errorReason);
170
+ error UnsupportedSelector(bytes4 selector);
171
+ error InvalidConfiguration(string errorReason);
172
+ error ExtractorError(bytes4 selector, address extractor, bytes errorReason);
173
+ ```
@@ -0,0 +1,156 @@
1
+ # Core Concepts: Policy Management
2
+
3
+ ## Abstract
4
+
5
+ The Policy Management component provides a modular framework for defining, executing, and managing dynamic onchain rules. By separating policy logic from core contract logic, this component allows developers to add, remove, and compose compliance and operational rules without redeploying or modifying an application's primary business logic. It provides a robust, extensible, and secure foundation for building adaptable financial applications.
6
+
7
+ ## Glossary of Key Terms
8
+
9
+ | Term | Role |
10
+ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | **`PolicyProtected`** | An abstract contract that your application inherits from. It provides the `runPolicy` modifier which acts as the hook into the policy system. |
12
+ | **`PolicyEngine`** | The central onchain orchestrator. It holds the registry of all policies, executes them in order, and manages the default outcome. |
13
+ | **`Policy`** | A self-contained, modular contract that holds a single, specific rule (e.g., checking a user's role or limiting transaction volume). |
14
+ | **`Extractor`** | A helper contract that parses raw transaction `calldata` for a specific function signature and decodes it into a list of named parameters. |
15
+ | **`Mapper`** | An advanced, optional helper contract used to transform or combine parameters from an `Extractor` before they are passed to a `Policy`. |
16
+
17
+ ## Core Architecture
18
+
19
+ The Policy Management architecture is designed to be as flexible and decoupled as possible. It is built on a few key components that work in concert to validate transactions.
20
+
21
+ - **`PolicyProtected`**: The bridge between your application and the compliance engine. By inheriting from this contract and using its `runPolicy` modifier on a function, you designate that function as being subject to the rules managed by the `PolicyEngine`.
22
+
23
+ - **`PolicyEngine`**: The brain of the system. For any protected function, the engine is responsible for:
24
+
25
+ 1. Calling the correct `Extractor` to parse the transaction data.
26
+ 2. Mapping the extracted parameters for each policy (either internally by default, or by delegating to a custom **[`Mapper`](#the-advanced-flow-custom-mappers)**).
27
+ 3. Executing a chain of `Policy` contracts in a defined order.
28
+ 4. Processing the `Allowed`, `Continue` result or `PolicyRejected` revert from each policy.
29
+ 5. Applying a final, default result if no policy returns a definitive `Allowed` or revert.
30
+
31
+ - **`Policy`**: A modular rule. Each policy is a small, focused contract with a `run()` function that receives parameters and returns a verdict. This is where your specific business or compliance logic lives. The `Policy` can also have an optional `postRun()` function for state changes.
32
+
33
+ ### The Policy Flow: A Step-by-Step Breakdown
34
+
35
+ The heart of the Policy Management component is the "chain of responsibility" pattern, where a transaction is passed through a series of checks before it is allowed to execute.
36
+
37
+ #### Policy Flow Diagram
38
+
39
+ ```mermaid
40
+ sequenceDiagram
41
+ participant YourContract
42
+ participant PolicyEngine
43
+ participant Extractor
44
+ participant Policy1
45
+ participant Policy2
46
+
47
+ YourContract->>PolicyEngine: 1. run(payload)
48
+ Note right of YourContract: payload contains<br/>{selector, sender, data, context}
49
+
50
+ PolicyEngine->>Extractor: 2. extract(payload)
51
+ Extractor-->>PolicyEngine: 3. Returns named parameters (Parameter[])
52
+
53
+ Note over PolicyEngine: 4. Internally maps parameters for Policy 1
54
+ PolicyEngine->>Policy1: 5. run(caller, subject, selector, mapped_params, context)
55
+
56
+ alt Policy 1 Returns Continue
57
+ Policy1-->>PolicyEngine: 6a. Returns `Continue`
58
+ PolicyEngine->>Policy1: 7a. postRun(...) (optional)
59
+
60
+ Note over PolicyEngine: 8a. Proceeds to next policy.<br/>Internally maps parameters for Policy 2.
61
+ PolicyEngine->>Policy2: 9a. run(caller, subject, selector, mapped_params, context)
62
+
63
+ alt Final Decision
64
+ Policy2-->>PolicyEngine: 10a. Returns `Allowed` or reverts `PolicyRejected`
65
+ PolicyEngine->>Policy2: 11a. postRun(...) (if not reverted)
66
+ PolicyEngine-->>YourContract: 12a. Allows or Reverts Transaction
67
+ else No More Policies
68
+ Policy2-->>PolicyEngine: 10b. Returns `Continue`
69
+ PolicyEngine->>Policy2: 11b. postRun(...)
70
+ Note over PolicyEngine: 12b. No policies left, applying default result.
71
+ PolicyEngine-->>YourContract: 13b. Allows or Reverts based on default
72
+ end
73
+
74
+ else Policy 1 Makes Final Decision
75
+ Policy1-->>PolicyEngine: 6b. Returns `Allowed` or reverts `PolicyRejected`
76
+ PolicyEngine->>Policy1: 7b. postRun(...) (if not reverted)
77
+ Note over PolicyEngine: Skips all subsequent policies
78
+ PolicyEngine-->>YourContract: 8b. Allows or Reverts Transaction
79
+ end
80
+ ```
81
+
82
+ 1. **Diagram Step 1-3 — Invocation and Extraction**
83
+ The protected contract invokes `PolicyEngine.run(payload)`. The engine calls the designated **Extractor** for the function selector, which decodes the calldata into a full list of named parameters.
84
+
85
+ 2. **Diagram Step 4-5 — First Policy Execution**
86
+ The engine's internal mapper prepares the specific parameters required by **`Policy1`**. The engine then calls `Policy1.run(...)`.
87
+
88
+ 3. **Diagram Step 6-7 — Processing the Result**
89
+ The diagram now shows two main paths:
90
+
91
+ - **Policy 1 makes a final decision (Path 6b):** If `Policy1` returns `Allowed` the engine calls its optional `postRun(...)` function (if not reverted) and the process stops, skipping `Policy2` entirely.
92
+ - **Policy 1 defers (Path 6a):** If `Policy1` returns `Continue`, the engine calls its optional `postRun(...)` function and prepares to execute the next policy in the chain.
93
+
94
+ 4. **Diagram Step 8-13 — Second Policy Execution**
95
+ Following Path 6a, the engine now prepares the parameters required by **`Policy2`** and calls its `run` function. The outcome of this second call (or any subsequent policy) will determine the final result, or the engine will use its default if `Policy2` also returns `Continue`.
96
+
97
+ 5. **Branching Decisions & `postRun`**
98
+ - **reverts `PolicyRejected`**: The policy has found a definitive reason to block the transaction. The `PolicyEngine` immediately reverts the entire transaction. No `postRun` is called.
99
+ - **`Allowed`**: The engine then calls the policy's optional `postRun(...)` function (if implemented), which can be used to perform state changes. The engine then **skips** any remaining policies and allows the transaction.
100
+ - **`Continue`**: The engine calls the policy's optional `postRun(...)` function (if implemented), then moves to the next policy in the chain. If no policies remain, it applies the engine's default outcome.
101
+
102
+ ### The Extractor and Mapper Pattern
103
+
104
+ A key design principle of the Policy Management component is the separation of concerns between **parsing data** and **enforcing rules**. This is achieved through Extractors and Mappers.
105
+
106
+ #### The Default Flow (95% of Use Cases)
107
+
108
+ For most scenarios, the process is simple and powerful:
109
+
110
+ 1. You write an **Extractor** for a specific function signature (e.g., `ERC20TransferExtractor` for `transfer(address,uint256)`). This extractor is responsible for parsing _all_ the parameters you might need from that function's calldata (e.g., `to` and `value`). You register this one extractor for that function selector using `setExtractor`.
111
+
112
+ 2. You then attach multiple **Policies** to that same function selector using `addPolicy`. The `PolicyEngine` has a built-in default mapper that works by **name matching**. When you call `addPolicy`, you provide a list of the parameter names that specific policy needs (e.g., `["to"]` for a sanctions policy, `["value"]` for a volume policy).
113
+
114
+ The engine handles the rest: it calls the single extractor, gets all the parameters, and then for each policy, it provides only the subset of parameters that policy requested by name.
115
+
116
+ This provides the best of both worlds: efficient data parsing (one extraction per transaction) and clean, decoupled policies.
117
+
118
+ #### The Advanced Flow (Custom Mappers)
119
+
120
+ A custom **Mapper** contract (`IMapper`) is only needed for rare, advanced scenarios where simple name-based mapping is not enough. This is for when you need to **transform or combine** extracted data before passing it to a policy.
121
+
122
+ For example, imagine a policy that needs a `usdValue`, but the extractor only provides `tokenAmount` and `tokenPrice`. You could write a custom mapper that:
123
+
124
+ 1. Takes `tokenAmount` and `tokenPrice` as input.
125
+ 2. Multiplies them to calculate `usdValue`.
126
+ 3. Returns the `usdValue` in the `bytes[]` array for the policy.
127
+
128
+ You would then associate this custom mapper with that specific policy using `setPolicyMapper`, overriding the engine's default behavior for that policy only.
129
+
130
+ ### The `context` Parameter: A Flexible Data Channel
131
+
132
+ Throughout the Policy Management interfaces, you will see a `bytes calldata context` parameter in functions like `run` and `postRun`. This parameter serves as a powerful, flexible data channel for passing arbitrary, transaction-specific information to your policies.
133
+
134
+ It is designed for situations where the data needed for a compliance check is **not** part of the protected function's arguments and is provided by the entity that initiates the transaction.
135
+
136
+ #### Common Use Cases for `context`
137
+
138
+ - **Offchain Signatures:** A user signs a message offchain (e.g., approving a high-value transaction), and the front-end passes this signature into the `context`. A policy can then decode the signature and use `ecrecover` to validate it.
139
+ - **Merkle Proofs:** To verify that an address is part of a large, dynamic allowlist stored offchain, the caller can provide a Merkle proof in the `context`. The policy would then validate this proof against a stored Merkle root.
140
+ - **Dynamic Risk Parameters:** An integrator could pass in dynamic, offchain risk scores or session data via the `context`, allowing policies to make more nuanced decisions.
141
+
142
+ #### How it Works: Two Methods for Passing Context
143
+
144
+ The `PolicyProtected` contract offers two ways to pass `context` data. The recommended approach is to pass it directly as a function argument, but a two-step method is also available for compatibility with standard interfaces.
145
+
146
+ 1. **The `...WithContext` Method (Recommended for Custom Functions):**
147
+ For your own custom functions, the cleanest, safest, and most gas-efficient pattern is to pass the context directly as an argument. The `PolicyProtected` contract provides a `runPolicyWithContext(bytes)` modifier for this purpose.
148
+
149
+ - **Pros:** Cleaner code, safer (eliminates risk of stale context), more gas-efficient (one transaction).
150
+ - **Cons:** Only works for custom functions where you can define the signature.
151
+ - **Implementation:** The function signature would look like `myFunction(arg1, arg2, bytes calldata context)`, and the modifier would be `runPolicyWithContext(context)`.
152
+
153
+ 2. **The `setContext` Method (For Standard Interfaces):**
154
+ This method should be used when you need to protect a function with a standard, unchangeable signature (like an ERC20 `transfer`). An external caller first calls `setContext(bytes)` and then immediately calls the protected function in the same transaction. The `runPolicy` modifier automatically retrieves this `context`, includes it in the `Payload`, and clears it after the transaction.
155
+
156
+ > **Security Note:** This method stores the context on a per-sender basis. It is **strongly recommended** to set and consume the context in the same atomic transaction to avoid potential race conditions or stale context being reused. **[Learn more about the security implications.](./SECURITY.md#4-context-handling-and-race-conditions)**