@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,192 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
5
+ import {Policy} from "@chainlink/policy-management/core/Policy.sol";
6
+
7
+ /**
8
+ * @title VolumeRatePolicy
9
+ * @notice A policy for limiting the volume of transfers within a specified time period for each account.
10
+ * @dev Implements Chainlink's Policy reference implementation and OpenZeppelin's Ownable for access control.
11
+ * This policy enforces limits on the total amount transferred per account over a configurable time period.
12
+ */
13
+ contract VolumeRatePolicy is Policy {
14
+ /// @notice The transfer volume data of an account.
15
+ struct TransferredAt {
16
+ /// @notice The time period in which the transfer occurred.
17
+ /// @dev calculated as block.timestamp / timePeriodDuration.
18
+ uint256 timePeriod;
19
+ /// @notice The duration of the time period. This is used to evict the data if the time period duration changes.
20
+ uint256 timePeriodDuration;
21
+ /// @notice The total amount transferred in the current time period.
22
+ uint256 amount;
23
+ }
24
+
25
+ /// @notice Emitted when the maximum transfer amount is set.
26
+ event MaxAmountSet(uint256 maxAmount);
27
+ /// @notice Emitted when the time period duration is set.
28
+ event TimePeriodDurationSet(uint256 timePeriodDuration);
29
+
30
+ /// @custom:storage-location erc7201:policy-management.VolumeRatePolicy
31
+ struct VolumeRatePolicyStorage {
32
+ /// @notice The duration (in seconds) of the time period for tracking transfers.
33
+ uint256 timePeriodDuration;
34
+ /// @notice Tracks the transfer volume and time period for a specific account.
35
+ mapping(address account => TransferredAt transferredAt) transferredAtByAmount;
36
+ /// @notice The maximum allowed transfer amount within a single time period
37
+ uint256 maxAmount;
38
+ }
39
+
40
+ // keccak256(abi.encode(uint256(keccak256("policy-management.VolumeRatePolicy")) - 1)) & ~bytes32(uint256(0xff))
41
+ bytes32 private constant VolumeRatePolicyStorageLocation =
42
+ 0xcb25215b4838d4e292145a50fb0434197be06c30a5e619d206e7746dc76f7700;
43
+
44
+ function _getVolumeRatePolicyStorage() private pure returns (VolumeRatePolicyStorage storage $) {
45
+ assembly {
46
+ $.slot := VolumeRatePolicyStorageLocation
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @notice Configures the policy by setting the time period duration and the maximum allowed transfer amount.
52
+ * @param parameters ABI-encoded bytes containing two `uint256` values: the time period duration and the max amount.
53
+ * - `timePeriodDuration`: The duration (in seconds) of the time period for tracking transfers.
54
+ * - `maxAmount`: The maximum allowed transfer amount within a single time period.
55
+ */
56
+ function configure(bytes calldata parameters) internal override {
57
+ (uint256 timePeriodDuration, uint256 maxAmount) = abi.decode(parameters, (uint256, uint256));
58
+ require(timePeriodDuration != 0, "Time period duration must be non-zero");
59
+ VolumeRatePolicyStorage storage $ = _getVolumeRatePolicyStorage(); // Gas optimization: single storage reference
60
+ $.timePeriodDuration = timePeriodDuration;
61
+ $.maxAmount = maxAmount;
62
+ }
63
+
64
+ /**
65
+ * @notice Sets the maximum transfer amount within a single time period.
66
+ * @dev Reverts if the new maximum amount is the same as the current one.
67
+ * @param _maxAmount The new maximum transfer amount.
68
+ */
69
+ function setMaxAmount(uint256 _maxAmount) public onlyOwner {
70
+ VolumeRatePolicyStorage storage $ = _getVolumeRatePolicyStorage(); // Gas optimization: single storage reference
71
+ require($.maxAmount != _maxAmount, "new max amount same as current max amount");
72
+ $.maxAmount = _maxAmount;
73
+ emit MaxAmountSet($.maxAmount);
74
+ }
75
+
76
+ /**
77
+ * @notice Gets the maximum transfer amount within a single time period.
78
+ * @return maxAmount The maximum transfer amount.
79
+ */
80
+ function getMaxAmount() public view returns (uint256) {
81
+ VolumeRatePolicyStorage storage $ = _getVolumeRatePolicyStorage();
82
+ return $.maxAmount;
83
+ }
84
+
85
+ /**
86
+ * @notice Sets the time period duration for tracking transfers.
87
+ * @dev All volume tracking will reset after time period duration change.
88
+ * @param _timePeriodDuration The duration (in seconds) of the time period.
89
+ */
90
+ function setTimePeriodDuration(uint256 _timePeriodDuration) public onlyOwner {
91
+ require(_timePeriodDuration != 0, "Time period duration must be non-zero");
92
+ VolumeRatePolicyStorage storage $ = _getVolumeRatePolicyStorage(); // Gas optimization: single storage reference
93
+ require($.timePeriodDuration != _timePeriodDuration, "new duration same as current duration");
94
+ $.timePeriodDuration = _timePeriodDuration;
95
+ emit TimePeriodDurationSet($.timePeriodDuration);
96
+ }
97
+
98
+ /**
99
+ * @notice Gets the time period duration for tracking transfers.
100
+ * @return timePeriodDuration The duration (in seconds) of the time period.
101
+ */
102
+ function getTimePeriodDuration() public view returns (uint256) {
103
+ VolumeRatePolicyStorage storage $ = _getVolumeRatePolicyStorage();
104
+ return $.timePeriodDuration;
105
+ }
106
+
107
+ /**
108
+ * @notice Decodes the parameters.
109
+ * @param parameters [amount(uint256), from(address)] The parameters of the called method.
110
+ * @return data The transfer amount and the sender address.
111
+ */
112
+ function _extractParameters(bytes[] calldata parameters) internal pure returns (uint256, address) {
113
+ require(parameters.length == 2, "expected 2 parameters");
114
+
115
+ uint256 amount = abi.decode(parameters[0], (uint256));
116
+ address account = abi.decode(parameters[1], (address));
117
+
118
+ return (amount, account);
119
+ }
120
+
121
+ /**
122
+ * @notice Function to be called by the policy engine to check if execution is allowed.
123
+ * @param parameters [amount(uint256), from(address)] The parameters of the called method.
124
+ * @return result The result of the policy check.
125
+ */
126
+ function run(
127
+ address, /*caller*/
128
+ address, /*subject*/
129
+ bytes4, /*selector*/
130
+ bytes[] calldata parameters,
131
+ bytes calldata /*context*/
132
+ )
133
+ public
134
+ view
135
+ override
136
+ returns (IPolicyEngine.PolicyResult)
137
+ {
138
+ (uint256 amount, address account) = _extractParameters(parameters);
139
+
140
+ VolumeRatePolicyStorage storage $ = _getVolumeRatePolicyStorage(); // Gas optimization: single storage reference
141
+ uint256 timePeriod = block.timestamp / $.timePeriodDuration;
142
+
143
+ TransferredAt memory transferredAt = $.transferredAtByAmount[account];
144
+
145
+ if (timePeriod == transferredAt.timePeriod && transferredAt.timePeriodDuration == $.timePeriodDuration) {
146
+ if (transferredAt.amount + amount > $.maxAmount) {
147
+ revert IPolicyEngine.PolicyRejected("volume rate limit exceeded for time period");
148
+ } else {
149
+ return IPolicyEngine.PolicyResult.Continue;
150
+ }
151
+ }
152
+
153
+ if (amount > $.maxAmount) {
154
+ revert IPolicyEngine.PolicyRejected("volume rate limit exceeded for time period");
155
+ }
156
+
157
+ return IPolicyEngine.PolicyResult.Continue;
158
+ }
159
+
160
+ /**
161
+ * @notice Runs after the policy check if the check was successful, and updates the transfer volume tracking for
162
+ * the account. This function is called by the policy engine after run() succeeds but before the protected
163
+ * target function executes.
164
+ * @param parameters [from(address), amount(uint256)] The parameters of the called method.
165
+ */
166
+ function postRun(
167
+ address, /*caller*/
168
+ address, /*subject*/
169
+ bytes4, /*selector*/
170
+ bytes[] calldata parameters,
171
+ bytes calldata /*context*/
172
+ )
173
+ public
174
+ override
175
+ onlyPolicyEngine
176
+ {
177
+ (uint256 amount, address account) = _extractParameters(parameters);
178
+
179
+ VolumeRatePolicyStorage storage $ = _getVolumeRatePolicyStorage(); // Gas optimization: single storage reference
180
+ uint256 timePeriod = block.timestamp / $.timePeriodDuration;
181
+
182
+ TransferredAt storage transferredAt = $.transferredAtByAmount[account];
183
+
184
+ if (transferredAt.timePeriod == timePeriod && transferredAt.timePeriodDuration == $.timePeriodDuration) {
185
+ transferredAt.amount += amount;
186
+ } else {
187
+ transferredAt.timePeriod = timePeriod;
188
+ transferredAt.timePeriodDuration = $.timePeriodDuration;
189
+ transferredAt.amount = amount;
190
+ }
191
+ }
192
+ }
@@ -0,0 +1,368 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IPolicyEngine} from "../src/interfaces/IPolicyEngine.sol";
5
+ import {IExtractor} from "../src/interfaces/IExtractor.sol";
6
+ import {PolicyEngine} from "../src/core/PolicyEngine.sol";
7
+ import {PolicyAlwaysAllowed, PolicyAlwaysAllowedWithPostRunError} from "./helpers/PolicyAlwaysAllowed.sol";
8
+ import {PolicyAlwaysRejected} from "./helpers/PolicyAlwaysRejected.sol";
9
+ import {PolicyFailingRun} from "./helpers/PolicyFailingRun.sol";
10
+ import {DummyExtractor} from "./helpers/DummyExtractor.sol";
11
+ import {ExpectedParameterPolicy} from "./helpers/ExpectedParameterPolicy.sol";
12
+ import {CustomMapper} from "./helpers/CustomMapper.sol";
13
+ import {BaseProxyTest} from "./helpers/BaseProxyTest.sol";
14
+ import {PolicyAlwaysContinue} from "./helpers/PolicyAlwaysContinue.sol";
15
+
16
+ contract PolicyEngineTest is BaseProxyTest {
17
+ PolicyEngine public policyEngine;
18
+ IExtractor public extractor;
19
+ bytes4 public constant selector = bytes4(keccak256("someSelector()"));
20
+ IPolicyEngine.Payload public testPayload;
21
+ address target;
22
+
23
+ PolicyAlwaysAllowed public policyAlwaysAllowedImpl;
24
+ PolicyAlwaysRejected public policyAlwaysRejectedImpl;
25
+ PolicyAlwaysContinue public policyAlwaysContinueImpl;
26
+ PolicyFailingRun public policyFailingRunImpl;
27
+ PolicyAlwaysAllowedWithPostRunError public policyAllowedWithPostRunErrorImpl;
28
+ ExpectedParameterPolicy public expectedParameterPolicyImpl;
29
+
30
+ function setUp() public {
31
+ policyEngine = _deployPolicyEngine(false, address(this));
32
+
33
+ target = makeAddr("target");
34
+
35
+ extractor = new DummyExtractor();
36
+
37
+ policyAlwaysAllowedImpl = new PolicyAlwaysAllowed();
38
+ policyAlwaysRejectedImpl = new PolicyAlwaysRejected();
39
+ policyAlwaysContinueImpl = new PolicyAlwaysContinue();
40
+ policyFailingRunImpl = new PolicyFailingRun();
41
+ policyAllowedWithPostRunErrorImpl = new PolicyAlwaysAllowedWithPostRunError();
42
+ expectedParameterPolicyImpl = new ExpectedParameterPolicy();
43
+
44
+ testPayload = IPolicyEngine.Payload({selector: selector, sender: target, data: new bytes(0), context: new bytes(0)});
45
+ }
46
+
47
+ function test_setExtractor_storesExtractorAndEmitsEvent() public {
48
+ vm.expectEmit();
49
+ emit IPolicyEngine.ExtractorSet(selector, address(extractor));
50
+
51
+ policyEngine.setExtractor(selector, address(extractor));
52
+
53
+ address storedExtractor = policyEngine.getExtractor(selector);
54
+
55
+ assertEq(storedExtractor, address(extractor), "Extractor should be set");
56
+ }
57
+
58
+ function test_addPolicy_storesPolicyAndEmitsEvent() public {
59
+ PolicyAlwaysAllowed policy = PolicyAlwaysAllowed(
60
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
61
+ );
62
+
63
+ vm.expectEmit();
64
+ emit IPolicyEngine.PolicyAdded(target, selector, address(policy));
65
+
66
+ policyEngine.addPolicy(target, selector, address(policy), new bytes32[](0));
67
+
68
+ address[] memory policies = policyEngine.getPolicies(target, selector);
69
+
70
+ assertEq(policies.length, 1, "Policy should be added");
71
+ assertEq(policies[0], address(policy), "Policy address should match");
72
+ }
73
+
74
+ function test_addPolicy_thatIsDuplicate_thenReverts() public {
75
+ PolicyAlwaysAllowed policy = PolicyAlwaysAllowed(
76
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
77
+ );
78
+
79
+ policyEngine.addPolicy(target, selector, address(policy), new bytes32[](0));
80
+
81
+ vm.expectRevert(abi.encodeWithSelector(IPolicyEngine.InvalidConfiguration.selector, "Policy already added"));
82
+ policyEngine.addPolicy(target, selector, address(policy), new bytes32[](0));
83
+ }
84
+
85
+ function test_run_whenNoPoliciesAddedThenDefaultPolicyIsUsed() public {
86
+ bytes memory expectedRevert = abi.encodeWithSelector(
87
+ IPolicyEngine.PolicyRunRejected.selector, 0, address(0), "no policy allowed the action and default is reject"
88
+ );
89
+
90
+ vm.expectRevert(expectedRevert);
91
+ vm.startPrank(target);
92
+ policyEngine.run(testPayload);
93
+ }
94
+
95
+ function test_run_whenNoPoliciesDefaultAllowedEmitsCompleteEvent() public {
96
+ policyEngine.setDefaultPolicyAllow(true);
97
+
98
+ vm.startPrank(target);
99
+
100
+ vm.expectEmit();
101
+ emit IPolicyEngine.PolicyRunComplete(testPayload.sender, target, testPayload.selector);
102
+ policyEngine.run(testPayload);
103
+ }
104
+
105
+ function test_run_whenSingleAllowedPolicyAddedThenPolicyIsUsed() public {
106
+ PolicyAlwaysAllowed policy = PolicyAlwaysAllowed(
107
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
108
+ );
109
+
110
+ policyEngine.addPolicy(target, selector, address(policy), new bytes32[](0));
111
+
112
+ bool success;
113
+
114
+ vm.expectEmit();
115
+ emit PolicyAlwaysAllowed.PolicyAllowedExecuted(1);
116
+
117
+ vm.startPrank(target);
118
+
119
+ vm.expectEmit();
120
+ emit IPolicyEngine.PolicyRunComplete(testPayload.sender, target, testPayload.selector);
121
+ policyEngine.run(testPayload);
122
+ }
123
+
124
+ function test_run_whenSingleContinuedPolicyAddedThenPolicyIsUsed() public {
125
+ policyEngine.setDefaultPolicyAllow(true);
126
+
127
+ PolicyAlwaysContinue policy =
128
+ PolicyAlwaysContinue(_deployPolicy(address(policyAlwaysContinueImpl), address(policyEngine), address(this), ""));
129
+
130
+ policyEngine.addPolicy(target, selector, address(policy), new bytes32[](0));
131
+
132
+ vm.startPrank(target);
133
+
134
+ vm.expectEmit();
135
+ emit IPolicyEngine.PolicyRunComplete(testPayload.sender, target, testPayload.selector);
136
+ policyEngine.run(testPayload);
137
+ }
138
+
139
+ function test_run_whenRejectingPolicyPrecedesAllowingPolicyThenRevertsOccurs() public {
140
+ PolicyAlwaysRejected policyRejected = PolicyAlwaysRejected(
141
+ _deployPolicy(address(policyAlwaysRejectedImpl), address(policyEngine), address(this), new bytes(0))
142
+ );
143
+ PolicyAlwaysAllowed policyAllowed = PolicyAlwaysAllowed(
144
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
145
+ );
146
+
147
+ policyEngine.addPolicy(target, selector, address(policyRejected), new bytes32[](0));
148
+ policyEngine.addPolicy(target, selector, address(policyAllowed), new bytes32[](0));
149
+
150
+ vm.startPrank(target);
151
+ vm.expectRevert(_encodeRejectedRevert(selector, address(policyRejected), "test policy always rejects"));
152
+
153
+ policyEngine.run(testPayload);
154
+ }
155
+
156
+ function test_run_whenAllowingPolicyPrecedesRejectingPolicyThenTransactionGoesThrough() public {
157
+ PolicyAlwaysRejected policyRejected = PolicyAlwaysRejected(
158
+ _deployPolicy(address(policyAlwaysRejectedImpl), address(policyEngine), address(this), new bytes(0))
159
+ );
160
+ PolicyAlwaysAllowed policyAllowed = PolicyAlwaysAllowed(
161
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
162
+ );
163
+
164
+ policyEngine.addPolicy(target, selector, address(policyAllowed), new bytes32[](0));
165
+ policyEngine.addPolicy(target, selector, address(policyRejected), new bytes32[](0));
166
+
167
+ bool success;
168
+
169
+ vm.expectEmit();
170
+ emit PolicyAlwaysAllowed.PolicyAllowedExecuted(1);
171
+ vm.startPrank(target);
172
+
173
+ try policyEngine.run(testPayload) {
174
+ success = true;
175
+ } catch {
176
+ success = false;
177
+ }
178
+
179
+ assertTrue(success, "Policy should allow execution");
180
+ }
181
+
182
+ function test_run_whenPolicyRevertsTransactionReverts() public {
183
+ PolicyFailingRun policyFailingRun =
184
+ PolicyFailingRun(_deployPolicy(address(policyFailingRunImpl), address(policyEngine), address(this), new bytes(0)));
185
+ PolicyAlwaysAllowed policyAllowed = PolicyAlwaysAllowed(
186
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
187
+ );
188
+
189
+ policyEngine.addPolicy(target, selector, address(policyFailingRun), new bytes32[](0));
190
+ policyEngine.addPolicy(target, selector, address(policyAllowed), new bytes32[](0));
191
+
192
+ vm.startPrank(target);
193
+
194
+ vm.expectPartialRevert(IPolicyEngine.PolicyRunError.selector);
195
+ policyEngine.run(testPayload);
196
+ }
197
+
198
+ function test_run_whenAllowingPolicyRevertsOnPostRunAndActionIndicatesItShouldRevertThenTransactionReverts() public {
199
+ PolicyAlwaysAllowedWithPostRunError policyAllowedWithPostRunError = PolicyAlwaysAllowedWithPostRunError(
200
+ _deployPolicy(address(policyAllowedWithPostRunErrorImpl), address(policyEngine), address(this), abi.encode(1))
201
+ );
202
+
203
+ policyEngine.addPolicy(target, selector, address(policyAllowedWithPostRunError), new bytes32[](0));
204
+
205
+ vm.startPrank(target);
206
+
207
+ vm.expectPartialRevert(IPolicyEngine.PolicyPostRunError.selector);
208
+ policyEngine.run(testPayload);
209
+ }
210
+
211
+ function test_run_whenAddingAllowingPolicyAtPrecedingIndexThenTransactionDoesNotRevert() public {
212
+ PolicyAlwaysRejected policyRejected = PolicyAlwaysRejected(
213
+ _deployPolicy(address(policyAlwaysRejectedImpl), address(policyEngine), address(this), new bytes(0))
214
+ );
215
+ PolicyAlwaysAllowed policyAllowed = PolicyAlwaysAllowed(
216
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
217
+ );
218
+
219
+ policyEngine.addPolicy(target, selector, address(policyRejected), new bytes32[](0));
220
+ policyEngine.addPolicyAt(target, selector, address(policyAllowed), new bytes32[](0), 0);
221
+
222
+ bool success;
223
+
224
+ vm.expectEmit();
225
+ emit PolicyAlwaysAllowed.PolicyAllowedExecuted(1);
226
+ vm.startPrank(target);
227
+ try policyEngine.run(testPayload) {
228
+ success = true;
229
+ } catch {
230
+ success = false;
231
+ }
232
+
233
+ assertTrue(success, "Policy should allow execution");
234
+ }
235
+
236
+ function test_removePolicy_whenRemovingPolicyAtIntermediateIndexOrderIsPreserved() public {
237
+ PolicyAlwaysAllowed policyAllowed1 = PolicyAlwaysAllowed(
238
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
239
+ );
240
+ PolicyAlwaysAllowed policyAllowed2 = PolicyAlwaysAllowed(
241
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(2))
242
+ );
243
+ PolicyAlwaysAllowed policyAllowed3 = PolicyAlwaysAllowed(
244
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(3))
245
+ );
246
+ PolicyAlwaysAllowed policyAllowed4 = PolicyAlwaysAllowed(
247
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(4))
248
+ );
249
+
250
+ policyEngine.addPolicy(target, selector, address(policyAllowed1), new bytes32[](0));
251
+ policyEngine.addPolicy(target, selector, address(policyAllowed2), new bytes32[](0));
252
+ policyEngine.addPolicy(target, selector, address(policyAllowed3), new bytes32[](0));
253
+ policyEngine.addPolicy(target, selector, address(policyAllowed4), new bytes32[](0));
254
+
255
+ vm.expectEmit();
256
+ emit IPolicyEngine.PolicyRemoved(target, selector, address(policyAllowed2));
257
+
258
+ policyEngine.removePolicy(target, selector, address(policyAllowed2));
259
+
260
+ address[] memory policies = policyEngine.getPolicies(target, selector);
261
+
262
+ assertEq(policies.length, 3, "Policy should be removed");
263
+ assertEq(policies[0], address(policyAllowed1), "Policy address should match");
264
+ assertEq(policies[1], address(policyAllowed3), "Policy address should match");
265
+ assertEq(policies[2], address(policyAllowed4), "Policy address should match");
266
+ }
267
+
268
+ function test_removePolicy_then_run_omitsRemovedPolicy() public {
269
+ PolicyAlwaysRejected policyRejected = PolicyAlwaysRejected(
270
+ _deployPolicy(address(policyAlwaysRejectedImpl), address(policyEngine), address(this), new bytes(0))
271
+ );
272
+ PolicyAlwaysAllowed policyAllowed = PolicyAlwaysAllowed(
273
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
274
+ );
275
+
276
+ policyEngine.addPolicy(target, selector, address(policyAllowed), new bytes32[](0));
277
+ policyEngine.addPolicy(target, selector, address(policyRejected), new bytes32[](0));
278
+
279
+ bool success;
280
+
281
+ vm.expectEmit();
282
+ emit PolicyAlwaysAllowed.PolicyAllowedExecuted(1);
283
+
284
+ vm.startPrank(target);
285
+ try policyEngine.run(testPayload) {
286
+ success = true;
287
+ } catch {
288
+ success = false;
289
+ }
290
+
291
+ assertTrue(success, "Policy should allow execution");
292
+ vm.stopPrank();
293
+
294
+ vm.startPrank(address(this));
295
+ policyEngine.removePolicy(target, selector, address(policyAllowed));
296
+ vm.stopPrank();
297
+
298
+ vm.startPrank(target);
299
+ vm.expectRevert(_encodeRejectedRevert(selector, address(policyRejected), "test policy always rejects"));
300
+ policyEngine.run(testPayload);
301
+ }
302
+
303
+ function test_run_CustomMapper() public {
304
+ bytes[] memory parameters = new bytes[](1);
305
+ parameters[0] = abi.encode("test_run_CustomMapper");
306
+
307
+ CustomMapper mapper = new CustomMapper();
308
+ mapper.setMappedParameters(parameters);
309
+
310
+ ExpectedParameterPolicy policy = ExpectedParameterPolicy(
311
+ _deployPolicy(address(expectedParameterPolicyImpl), address(policyEngine), address(this), abi.encode(parameters))
312
+ );
313
+
314
+ policyEngine.addPolicy(target, selector, address(policy), new bytes32[](0));
315
+ policyEngine.setPolicyMapper(address(policy), address(mapper));
316
+
317
+ vm.startPrank(target);
318
+ policyEngine.run(testPayload);
319
+ }
320
+
321
+ function test_run_forDifferentTargets() public {
322
+ address secondTarget = makeAddr("secondTarget");
323
+
324
+ PolicyAlwaysAllowed policy = PolicyAlwaysAllowed(
325
+ _deployPolicy(address(policyAlwaysAllowedImpl), address(policyEngine), address(this), abi.encode(1))
326
+ );
327
+
328
+ policyEngine.addPolicy(target, selector, address(policy), new bytes32[](0));
329
+
330
+ PolicyAlwaysRejected policyRejected = PolicyAlwaysRejected(
331
+ _deployPolicy(address(policyAlwaysRejectedImpl), address(policyEngine), address(this), new bytes(0))
332
+ );
333
+
334
+ policyEngine.addPolicy(secondTarget, selector, address(policyRejected), new bytes32[](0));
335
+
336
+ bool success;
337
+ vm.startPrank(target);
338
+ try policyEngine.run(testPayload) {
339
+ success = true;
340
+ } catch {
341
+ success = false;
342
+ }
343
+
344
+ assertTrue(success, "Policy should allow execution");
345
+ vm.stopPrank();
346
+
347
+ vm.startPrank(secondTarget);
348
+ vm.expectRevert(_encodeRejectedRevert(selector, address(policyRejected), "test policy always rejects"));
349
+ policyEngine.run(
350
+ IPolicyEngine.Payload({selector: selector, sender: secondTarget, data: new bytes(0), context: new bytes(0)})
351
+ );
352
+ }
353
+
354
+ function test_run_targetDefaultPolicyTakesPrecedenceOverGlobalDefaultPolicy() public {
355
+ policyEngine.setTargetDefaultPolicyAllow(target, true); // true = allow by default
356
+
357
+ bool success;
358
+
359
+ vm.startPrank(target);
360
+ try policyEngine.run(testPayload) {
361
+ success = true;
362
+ } catch {
363
+ success = false;
364
+ }
365
+
366
+ assertTrue(success, "Policy should allow execution");
367
+ }
368
+ }
@@ -0,0 +1,114 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {PolicyEngine} from "../src/core/PolicyEngine.sol";
5
+ import {PolicyFactory} from "../src/core/PolicyFactory.sol";
6
+ import {PolicyAlwaysAllowed} from "./helpers/PolicyAlwaysAllowed.sol";
7
+ import {CredentialRegistryIdentityValidatorPolicy} from
8
+ "@chainlink/cross-chain-identity/CredentialRegistryIdentityValidatorPolicy.sol";
9
+ import {ICredentialRequirements} from "@chainlink/cross-chain-identity/interfaces/ICredentialRequirements.sol";
10
+ import {Test} from "forge-std/Test.sol";
11
+
12
+ contract PolicyFactoryTest is Test {
13
+ PolicyEngine private s_policyEngine;
14
+ PolicyFactory private s_factory;
15
+
16
+ PolicyAlwaysAllowed private s_policyImplementation;
17
+
18
+ bytes32 public constant REQUIREMENT_KYC = keccak256("KYC");
19
+ bytes32 public constant CREDENTIAL_KYC = keccak256("common.kyc");
20
+
21
+ function setUp() public {
22
+ s_policyEngine = new PolicyEngine();
23
+ s_factory = new PolicyFactory();
24
+ s_policyImplementation = new PolicyAlwaysAllowed();
25
+ }
26
+
27
+ function test_createPolicy_success() public {
28
+ bytes32 policyId = keccak256(abi.encodePacked("policy-1"));
29
+ bytes memory configData = abi.encode(42);
30
+
31
+ address expectedPolicyAddress =
32
+ s_factory.predictPolicyAddress(address(this), address(s_policyImplementation), policyId);
33
+
34
+ vm.expectEmit();
35
+ emit PolicyFactory.PolicyCreated(expectedPolicyAddress);
36
+
37
+ address newPolicyAddress = s_factory.createPolicy(
38
+ address(s_policyImplementation), policyId, address(s_policyEngine), address(this), configData
39
+ );
40
+ assertEq(newPolicyAddress, expectedPolicyAddress);
41
+
42
+ PolicyAlwaysAllowed newPolicy = PolicyAlwaysAllowed(newPolicyAddress);
43
+ assertEq(newPolicy.getPolicyNumber(), 42);
44
+ }
45
+
46
+ function test_createPolicy_duplicateCreate_success() public {
47
+ bytes32 policyId = keccak256(abi.encodePacked("policy-1"));
48
+ bytes memory configData = abi.encode(42);
49
+
50
+ address expectedPolicyAddress =
51
+ s_factory.predictPolicyAddress(address(this), address(s_policyImplementation), policyId);
52
+
53
+ vm.expectEmit();
54
+ emit PolicyFactory.PolicyCreated(expectedPolicyAddress);
55
+
56
+ address newPolicyAddress = s_factory.createPolicy(
57
+ address(s_policyImplementation), policyId, address(s_policyEngine), address(this), configData
58
+ );
59
+ address newPolicyAddress2 = s_factory.createPolicy(
60
+ address(s_policyImplementation), policyId, address(s_policyEngine), address(this), configData
61
+ );
62
+ assertEq(newPolicyAddress, newPolicyAddress2);
63
+ }
64
+
65
+ function test_createPolicy_badconfigData_revert() public {
66
+ bytes32 policyId = keccak256(abi.encodePacked("policy-1"));
67
+
68
+ vm.expectPartialRevert(PolicyFactory.PolicyInitializationFailed.selector);
69
+ s_factory.createPolicy(address(s_policyImplementation), policyId, address(s_policyEngine), address(this), "0x1234");
70
+ }
71
+
72
+ function test_createCredentialRegistryIdentityValidatorPolicy_success() public {
73
+ bytes32 policyId = keccak256(abi.encodePacked("policy-1"));
74
+ address identityRegistry = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, "identity"))));
75
+ address credentialRegistry = vm.addr(uint256(keccak256(abi.encodePacked(block.timestamp, "credential"))));
76
+ bytes32[] memory credentialsKyc = new bytes32[](1);
77
+ credentialsKyc[0] = CREDENTIAL_KYC;
78
+
79
+ ICredentialRequirements.CredentialRequirementInput[] memory requirementsInput =
80
+ new ICredentialRequirements.CredentialRequirementInput[](1);
81
+ requirementsInput[0] = ICredentialRequirements.CredentialRequirementInput(REQUIREMENT_KYC, credentialsKyc, 1, false);
82
+
83
+ ICredentialRequirements.CredentialSourceInput[] memory sourceInputs =
84
+ new ICredentialRequirements.CredentialSourceInput[](1);
85
+
86
+ sourceInputs[0] =
87
+ ICredentialRequirements.CredentialSourceInput(CREDENTIAL_KYC, identityRegistry, credentialRegistry, address(0));
88
+
89
+ bytes memory configData = abi.encode(sourceInputs, requirementsInput);
90
+
91
+ CredentialRegistryIdentityValidatorPolicy identityValidatorPolicy = new CredentialRegistryIdentityValidatorPolicy();
92
+
93
+ address expectedPolicyAddress =
94
+ s_factory.predictPolicyAddress(address(this), address(identityValidatorPolicy), policyId);
95
+
96
+ vm.expectEmit();
97
+ emit PolicyFactory.PolicyCreated(expectedPolicyAddress);
98
+
99
+ address newPolicyAddress = s_factory.createPolicy(
100
+ address(identityValidatorPolicy), policyId, address(s_policyEngine), address(this), configData
101
+ );
102
+ assertEq(newPolicyAddress, expectedPolicyAddress);
103
+
104
+ CredentialRegistryIdentityValidatorPolicy deployedPolicy =
105
+ CredentialRegistryIdentityValidatorPolicy(newPolicyAddress);
106
+ ICredentialRequirements.CredentialSource[] memory credentialSources =
107
+ deployedPolicy.getCredentialSources(CREDENTIAL_KYC);
108
+ assertEq(credentialSources.length, 1);
109
+ assertEq(credentialSources[0].identityRegistry, identityRegistry);
110
+ bytes32[] memory credentialRequirementIds = deployedPolicy.getCredentialRequirementIds();
111
+ assertEq(credentialRequirementIds.length, 1);
112
+ assertEq(credentialRequirementIds[0], REQUIREMENT_KYC);
113
+ }
114
+ }