@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,195 @@
1
+ # Tutorial: Creating a Custom Policy
2
+
3
+ This tutorial will guide you through the process of creating your own custom policy from scratch. We will build a simple `LockoutPolicy` that blocks transactions from an address for a specified period of time.
4
+
5
+ This will teach you the core principles of policy development and provide a template for your own, more complex policies.
6
+
7
+ > **Note:** A clean, copy-pasteable boilerplate template is available at the end of this tutorial for you to use as a starting point.
8
+
9
+ ## Step 1: The Policy Contract Boilerplate
10
+
11
+ Every policy must inherit from the base `Policy` contract and implement the `run` function. Let's create our file `LockoutPolicy.sol` with the basic structure.
12
+
13
+ **`LockoutPolicy.sol`**
14
+
15
+ ```solidity
16
+ // SPDX-License-Identifier: MIT
17
+ pragma solidity 0.8.26;
18
+
19
+ import { Policy } from "@chainlink/policy-management/core/Policy.sol";
20
+ import { IPolicyEngine } from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
21
+
22
+ contract LockoutPolicy is Policy {
23
+ // Our policy's logic will go here.
24
+
25
+ function run(
26
+ address caller,
27
+ address subject,
28
+ bytes4 selector,
29
+ bytes[] calldata parameters,
30
+ bytes calldata context
31
+ ) public view virtual override returns (IPolicyEngine.PolicyResult) {
32
+ // Our enforcement logic will go here.
33
+ return IPolicyEngine.PolicyResult.Continue; // Default to Continue
34
+ }
35
+ }
36
+ ```
37
+
38
+ This is the simplest possible policy. It doesn't do anything yet, but it's a valid, compilable policy contract.
39
+
40
+ ## Step 2: Adding State and Logic
41
+
42
+ Our `LockoutPolicy` needs to store two pieces of information for each locked-out address: the address itself, and the Unix timestamp when their lockout period expires. We'll use a `mapping` for this.
43
+
44
+ We also need a public function that the policy owner can call to add or update a lockout.
45
+
46
+ ```solidity
47
+ // ... imports ...
48
+
49
+ contract LockoutPolicy is Policy {
50
+ mapping(address => uint256) public lockoutExpiresAt;
51
+
52
+ /// @notice The policy owner can call this to lock an address.
53
+ /// @param account The address to lock out.
54
+ /// @param duration The duration of the lockout, in seconds.
55
+ function setLockout(address account, uint256 duration) public onlyOwner {
56
+ lockoutExpiresAt[account] = block.timestamp + duration;
57
+ }
58
+
59
+ function run(
60
+ address caller,
61
+ address, // subject
62
+ bytes4, // selector
63
+ bytes[] calldata, // parameters
64
+ bytes calldata // context
65
+ ) public view virtual override returns (IPolicyEngine.PolicyResult) {
66
+ // We only care about the original sender of the transaction.
67
+ if (lockoutExpiresAt[caller] > block.timestamp) {
68
+ // If the sender's lockout period is still active, reject.
69
+ revert IPolicyEngine.PolicyRejected("LockoutPolicy: Address is locked out");
70
+ }
71
+
72
+ // Otherwise, continue to the next policy.
73
+ return IPolicyEngine.PolicyResult.Continue;
74
+ }
75
+ }
76
+ ```
77
+
78
+ Our policy now has logic! It checks if the `sender` of the transaction is trying to transact before their `lockoutExpiresAt` timestamp has been reached. If they are, it rejects the transaction. Otherwise, it takes no action and lets the `PolicyEngine` continue to the next policy.
79
+
80
+ ## Step 3: Input Validation (A Critical Best Practice)
81
+
82
+ What if our policy needed to check a parameter from the transaction data? For example, what if we wanted to lock out the `recipient` of a transfer instead of the `sender`?
83
+
84
+ The policy would receive the recipient's address in the `parameters` array. It is a **critical best practice to always validate the inputs** your policy receives.
85
+
86
+ Let's modify our `run` function to expect the `recipient` as a parameter and add the necessary `require` check.
87
+
88
+ ```solidity
89
+ // ...
90
+ function run(
91
+ address, // caller
92
+ address, // subject
93
+ bytes4, // selector
94
+ bytes[] calldata parameters,
95
+ bytes calldata // context
96
+ ) public view virtual override returns (IPolicyEngine.PolicyResult) {
97
+ // BEST PRACTICE: Always validate your expected inputs.
98
+ // This prevents reverts and ensures your policy is used correctly.
99
+ require(parameters.length == 1, "LockoutPolicy: Expected 1 parameter");
100
+
101
+ // Decode the address from the parameters array.
102
+ address recipient = abi.decode(parameters[0], (address));
103
+
104
+ if (lockoutExpiresAt[recipient] > block.timestamp) {
105
+ revert IPolicyEngine.PolicyRejected("LockoutPolicy: Address is locked out");
106
+ }
107
+
108
+ return IPolicyEngine.PolicyResult.Continue;
109
+ }
110
+ // ...
111
+ ```
112
+
113
+ This `require` statement makes our policy more robust. It provides a clear error message if the policy is ever misconfigured in the `PolicyEngine` without the required parameter, which makes debugging much easier.
114
+
115
+ ## Step 4: Using Your Custom Policy
116
+
117
+ Now your `LockoutPolicy` is complete. Using it follows the same pattern as any pre-built policy.
118
+
119
+ In your deployment script:
120
+
121
+ 1. **Deploy it**: `LockoutPolicy lockoutPolicy = new LockoutPolicy();`
122
+ 2. **Initialize it**: `lockoutPolicy.initialize(address(policyEngine), OWNER_ADDRESS, new bytes(0));`
123
+ > **What is the `new bytes(0)` parameter?**
124
+ >
125
+ > The third argument of the `initialize` function is a `bytes` array called `configParams`. It's a the feature for passing initial setup data to more complex policies (e.g., setting a volume limit for a [`VolumePolicy`](../src/policies/VolumePolicy.sol)). Since our `LockoutPolicy` doesn't need any initial configuration, we pass an empty byte array to explicitly provide no parameters. The `initialize` function passes these bytes to an internal `configure` function, which you can override to handle this data in your own policies.
126
+ 3. **Add it to the engine**: To use the version of our policy that checks the `recipient`, you must specify the parameter name. Assume we have an [`ERC20TransferExtractor`](../src/extractors/ERC20TransferExtractor.sol) registered for our target function.
127
+
128
+ ```solidity
129
+ // Define the parameter name your policy needs
130
+ bytes32[] memory policyParams = new bytes32[](1);
131
+ policyParams[0] = keccak256("to"); // This must match the name from the Extractor
132
+
133
+ // Add the policy to the engine and specify its required parameter
134
+ policyEngine.addPolicy(
135
+ address(myContract),
136
+ myContract.transfer.selector,
137
+ address(lockoutPolicy),
138
+ policyParams
139
+ );
140
+ ```
141
+
142
+ You can then call `lockoutPolicy.setLockout(LOCKED_ADDRESS, 3600)` to block a recipient for one hour. Any transfer to that address will be rejected.
143
+
144
+ You now have a complete, secure, and reusable custom policy and the knowledge needed to create many more!
145
+
146
+ ## Boilerplate Template for Custom Policies
147
+
148
+ Here is a clean, commented template that you can use as a starting point for your own custom policies.
149
+
150
+ ```solidity
151
+ // SPDX-License-Identifier: MIT
152
+ pragma solidity 0.8.26;
153
+
154
+ import { Policy } from "@chainlink/policy-management/core/Policy.sol";
155
+ import { IPolicyEngine } from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
156
+
157
+ contract MyCustomPolicy is Policy {
158
+ /**
159
+ * @notice Use the `configure` function to decode and store any initial
160
+ * setup data passed into the `configParams` of the `initialize` function.
161
+ */
162
+ // function configure(bytes calldata parameters) internal override onlyInitializing {
163
+ // // Your custom initialization logic here...
164
+ // }
165
+
166
+ function run(
167
+ address caller,
168
+ address subject,
169
+ bytes4 selector,
170
+ bytes[] calldata parameters,
171
+ bytes calldata context
172
+ ) public view virtual override returns (IPolicyEngine.PolicyResult) {
173
+ // Your custom validation logic here...
174
+ //
175
+ // Based on your logic, return one of the two possible results or revert.
176
+
177
+ // Revert with PolicyRejected to definitively block the transaction.
178
+ // This HALTS execution and bypasses all subsequent policies.
179
+ // if (condition_for_rejection) {
180
+ // revert IPolicyEngine.PolicyRejected("reason for rejection");
181
+ // }
182
+
183
+ // Return ALLOWED to definitively approve the transaction.
184
+ // This ALSO HALTS execution and bypasses all subsequent policies.
185
+ // This is powerful and should be used with care (e.g., for an admin bypass).
186
+ // if (condition_for_allowance) {
187
+ // return IPolicyEngine.PolicyResult.Allowed;
188
+ // }
189
+
190
+ // Return CONTINUE if your check passes but other policies in the chain should still be executed.
191
+ // This is the most common return value for a passing check.
192
+ return IPolicyEngine.PolicyResult.Continue;
193
+ }
194
+ }
195
+ ```
@@ -0,0 +1,91 @@
1
+ # Policy Ordering Guide
2
+
3
+ ## Why Order Matters
4
+
5
+ Policies execute sequentially in the exact order they were added to the `PolicyEngine`. The execution order is critical because:
6
+
7
+ - **reverts `PolicyRejected`** - Immediately stops execution and reverts the transaction
8
+ - **`Allowed`** - Immediately stops execution and allows the transaction (skips all remaining policies)
9
+ - **`Continue`** - Proceeds to the next policy in the chain, or applies the default engine behavior if no more policies remain
10
+
11
+ This means the position of each policy in the chain directly affects which policies get executed.
12
+
13
+ ## Adding Policies
14
+
15
+ ### `addPolicy()` - Append to End
16
+
17
+ Adds a policy to the end of the existing policy chain.
18
+
19
+ ```solidity
20
+ // Current chain: []
21
+ policyEngine.addPolicy(target, selector, address(policyA), params);
22
+ // Result: [PolicyA]
23
+
24
+ policyEngine.addPolicy(target, selector, address(policyB), params);
25
+ // Result: [PolicyA, PolicyB]
26
+
27
+ policyEngine.addPolicy(target, selector, address(policyC), params);
28
+ // Result: [PolicyA, PolicyB, PolicyC]
29
+ ```
30
+
31
+ ### `addPolicyAt()` - Insert at Specific Position
32
+
33
+ Inserts a policy at a specific position, shifting existing policies to the right.
34
+
35
+ ```solidity
36
+ // Current chain: [PolicyA, PolicyB, PolicyC]
37
+
38
+ // Insert PolicyD at position 1
39
+ policyEngine.addPolicyAt(target, selector, address(policyD), params, 1);
40
+ // Result: [PolicyA, PolicyD, PolicyB, PolicyC]
41
+
42
+ // Insert PolicyE at position 0 (first position)
43
+ policyEngine.addPolicyAt(target, selector, address(policyE), params, 0);
44
+ // Result: [PolicyE, PolicyA, PolicyD, PolicyB, PolicyC]
45
+ ```
46
+
47
+ ## Removing Policies
48
+
49
+ ### `removePolicy()` - Remove by Address
50
+
51
+ Removes a specific policy from the chain, shifting remaining policies to maintain order.
52
+
53
+ ```solidity
54
+ // Current chain: [PolicyA, PolicyB, PolicyC, PolicyD]
55
+
56
+ policyEngine.removePolicy(target, selector, address(policyB));
57
+ // Result: [PolicyA, PolicyC, PolicyD]
58
+
59
+ policyEngine.removePolicy(target, selector, address(policyA));
60
+ // Result: [PolicyC, PolicyD]
61
+ ```
62
+
63
+ ## Checking Current Order
64
+
65
+ Use `getPolicies()` to view the current policy chain in execution order:
66
+
67
+ ```solidity
68
+ address[] memory policies = policyEngine.getPolicies(target, selector);
69
+ // Returns: [policy1Address, policy2Address, policy3Address, ...]
70
+ // Array index 0 executes first, then index 1, then index 2, etc.
71
+ ```
72
+
73
+ ## Reordering Existing Policies
74
+
75
+ To change the position of an existing policy:
76
+
77
+ 1. Remove the policy from its current position
78
+ 2. Add it back at the desired position
79
+
80
+ ```solidity
81
+ // Current: [PolicyA, PolicyB, PolicyC]
82
+ // Goal: Move PolicyC to first position
83
+
84
+ // Step 1: Remove PolicyC
85
+ policyEngine.removePolicy(target, selector, address(policyC));
86
+ // Result: [PolicyA, PolicyB]
87
+
88
+ // Step 2: Add PolicyC at position 0
89
+ policyEngine.addPolicyAt(target, selector, address(policyC), params, 0);
90
+ // Result: [PolicyC, PolicyA, PolicyB]
91
+ ```
@@ -0,0 +1,57 @@
1
+ # Security Considerations: Policy Management
2
+
3
+ The Policy Management component provides a powerful and flexible way to enforce on-chain rules, but this flexibility requires careful attention to security during implementation and administration.
4
+
5
+ ## 1. Policy Administration is a Critical Control
6
+
7
+ The ability to add, remove, or reorder policies in the `PolicyEngine` is the most sensitive administrative power in the system. An attacker who gains control over these functions can effectively disable or bypass all compliance rules.
8
+
9
+ - **Access Control:** Only highly trusted roles, such as a DAO, a multi-sig wallet, or a designated `owner`, MUST be able to call `addPolicy`, `removePolicy`, `setExtractor`, `setPolicyMapper`, and `setDefaultAllow`.
10
+ - **Timelocks:** It is RECOMMENDED that all administrative changes to the `PolicyEngine` are passed through a timelock contract. This creates a delay between the proposal of a change and its execution, giving users time to review the change and exit the system if they do not agree with it.
11
+
12
+ ## 2. Policy Order Matters
13
+
14
+ The `PolicyEngine` executes policies for a given function in a specific, ordered sequence. The outcome of the entire chain can change dramatically based on this order.
15
+
16
+ - **`Allowed` as a Bypass:** A policy that returns `Allowed` will immediately halt execution and bypass all subsequent policies in the chain. For this reason, permissive policies (like a `BypassPolicy` for admins) should be placed with extreme care, typically at the beginning of the policy chain.
17
+ - **Ordering for Restriction:** Restrictive policies should generally be placed before more lenient ones. For example, a `RejectPolicy` for a hard-coded denylist should come before a `VolumePolicy` to ensure the denied user cannot transact at all.
18
+
19
+ ## 3. Trust in Policy, Extractor, and Mapper Contracts
20
+
21
+ The `PolicyEngine` delegates trust to the individual `Policy`, `Extractor`, and `Mapper` contracts it is configured to use. A vulnerability in any one of these components can compromise the entire system.
22
+
23
+ - **Policy Trust:** Malicious or poorly written policies can introduce vulnerabilities. Only install trusted, audited policies.
24
+ - **`postRun` State Changes:** The `postRun` function on a policy can modify state. This is a powerful feature that could be used for malicious purposes if the policy is not trustworthy (e.g., draining funds, changing ownership).
25
+ - **Extractor/Mapper Trust:** The `PolicyEngine` relies on `Extractors` to correctly and honestly parse transaction data. If an extractor is compromised or misrepresents data, policies may make decisions based on false information, potentially leading to a bypass. For example, an extractor could lie about the `value` of a transfer to circumvent a `VolumePolicy`.
26
+
27
+ ## 4. External Call Risks in Policy Logic
28
+
29
+ Policies that make external calls during execution can introduce certain risks, though the specific risks depend on whether the policy functions are `view` or state-changing.
30
+
31
+ - **External Calls in Policies:** Many policies make external calls during their `run()` function execution. Examples include:
32
+ - Data source queries (e.g., `SanctionsPolicy` calling `dataRegistry.getDataSource()` and `sanctionsList.isSanctioned()`)
33
+ - Identity verification (e.g., `CredentialRegistryIdentityValidatorPolicy` calling multiple registry contracts)
34
+ - Price feeds, oracles, or other external data sources
35
+ - **Risks for `view` Policy Functions:** Since most policy `run()` functions are `view` (read-only), traditional reentrancy attacks are **not possible** because no state can be modified. However, other risks exist:
36
+ - **Denial of Service** - malicious external contracts could revert or consume excessive gas
37
+ - **Inconsistent reads** - external contract state could change between multiple calls within the same policy
38
+ - **Gas exhaustion** - deep call chains could cause transaction failures
39
+ - **Risks for State-Changing Policy Functions:** Policies with non-view functions (like `postRun()`) could be vulnerable to traditional reentrancy if they make external calls and modify state
40
+ - **Architectural Considerations:** Since protected contracts are **decoupled** from policy logic and policies can be changed dynamically:
41
+ - **Protected contract developers** cannot predict which policies will be applied to their functions
42
+ - **Policy administrators** bear the responsibility for ensuring safe policy combinations
43
+ - **External call risks** should be considered at the policy composition level, not forced onto every protected function
44
+ - **Mitigation Strategies:**
45
+ - **Use trusted external contracts** - only interact with well-established, audited external contracts in policy logic to minimize DoS and manipulation risks
46
+ - **Implement proper error handling** - ensure policies gracefully handle external contract failures rather than causing transaction reverts
47
+ - **Consider gas limits** - external calls in policies consume gas and could cause transaction failures if gas limits are exceeded
48
+ - **For non-view policies** - if a policy's `postRun()` or other functions modify state and make external calls, consider reentrancy protection
49
+ - **Policy administrators should assess risk** - when adding policies with external calls, evaluate the trustworthiness of the external contracts being called
50
+
51
+ ## 5. `context` Handling and Race Conditions
52
+
53
+ The `context` field in the `PolicyEngine.Payload` is a powerful feature for passing arbitrary data, but it must be managed carefully to avoid race conditions and incorrect usage.
54
+
55
+ - **Context is Per-Sender, Not Per-Transaction:** The standard implementation of `PolicyProtected` stores context in a mapping (`sender` => `context`). It is **not** automatically cleared after every transaction. If context is set but not consumed in the same transaction by a protected method, the stale context may be incorrectly reused by a subsequent call from the same sender.
56
+ - **Atomic Operations Recommended:** To mitigate this, it is **strongly recommended** to set and consume `context` within the same atomic transaction. The caller should first call `setContext(bytes)` and then immediately call the protected function. The `runPolicy` modifier will then consume the context and clear it.
57
+ - **Re-entrancy & Multi-User Scenarios:** In contracts that can be used by multiple users (like relayers or governance contracts), context from one user could potentially be overwritten by another before it is consumed. Implementations must ensure that context cannot be mismatched, especially in scenarios involving re-entrancy.
@@ -0,0 +1,124 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IPolicy} from "../interfaces/IPolicy.sol";
5
+ import {IPolicyEngine} from "../interfaces/IPolicyEngine.sol";
6
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
7
+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
8
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
9
+ import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
10
+
11
+ abstract contract Policy is Initializable, OwnableUpgradeable, ERC165Upgradeable, IPolicy {
12
+ error Unauthorized();
13
+
14
+ /// @custom:storage-location erc7201:policy-management.Policy
15
+ struct PolicyStorage {
16
+ address policyEngine;
17
+ }
18
+
19
+ // keccak256(abi.encode(uint256(keccak256("policy-management.Policy")) - 1)) &
20
+ // ~bytes32(uint256(0xff))
21
+ // solhint-disable-next-line const-name-snakecase
22
+ bytes32 private constant PolicyStorageLocation = 0xe4b9805cdebef99d9d38a3fed61079cbd4f3c0d610c4396bdc28a2ed8ad07100;
23
+
24
+ function _getPolicyStorage() private pure returns (PolicyStorage storage $) {
25
+ // solhint-disable-next-line no-inline-assembly
26
+ assembly {
27
+ $.slot := PolicyStorageLocation
28
+ }
29
+ }
30
+
31
+ constructor() {
32
+ _disableInitializers();
33
+ }
34
+
35
+ modifier onlyPolicyEngine() {
36
+ if (msg.sender != _getPolicyStorage().policyEngine) {
37
+ revert Unauthorized();
38
+ }
39
+ _;
40
+ }
41
+
42
+ /**
43
+ * @notice Initializes the policy contract.
44
+ * @dev This function MUST be called immediately after deployment (or via proxy initializer),
45
+ * setting up the policy with the address of the policy engine, initial ownership, and
46
+ * any configuration parameters required by the policy.
47
+ * @param policyEngine The address of the policy engine that will invoke this policy.
48
+ * @param initialOwner The address that will be assigned as the initial owner of the policy contract.
49
+ * @param configParams Arbitrary encoded configuration parameters to initialize the policy logic.
50
+ */
51
+ function initialize(
52
+ address policyEngine,
53
+ address initialOwner,
54
+ bytes calldata configParams
55
+ )
56
+ public
57
+ virtual
58
+ initializer
59
+ {
60
+ __Policy_init(policyEngine, initialOwner);
61
+ configure(configParams);
62
+ }
63
+
64
+ // solhint-disable-next-line no-empty-blocks
65
+ function configure(bytes calldata parameters) internal virtual onlyInitializing {}
66
+
67
+ function __Policy_init(address policyEngine, address initialOwner) internal onlyInitializing {
68
+ __Policy_init_unchained(policyEngine);
69
+ __Ownable_init(initialOwner);
70
+ __ERC165_init();
71
+ }
72
+
73
+ function __Policy_init_unchained(address policyEngine) internal onlyInitializing {
74
+ _getPolicyStorage().policyEngine = policyEngine;
75
+ }
76
+
77
+ /// @inheritdoc IPolicy
78
+ // solhint-disable-next-line no-empty-blocks
79
+ function onInstall(bytes4 /*selector*/ ) public virtual override onlyPolicyEngine {}
80
+
81
+ /// @inheritdoc IPolicy
82
+ // solhint-disable-next-line no-empty-blocks
83
+ function onUninstall(bytes4 /*selector*/ ) public virtual override onlyPolicyEngine {}
84
+
85
+ function run(
86
+ address caller,
87
+ address subject,
88
+ bytes4 selector,
89
+ bytes[] calldata parameters,
90
+ bytes calldata context
91
+ )
92
+ public
93
+ view
94
+ virtual
95
+ override
96
+ returns (IPolicyEngine.PolicyResult);
97
+
98
+ function postRun(
99
+ address, /*caller*/
100
+ address, /*subject*/
101
+ bytes4, /*selector*/
102
+ bytes[] calldata, /*parameters*/
103
+ bytes calldata /*context*/
104
+ )
105
+ public
106
+ virtual
107
+ override
108
+ onlyPolicyEngine
109
+ // solhint-disable-next-line no-empty-blocks
110
+ {}
111
+
112
+ /**
113
+ * @dev See {ERC165-supportsInterface}.
114
+ */
115
+ function supportsInterface(bytes4 interfaceId)
116
+ public
117
+ view
118
+ virtual
119
+ override(ERC165Upgradeable, IERC165)
120
+ returns (bool)
121
+ {
122
+ return interfaceId == type(IPolicy).interfaceId || super.supportsInterface(interfaceId);
123
+ }
124
+ }