@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,245 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {ICredentialRegistry} from "./interfaces/ICredentialRegistry.sol";
5
+ import {ICredentialValidator} from "./interfaces/ICredentialValidator.sol";
6
+ import {PolicyProtected} from "@chainlink/policy-management/core/PolicyProtected.sol";
7
+
8
+ contract CredentialRegistry is PolicyProtected, ICredentialRegistry {
9
+ /// @custom:storage-location erc7201:cross-chain-identity.CredentialRegistry
10
+ struct CredentialRegistryStorage {
11
+ mapping(bytes32 ccid => bytes32[] credentialTypeIds) credentialTypeIdsByCCID;
12
+ mapping(bytes32 ccid => mapping(bytes32 credentialTypeId => Credential credentials)) credentials;
13
+ }
14
+
15
+ // keccak256(abi.encode(uint256(keccak256("cross-chain-identity.CredentialRegistry")) - 1)) &
16
+ // ~bytes32(uint256(0xff))
17
+ // solhint-disable-next-line const-name-snakecase
18
+ bytes32 private constant credentialRegistryStorageLocation =
19
+ 0xda878a21d431ff897bdb535b211ae68088a4b0265066b239bc4db2e51d9a8200;
20
+
21
+ function _credentialRegistryStorage() private pure returns (CredentialRegistryStorage storage $) {
22
+ // solhint-disable-next-line no-inline-assembly
23
+ assembly {
24
+ $.slot := credentialRegistryStorageLocation
25
+ }
26
+ }
27
+
28
+ /**
29
+ * @dev Initializes the credential registry and sets the policy engine.
30
+ * @param policyEngine The address of the policy engine contract.
31
+ * @param initialOwner The address that will own the newly created registry contract.
32
+ */
33
+ function initialize(address policyEngine, address initialOwner) public virtual initializer {
34
+ __CredentialRegistry_init(policyEngine, initialOwner);
35
+ }
36
+
37
+ function __CredentialRegistry_init(address policyEngine, address initialOwner) internal onlyInitializing {
38
+ __CredentialRegistry_init_unchained();
39
+ __PolicyProtected_init(initialOwner, policyEngine);
40
+ }
41
+
42
+ // solhint-disable-next-line no-empty-blocks
43
+ function __CredentialRegistry_init_unchained() internal onlyInitializing {}
44
+
45
+ /// @inheritdoc ICredentialRegistry
46
+ function registerCredential(
47
+ bytes32 ccid,
48
+ bytes32 credentialTypeId,
49
+ uint40 expiresAt,
50
+ bytes calldata credentialData,
51
+ bytes calldata context
52
+ )
53
+ public
54
+ virtual
55
+ override
56
+ runPolicyWithContext(context)
57
+ {
58
+ if (expiresAt > 0 && expiresAt <= block.timestamp) {
59
+ revert InvalidCredentialConfiguration("Invalid expiration time");
60
+ }
61
+ _registerCredential(ccid, credentialTypeId, expiresAt, credentialData);
62
+ }
63
+
64
+ /// @inheritdoc ICredentialRegistry
65
+ function registerCredentials(
66
+ bytes32 ccid,
67
+ bytes32[] calldata credentialTypeIds,
68
+ uint40 expiresAt,
69
+ bytes[] calldata credentialDatas,
70
+ bytes calldata context
71
+ )
72
+ public
73
+ virtual
74
+ override
75
+ runPolicyWithContext(context)
76
+ {
77
+ if (credentialTypeIds.length == 0 || credentialTypeIds.length != credentialDatas.length) {
78
+ revert InvalidCredentialConfiguration("Invalid input length");
79
+ }
80
+ if (expiresAt > 0 && expiresAt <= block.timestamp) {
81
+ revert InvalidCredentialConfiguration("Invalid expiration time");
82
+ }
83
+ for (uint256 i = 0; i < credentialTypeIds.length; i++) {
84
+ _registerCredential(ccid, credentialTypeIds[i], expiresAt, credentialDatas[i]);
85
+ }
86
+ }
87
+
88
+ /// @inheritdoc ICredentialRegistry
89
+ function removeCredential(
90
+ bytes32 ccid,
91
+ bytes32 credentialTypeId,
92
+ bytes calldata context
93
+ )
94
+ public
95
+ virtual
96
+ override
97
+ runPolicyWithContext(context)
98
+ {
99
+ uint256 length = _credentialRegistryStorage().credentialTypeIdsByCCID[ccid].length;
100
+ for (uint256 i = 0; i < length; i++) {
101
+ if (_credentialRegistryStorage().credentialTypeIdsByCCID[ccid][i] == credentialTypeId) {
102
+ _credentialRegistryStorage().credentialTypeIdsByCCID[ccid][i] =
103
+ _credentialRegistryStorage().credentialTypeIdsByCCID[ccid][length - 1];
104
+ _credentialRegistryStorage().credentialTypeIdsByCCID[ccid].pop();
105
+ delete _credentialRegistryStorage().credentials[ccid][credentialTypeId];
106
+
107
+ emit CredentialRemoved(ccid, credentialTypeId);
108
+ return;
109
+ }
110
+ }
111
+ revert CredentialNotFound(ccid, credentialTypeId);
112
+ }
113
+
114
+ /// @inheritdoc ICredentialRegistry
115
+ function renewCredential(
116
+ bytes32 ccid,
117
+ bytes32 credentialTypeId,
118
+ uint40 expiresAt,
119
+ bytes calldata context
120
+ )
121
+ public
122
+ virtual
123
+ override
124
+ runPolicyWithContext(context)
125
+ {
126
+ if (expiresAt > 0 && expiresAt <= block.timestamp) {
127
+ revert InvalidCredentialConfiguration("Invalid expiration time");
128
+ }
129
+ uint256 length = _credentialRegistryStorage().credentialTypeIdsByCCID[ccid].length;
130
+ for (uint256 i = 0; i < length; i++) {
131
+ if (_credentialRegistryStorage().credentialTypeIdsByCCID[ccid][i] == credentialTypeId) {
132
+ uint40 currentExpiresAt = _credentialRegistryStorage().credentials[ccid][credentialTypeId].expiresAt;
133
+
134
+ _credentialRegistryStorage().credentials[ccid][credentialTypeId].expiresAt = expiresAt;
135
+ emit CredentialRenewed(ccid, credentialTypeId, currentExpiresAt, expiresAt);
136
+ return;
137
+ }
138
+ }
139
+ revert CredentialNotFound(ccid, credentialTypeId);
140
+ }
141
+
142
+ function isCredentialExpired(bytes32 ccid, bytes32 credentialTypeId) public view returns (bool) {
143
+ return _credentialRegistryStorage().credentials[ccid][credentialTypeId].expiresAt > 0
144
+ && _credentialRegistryStorage().credentials[ccid][credentialTypeId].expiresAt <= block.timestamp;
145
+ }
146
+
147
+ /// @inheritdoc ICredentialRegistry
148
+ function getCredentialTypes(bytes32 ccid) public view virtual override returns (bytes32[] memory) {
149
+ return _credentialRegistryStorage().credentialTypeIdsByCCID[ccid];
150
+ }
151
+
152
+ /// @inheritdoc ICredentialRegistry
153
+ function getCredential(bytes32 ccid, bytes32 credentialTypeId) public view returns (Credential memory) {
154
+ for (uint256 i = 0; i < _credentialRegistryStorage().credentialTypeIdsByCCID[ccid].length; i++) {
155
+ if (_credentialRegistryStorage().credentialTypeIdsByCCID[ccid][i] == credentialTypeId) {
156
+ return _credentialRegistryStorage().credentials[ccid][credentialTypeId];
157
+ }
158
+ }
159
+ revert CredentialNotFound(ccid, credentialTypeId);
160
+ }
161
+
162
+ /// @inheritdoc ICredentialRegistry
163
+ function getCredentials(
164
+ bytes32 ccid,
165
+ bytes32[] calldata credentialTypeIds
166
+ )
167
+ external
168
+ view
169
+ returns (Credential[] memory)
170
+ {
171
+ uint8 length = uint8(credentialTypeIds.length);
172
+ Credential[] memory credentials = new Credential[](length);
173
+ for (uint256 i = 0; i < length; i++) {
174
+ credentials[i] = getCredential(ccid, credentialTypeIds[i]);
175
+ }
176
+ return credentials;
177
+ }
178
+
179
+ /// @inheritdoc ICredentialValidator
180
+ function validate(
181
+ bytes32 ccid,
182
+ bytes32 credentialTypeId,
183
+ bytes calldata context
184
+ )
185
+ public
186
+ view
187
+ virtual
188
+ override
189
+ returns (bool)
190
+ {
191
+ return _validate(ccid, credentialTypeId, context);
192
+ }
193
+
194
+ /// @inheritdoc ICredentialValidator
195
+ function validateAll(
196
+ bytes32 ccid,
197
+ bytes32[] calldata credentialTypeIds,
198
+ bytes calldata context
199
+ )
200
+ public
201
+ view
202
+ virtual
203
+ override
204
+ returns (bool)
205
+ {
206
+ for (uint256 i = 0; i < credentialTypeIds.length; i++) {
207
+ if (!_validate(ccid, credentialTypeIds[i], context)) {
208
+ return false;
209
+ }
210
+ }
211
+ return true;
212
+ }
213
+
214
+ function _validate(bytes32 ccid, bytes32 credentialTypeId, bytes calldata /*context*/ ) internal view returns (bool) {
215
+ uint256 length = _credentialRegistryStorage().credentialTypeIdsByCCID[ccid].length;
216
+ for (uint256 i = 0; i < length; i++) {
217
+ if (_credentialRegistryStorage().credentialTypeIdsByCCID[ccid][i] == credentialTypeId) {
218
+ return (
219
+ _credentialRegistryStorage().credentials[ccid][credentialTypeId].expiresAt == 0
220
+ || _credentialRegistryStorage().credentials[ccid][credentialTypeId].expiresAt > block.timestamp
221
+ );
222
+ }
223
+ }
224
+ return false;
225
+ }
226
+
227
+ function _registerCredential(
228
+ bytes32 ccid,
229
+ bytes32 credentialTypeId,
230
+ uint40 expiresAt,
231
+ bytes calldata credentialData
232
+ )
233
+ internal
234
+ {
235
+ uint256 length = _credentialRegistryStorage().credentialTypeIdsByCCID[ccid].length;
236
+ for (uint256 i = 0; i < length; i++) {
237
+ if (_credentialRegistryStorage().credentialTypeIdsByCCID[ccid][i] == credentialTypeId) {
238
+ revert CredentialAlreadyRegistered(ccid, credentialTypeId);
239
+ }
240
+ }
241
+ _credentialRegistryStorage().credentialTypeIdsByCCID[ccid].push(credentialTypeId);
242
+ _credentialRegistryStorage().credentials[ccid][credentialTypeId] = Credential(expiresAt, credentialData);
243
+ emit CredentialRegistered(ccid, credentialTypeId, expiresAt, credentialData);
244
+ }
245
+ }
@@ -0,0 +1,339 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {ICredentialRequirements} from "./interfaces/ICredentialRequirements.sol";
5
+ import {IIdentityValidator} from "./interfaces/IIdentityValidator.sol";
6
+ import {ICredentialDataValidator} from "./interfaces/ICredentialDataValidator.sol";
7
+ import {IIdentityRegistry} from "./interfaces/IIdentityRegistry.sol";
8
+ import {ICredentialRegistry} from "./interfaces/ICredentialRegistry.sol";
9
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
10
+
11
+ contract CredentialRegistryIdentityValidator is OwnableUpgradeable, ICredentialRequirements, IIdentityValidator {
12
+ uint256 private constant MAX_REQUIREMENTS = 8;
13
+ uint256 private constant MAX_REQUIREMENT_SOURCES = 8;
14
+
15
+ /// @custom:storage-location erc7201:cross-chain-identity.CredentialRegistryIdentityValidator
16
+ struct CredentialRegistryIdentityValidatorStorage {
17
+ bytes32[] requirements;
18
+ mapping(bytes32 requirementId => CredentialRequirement credentialRequirement) credentialRequirementMap;
19
+ mapping(bytes32 credential => CredentialSource[] sources) credentialSources;
20
+ }
21
+
22
+ // keccak256(abi.encode(uint256(keccak256("cross-chain-identity.CredentialRegistryIdentityValidator")) - 1)) &
23
+ // ~bytes32(uint256(0xff))
24
+ // solhint-disable-next-line const-name-snakecase
25
+ bytes32 private constant credentialRegistryIdentityValidatorStorageLocation =
26
+ 0xc27301a28eb510a5458d7558b8bccbf4cdde3a4546d3bf041997133950e7d200;
27
+
28
+ function _credentialRegistryIdentityValidatorStorage()
29
+ private
30
+ pure
31
+ returns (CredentialRegistryIdentityValidatorStorage storage $)
32
+ {
33
+ // solhint-disable-next-line no-inline-assembly
34
+ assembly {
35
+ $.slot := credentialRegistryIdentityValidatorStorageLocation
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @dev Initializes the credential validator and sets the initial credential sources and requirements.
41
+ * @param credentialSourceInputs The credential sources to add.
42
+ * @param credentialRequirementInputs The credential requirements to add.
43
+ */
44
+ function initialize(
45
+ CredentialSourceInput[] memory credentialSourceInputs,
46
+ CredentialRequirementInput[] memory credentialRequirementInputs
47
+ )
48
+ public
49
+ virtual
50
+ initializer
51
+ {
52
+ __CredentialRegistryIdentitityValidator_init(credentialSourceInputs, credentialRequirementInputs);
53
+ }
54
+
55
+ function __CredentialRegistryIdentitityValidator_init(
56
+ CredentialSourceInput[] memory credentialSourceInputs,
57
+ CredentialRequirementInput[] memory credentialRequirementInputs
58
+ )
59
+ internal
60
+ onlyInitializing
61
+ {
62
+ __Ownable_init(msg.sender);
63
+ __CredentialRegistryIdentitityValidator_init_unchained(credentialSourceInputs, credentialRequirementInputs);
64
+ }
65
+
66
+ function __CredentialRegistryIdentitityValidator_init_unchained(
67
+ CredentialSourceInput[] memory credentialSourceInputs,
68
+ CredentialRequirementInput[] memory credentialRequirementInputs
69
+ )
70
+ internal
71
+ onlyInitializing
72
+ {
73
+ uint256 length = credentialSourceInputs.length;
74
+ for (uint256 i = 0; i < length; i++) {
75
+ _addCredentialSource(credentialSourceInputs[i]);
76
+ }
77
+ length = credentialRequirementInputs.length;
78
+ for (uint256 i = 0; i < length; i++) {
79
+ _addCredentialRequirement(credentialRequirementInputs[i]);
80
+ }
81
+ }
82
+
83
+ function _addCredentialRequirement(CredentialRequirementInput memory input) internal {
84
+ uint256 minValidations = input.minValidations;
85
+ if (minValidations == 0) {
86
+ revert InvalidRequirementConfiguration("minValidations must be greater than 0");
87
+ }
88
+ uint256 length = _credentialRegistryIdentityValidatorStorage().requirements.length;
89
+ if (length >= MAX_REQUIREMENTS) {
90
+ revert InvalidRequirementConfiguration("Max requirements reached");
91
+ }
92
+ bytes32 requirementId = input.requirementId;
93
+ for (uint256 i = 0; i < length; i++) {
94
+ if (_credentialRegistryIdentityValidatorStorage().requirements[i] == requirementId) {
95
+ revert RequirementExists(requirementId);
96
+ }
97
+ }
98
+ bytes32[] memory credentialTypeIds = input.credentialTypeIds;
99
+ _credentialRegistryIdentityValidatorStorage().requirements.push(requirementId);
100
+ _credentialRegistryIdentityValidatorStorage().credentialRequirementMap[requirementId] =
101
+ CredentialRequirement(credentialTypeIds, minValidations, input.invert);
102
+ emit CredentialRequirementAdded(requirementId, credentialTypeIds, minValidations, input.invert);
103
+ }
104
+
105
+ /// @inheritdoc ICredentialRequirements
106
+ function addCredentialRequirement(CredentialRequirementInput memory input) public virtual override onlyOwner {
107
+ _addCredentialRequirement(input);
108
+ }
109
+
110
+ /// @inheritdoc ICredentialRequirements
111
+ function removeCredentialRequirement(bytes32 requirementId) public virtual override onlyOwner {
112
+ uint256 length = _credentialRegistryIdentityValidatorStorage().requirements.length;
113
+ for (uint256 i = 0; i < length; i++) {
114
+ if (_credentialRegistryIdentityValidatorStorage().requirements[i] == requirementId) {
115
+ _credentialRegistryIdentityValidatorStorage().requirements[i] =
116
+ _credentialRegistryIdentityValidatorStorage().requirements[length - 1];
117
+ _credentialRegistryIdentityValidatorStorage().requirements.pop();
118
+
119
+ CredentialRequirement memory requirement =
120
+ _credentialRegistryIdentityValidatorStorage().credentialRequirementMap[requirementId];
121
+
122
+ emit CredentialRequirementRemoved(
123
+ requirementId, requirement.credentialTypeIds, requirement.minValidations, requirement.invert
124
+ );
125
+ delete _credentialRegistryIdentityValidatorStorage().credentialRequirementMap[requirementId];
126
+ return;
127
+ }
128
+ }
129
+ revert RequirementNotFound(requirementId);
130
+ }
131
+
132
+ /// @inheritdoc ICredentialRequirements
133
+ function getCredentialRequirement(bytes32 requirementId)
134
+ public
135
+ view
136
+ virtual
137
+ override
138
+ returns (CredentialRequirement memory)
139
+ {
140
+ return _credentialRegistryIdentityValidatorStorage().credentialRequirementMap[requirementId];
141
+ }
142
+
143
+ /// @inheritdoc ICredentialRequirements
144
+ function getCredentialRequirementIds() public view virtual override returns (bytes32[] memory) {
145
+ return _credentialRegistryIdentityValidatorStorage().requirements;
146
+ }
147
+
148
+ function _addCredentialSource(CredentialSourceInput memory input) internal {
149
+ address identityRegistry = input.identityRegistry;
150
+ address credentialRegistry = input.credentialRegistry;
151
+ bytes32 credentialTypeId = input.credentialTypeId;
152
+ bytes32 sourceId = keccak256(abi.encodePacked(identityRegistry, credentialRegistry));
153
+ uint256 length = _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId].length;
154
+ if (length >= MAX_REQUIREMENT_SOURCES) {
155
+ revert InvalidRequirementConfiguration("Max credential sources reached for credential type");
156
+ }
157
+ for (uint256 i = 0; i < length; i++) {
158
+ // Load the entire source struct into memory once
159
+ CredentialSource memory existingSource =
160
+ _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId][i];
161
+
162
+ bytes32 foundSourceId =
163
+ keccak256(abi.encodePacked(existingSource.identityRegistry, existingSource.credentialRegistry));
164
+ if (foundSourceId == sourceId) {
165
+ revert SourceExists(credentialTypeId, identityRegistry, credentialRegistry);
166
+ }
167
+ }
168
+ address dataValidator = input.dataValidator;
169
+ _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId].push(
170
+ CredentialSource(identityRegistry, credentialRegistry, dataValidator)
171
+ );
172
+ emit CredentialSourceAdded(credentialTypeId, identityRegistry, credentialRegistry, dataValidator);
173
+ }
174
+
175
+ /// @inheritdoc ICredentialRequirements
176
+ function addCredentialSource(CredentialSourceInput memory input) public virtual override onlyOwner {
177
+ _addCredentialSource(input);
178
+ }
179
+
180
+ /// @inheritdoc ICredentialRequirements
181
+ function removeCredentialSource(
182
+ bytes32 credentialTypeId,
183
+ address identityRegistry,
184
+ address credentialRegistry
185
+ )
186
+ public
187
+ virtual
188
+ override
189
+ onlyOwner
190
+ {
191
+ bytes32 sourceId = keccak256(abi.encodePacked(identityRegistry, credentialRegistry));
192
+ uint256 length = _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId].length;
193
+ for (uint256 i = 0; i < length; i++) {
194
+ // Load the entire source struct into memory once
195
+ CredentialSource memory existingSource =
196
+ _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId][i];
197
+
198
+ bytes32 foundSourceId =
199
+ keccak256(abi.encodePacked(existingSource.identityRegistry, existingSource.credentialRegistry));
200
+ if (foundSourceId == sourceId) {
201
+ _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId][i] =
202
+ _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId][length - 1];
203
+ _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId].pop();
204
+ emit CredentialSourceRemoved(
205
+ credentialTypeId, identityRegistry, credentialRegistry, existingSource.dataValidator
206
+ );
207
+ return;
208
+ }
209
+ }
210
+ revert CredentialSourceNotFound(credentialTypeId, identityRegistry, credentialRegistry);
211
+ }
212
+
213
+ /// @inheritdoc ICredentialRequirements
214
+ function getCredentialSources(bytes32 credential) public view virtual override returns (CredentialSource[] memory) {
215
+ return _credentialRegistryIdentityValidatorStorage().credentialSources[credential];
216
+ }
217
+
218
+ /// @inheritdoc IIdentityValidator
219
+ function validate(address account, bytes calldata context) public view virtual override returns (bool) {
220
+ uint256 length = _credentialRegistryIdentityValidatorStorage().requirements.length;
221
+ for (uint256 i = 0; i < length; i++) {
222
+ if (!_validateRequirement(account, _credentialRegistryIdentityValidatorStorage().requirements[i], context)) {
223
+ return false;
224
+ }
225
+ }
226
+ return true;
227
+ }
228
+
229
+ function _validateRequirement(
230
+ address account,
231
+ bytes32 requirementId,
232
+ bytes calldata context
233
+ )
234
+ internal
235
+ view
236
+ virtual
237
+ returns (bool)
238
+ {
239
+ CredentialRequirement memory requirement =
240
+ _credentialRegistryIdentityValidatorStorage().credentialRequirementMap[requirementId];
241
+ uint256 validations = 0;
242
+ for (uint256 i = 0; i < requirement.credentialTypeIds.length; i++) {
243
+ validations = _validateCredential(
244
+ account, requirement.credentialTypeIds[i], validations, requirement.minValidations, requirement.invert, context
245
+ );
246
+ if (validations >= requirement.minValidations) {
247
+ return true;
248
+ }
249
+ }
250
+ return false;
251
+ }
252
+
253
+ function _validateCredential(
254
+ address account,
255
+ bytes32 credentialTypeId,
256
+ uint256 currentValidations,
257
+ uint256 minValidations,
258
+ bool invert,
259
+ bytes calldata context
260
+ )
261
+ internal
262
+ view
263
+ virtual
264
+ returns (uint256)
265
+ {
266
+ uint256 validations = currentValidations;
267
+ uint256 length = _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId].length;
268
+ for (uint256 i = 0; i < length; i++) {
269
+ CredentialSource memory source =
270
+ _credentialRegistryIdentityValidatorStorage().credentialSources[credentialTypeId][i];
271
+ bytes32 ccid = IIdentityRegistry(source.identityRegistry).getIdentity(account);
272
+ if (ccid == 0) {
273
+ continue; // identity not found in this registry
274
+ }
275
+
276
+ if (
277
+ _validateCredentialWithRegistry(
278
+ ccid, account, source.credentialRegistry, source.dataValidator, credentialTypeId, invert, context
279
+ )
280
+ ) {
281
+ validations++;
282
+ }
283
+ if (validations >= minValidations) {
284
+ return validations;
285
+ }
286
+ }
287
+ return validations;
288
+ }
289
+
290
+ function _validateCredentialWithRegistry(
291
+ bytes32 ccid,
292
+ address account,
293
+ address credentialRegistry,
294
+ address dataValidator,
295
+ bytes32 credential,
296
+ bool invert,
297
+ bytes calldata context
298
+ )
299
+ internal
300
+ view
301
+ virtual
302
+ returns (bool)
303
+ {
304
+ // Check if credential exists and is valid in registry
305
+ bool credentialExists;
306
+ try ICredentialRegistry(credentialRegistry).validate(ccid, credential, context) returns (bool valid) {
307
+ credentialExists = valid;
308
+ } catch {
309
+ // If validation throws, we return false because the credential is not valid despite the value of invert
310
+ return false;
311
+ }
312
+
313
+ // For inverted credentials: return true only if credential doesn't exist
314
+ if (invert) {
315
+ return !credentialExists;
316
+ }
317
+
318
+ // For normal credentials: credential must exist to proceed
319
+ if (!credentialExists) {
320
+ return false;
321
+ }
322
+
323
+ // No data validator means credential is valid
324
+ if (dataValidator == address(0)) {
325
+ return true;
326
+ }
327
+
328
+ // Validate credential data
329
+ bytes memory credentialData = ICredentialRegistry(credentialRegistry).getCredential(ccid, credential).credentialData;
330
+
331
+ try ICredentialDataValidator(dataValidator).validateCredentialData(
332
+ ccid, account, credential, credentialData, context
333
+ ) returns (bool valid) {
334
+ return valid;
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+ }
@@ -0,0 +1,71 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {CredentialRegistryIdentityValidator} from "./CredentialRegistryIdentityValidator.sol";
5
+ import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
6
+ import {Policy} from "@chainlink/policy-management/core/Policy.sol";
7
+ import {ICredentialRequirements} from "@chainlink/cross-chain-identity/interfaces/ICredentialRequirements.sol";
8
+
9
+ contract CredentialRegistryIdentityValidatorPolicy is Policy, CredentialRegistryIdentityValidator {
10
+ /**
11
+ * @notice Configures the policy by setting up credential sources and credential requirements.
12
+ * @dev The `parameters` input must be the ABI encoding of two dynamic arrays:
13
+ * - An array of `CredentialSourceInput` structs (credential sources).
14
+ * - An array of `CredentialRequirementInput` structs (credential requirements).
15
+ *
16
+ * The function expects the parameters to be tightly packed together, meaning that the entire calldata
17
+ * should decode as `(CredentialSourceInput[], CredentialRequirementInput[])`.
18
+ *
19
+ * @param parameters ABI-encoded bytes containing two arrays: one of `CredentialSourceInput` and one of
20
+ * `CredentialRequirementInput`.
21
+ */
22
+ function configure(bytes calldata parameters) internal override onlyInitializing {
23
+ if (parameters.length == 0) {
24
+ __CredentialRegistryIdentitityValidator_init_unchained(
25
+ new ICredentialRequirements.CredentialSourceInput[](0),
26
+ new ICredentialRequirements.CredentialRequirementInput[](0)
27
+ );
28
+ return;
29
+ }
30
+
31
+ (
32
+ ICredentialRequirements.CredentialSourceInput[] memory credentialSourceInputs,
33
+ ICredentialRequirements.CredentialRequirementInput[] memory credentialRequirementInputs
34
+ ) = abi.decode(
35
+ parameters,
36
+ (ICredentialRequirements.CredentialSourceInput[], ICredentialRequirements.CredentialRequirementInput[])
37
+ );
38
+ // We call the init_unchained_() method to avoid calling _Ownable__init_() twice (Policy has called it before
39
+ // invoking configure), likely changing the owner (Policy uses the initialOwner param and IdentityValidator the
40
+ // msg.sender global variable).
41
+ __CredentialRegistryIdentitityValidator_init_unchained(credentialSourceInputs, credentialRequirementInputs);
42
+ }
43
+
44
+ function run(
45
+ address, /*caller*/
46
+ address, /*subject*/
47
+ bytes4, /*selector*/
48
+ bytes[] calldata parameters,
49
+ bytes calldata context
50
+ )
51
+ public
52
+ view
53
+ override
54
+ returns (IPolicyEngine.PolicyResult)
55
+ {
56
+ // expected parameters: [account(address)]
57
+ if (parameters.length != 1) {
58
+ revert IPolicyEngine.InvalidConfiguration("expected 1 parameter");
59
+ }
60
+ address account = abi.decode(parameters[0], (address));
61
+
62
+ if (!validate(account, context)) {
63
+ revert IPolicyEngine.PolicyRejected("account identity validation failed");
64
+ }
65
+ return IPolicyEngine.PolicyResult.Continue;
66
+ }
67
+
68
+ function supportsInterface(bytes4 interfaceId) public view override(Policy) returns (bool) {
69
+ return super.supportsInterface(interfaceId);
70
+ }
71
+ }