@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,556 @@
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 {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import {ComplianceTokenERC20} from "../src/ComplianceTokenERC20.sol";
7
+ import {PolicyEngine} from "@chainlink/policy-management/core/PolicyEngine.sol";
8
+ import {IPolicy} from "@chainlink/policy-management/interfaces/IPolicy.sol";
9
+ import {OnlyOwnerPolicy} from "@chainlink/policy-management/policies/OnlyOwnerPolicy.sol";
10
+ import {OnlyAuthorizedSenderPolicy} from "@chainlink/policy-management/policies/OnlyAuthorizedSenderPolicy.sol";
11
+ import {VolumePolicy} from "@chainlink/policy-management/policies/VolumePolicy.sol";
12
+ import {BaseProxyTest} from "./helpers/BaseProxyTest.sol";
13
+ import {ComplianceTokenMintBurnExtractor} from
14
+ "@chainlink/policy-management/extractors/ComplianceTokenMintBurnExtractor.sol";
15
+ import {ComplianceTokenFreezeUnfreezeExtractor} from
16
+ "@chainlink/policy-management/extractors/ComplianceTokenFreezeUnfreezeExtractor.sol";
17
+ import {ComplianceTokenForceTransferExtractor} from
18
+ "@chainlink/policy-management/extractors/ComplianceTokenForceTransferExtractor.sol";
19
+ import {ERC20TransferExtractor} from "@chainlink/policy-management/extractors/ERC20TransferExtractor.sol";
20
+
21
+ contract ComplianceTokenERC20Test is BaseProxyTest {
22
+ PolicyEngine internal s_policyEngine;
23
+ ComplianceTokenERC20 internal s_token;
24
+ address internal s_owner;
25
+ address internal s_bridge;
26
+ address internal s_enforcer;
27
+ OnlyOwnerPolicy internal onlyOwnerPolicy;
28
+ OnlyAuthorizedSenderPolicy internal minterBurnerList;
29
+ OnlyAuthorizedSenderPolicy internal freezingList;
30
+ VolumePolicy internal volumePolicy;
31
+
32
+ function setUp() public {
33
+ s_owner = makeAddr("owner");
34
+ s_bridge = makeAddr("bridge");
35
+ s_enforcer = makeAddr("enforcer");
36
+
37
+ vm.startPrank(s_owner);
38
+
39
+ s_policyEngine = _deployPolicyEngine(true, s_owner);
40
+
41
+ ERC20TransferExtractor transferExtractor = new ERC20TransferExtractor();
42
+ s_policyEngine.setExtractor(IERC20.transfer.selector, address(transferExtractor));
43
+ s_policyEngine.setExtractor(IERC20.transferFrom.selector, address(transferExtractor));
44
+ ComplianceTokenMintBurnExtractor mintBurnExtractor = new ComplianceTokenMintBurnExtractor();
45
+ s_policyEngine.setExtractor(ComplianceTokenERC20.mint.selector, address(mintBurnExtractor));
46
+ s_policyEngine.setExtractor(ComplianceTokenERC20.burn.selector, address(mintBurnExtractor));
47
+ s_policyEngine.setExtractor(ComplianceTokenERC20.burnFrom.selector, address(mintBurnExtractor));
48
+ ComplianceTokenFreezeUnfreezeExtractor freezeUnfreezeExtractor = new ComplianceTokenFreezeUnfreezeExtractor();
49
+ s_policyEngine.setExtractor(ComplianceTokenERC20.freeze.selector, address(freezeUnfreezeExtractor));
50
+ s_policyEngine.setExtractor(ComplianceTokenERC20.unfreeze.selector, address(freezeUnfreezeExtractor));
51
+ s_policyEngine.setExtractor(
52
+ ComplianceTokenERC20.forceTransfer.selector, address(new ComplianceTokenForceTransferExtractor())
53
+ );
54
+
55
+ // to protect admin methods
56
+ OnlyOwnerPolicy onlyOwnerPolicyImpl = new OnlyOwnerPolicy();
57
+ onlyOwnerPolicy =
58
+ OnlyOwnerPolicy(_deployPolicy(address(onlyOwnerPolicyImpl), address(s_policyEngine), s_owner, new bytes(0)));
59
+ // to protect mint/burn with admin list
60
+ OnlyAuthorizedSenderPolicy minterBurnerListImpl = new OnlyAuthorizedSenderPolicy();
61
+ minterBurnerList = OnlyAuthorizedSenderPolicy(
62
+ _deployPolicy(address(minterBurnerListImpl), address(s_policyEngine), s_owner, new bytes(0))
63
+ );
64
+ minterBurnerList.authorizeSender(s_owner);
65
+ minterBurnerList.authorizeSender(s_bridge);
66
+ // to protect freezing features with admin list
67
+ OnlyAuthorizedSenderPolicy freezingListImpl = new OnlyAuthorizedSenderPolicy();
68
+ freezingList = OnlyAuthorizedSenderPolicy(
69
+ _deployPolicy(address(freezingListImpl), address(s_policyEngine), s_owner, new bytes(0))
70
+ );
71
+ freezingList.authorizeSender(s_owner);
72
+ freezingList.authorizeSender(s_enforcer);
73
+ // to enforce transaction limits
74
+ VolumePolicy volumePolicyImpl = new VolumePolicy();
75
+ volumePolicy =
76
+ VolumePolicy(_deployPolicy(address(volumePolicyImpl), address(s_policyEngine), s_owner, abi.encode(100, 200)));
77
+
78
+ s_token = _deployComplianceTokenERC20("Test Token", "TST", 18, address(s_policyEngine));
79
+
80
+ bytes32[] memory volumeParams = new bytes32[](1);
81
+ volumeParams[0] = mintBurnExtractor.PARAM_AMOUNT();
82
+
83
+ // admin methods - onlyOwner
84
+ s_policyEngine.addPolicy(
85
+ address(s_token), ComplianceTokenERC20.forceTransfer.selector, address(onlyOwnerPolicy), new bytes32[](0)
86
+ );
87
+ // mint - onlyAuthorized - volume
88
+ s_policyEngine.addPolicy(
89
+ address(s_token), ComplianceTokenERC20.mint.selector, address(minterBurnerList), new bytes32[](0)
90
+ );
91
+ s_policyEngine.addPolicy(address(s_token), ComplianceTokenERC20.mint.selector, address(volumePolicy), volumeParams);
92
+ // burn/burnFrom - onlyAuthorized
93
+ s_policyEngine.addPolicy(
94
+ address(s_token), ComplianceTokenERC20.burn.selector, address(minterBurnerList), new bytes32[](0)
95
+ );
96
+ s_policyEngine.addPolicy(
97
+ address(s_token), ComplianceTokenERC20.burnFrom.selector, address(minterBurnerList), new bytes32[](0)
98
+ );
99
+ // freezing methods - onlyAuthorized
100
+ s_policyEngine.addPolicy(
101
+ address(s_token), ComplianceTokenERC20.freeze.selector, address(freezingList), new bytes32[](0)
102
+ );
103
+ s_policyEngine.addPolicy(
104
+ address(s_token), ComplianceTokenERC20.unfreeze.selector, address(freezingList), new bytes32[](0)
105
+ );
106
+ // transfer methods - volume
107
+ s_policyEngine.addPolicy(address(s_token), IERC20.transfer.selector, address(volumePolicy), volumeParams);
108
+ s_policyEngine.addPolicy(address(s_token), IERC20.transferFrom.selector, address(volumePolicy), volumeParams);
109
+ }
110
+
111
+ function test_mint_success() public {
112
+ address alice = makeAddr("alice");
113
+
114
+ s_token.mint(alice, 110);
115
+
116
+ assertEq(s_token.balanceOf(alice), 110);
117
+ assertEq(s_token.totalSupply(), 110);
118
+ }
119
+
120
+ function test_mint_bridge_success() public {
121
+ address alice = makeAddr("alice");
122
+
123
+ vm.stopPrank();
124
+ vm.startPrank(s_bridge);
125
+ s_token.mint(alice, 120);
126
+
127
+ assertEq(s_token.balanceOf(alice), 120);
128
+ assertEq(s_token.totalSupply(), 120);
129
+ }
130
+
131
+ function test_mint_over_failure() public {
132
+ address alice = makeAddr("alice");
133
+
134
+ vm.expectRevert(
135
+ abi.encodeWithSelector(
136
+ IPolicyEngine.PolicyRunRejected.selector,
137
+ ComplianceTokenERC20.mint.selector,
138
+ address(volumePolicy),
139
+ "amount outside allowed volume limits"
140
+ )
141
+ );
142
+ s_token.mint(alice, 220);
143
+ }
144
+
145
+ function test_mint_under_failure() public {
146
+ address alice = makeAddr("alice");
147
+
148
+ vm.expectRevert(
149
+ abi.encodeWithSelector(
150
+ IPolicyEngine.PolicyRunRejected.selector,
151
+ ComplianceTokenERC20.mint.selector,
152
+ address(volumePolicy),
153
+ "amount outside allowed volume limits"
154
+ )
155
+ );
156
+ s_token.mint(alice, 50);
157
+ }
158
+
159
+ function test_mint_notAuthorized_revert() public {
160
+ address alice = makeAddr("alice");
161
+
162
+ vm.stopPrank();
163
+ vm.startPrank(alice);
164
+
165
+ vm.expectRevert(
166
+ abi.encodeWithSelector(
167
+ IPolicyEngine.PolicyRunRejected.selector,
168
+ ComplianceTokenERC20.mint.selector,
169
+ address(minterBurnerList),
170
+ "sender is not authorized"
171
+ )
172
+ );
173
+ s_token.mint(alice, 10);
174
+ }
175
+
176
+ function test_burn_success() public {
177
+ s_token.mint(s_bridge, 110);
178
+
179
+ vm.stopPrank();
180
+ vm.startPrank(s_bridge);
181
+
182
+ s_token.burn(5);
183
+
184
+ assertEq(s_token.balanceOf(s_bridge), 105);
185
+ assertEq(s_token.totalSupply(), 105);
186
+ }
187
+
188
+ function test_burn_overBalance_revert() public {
189
+ s_token.mint(s_bridge, 110);
190
+
191
+ vm.stopPrank();
192
+ vm.startPrank(s_bridge);
193
+
194
+ vm.expectRevert("amount exceeds available balance");
195
+ s_token.burn(111);
196
+ }
197
+
198
+ function test_burn_notAuthorized_failure() public {
199
+ address alice = makeAddr("alice");
200
+
201
+ s_token.mint(alice, 110);
202
+
203
+ vm.stopPrank();
204
+ vm.startPrank(alice);
205
+
206
+ vm.expectRevert(
207
+ abi.encodeWithSelector(
208
+ IPolicyEngine.PolicyRunRejected.selector,
209
+ ComplianceTokenERC20.burn.selector,
210
+ address(minterBurnerList),
211
+ "sender is not authorized"
212
+ )
213
+ );
214
+ s_token.burn(111);
215
+ }
216
+
217
+ function test_burn_frozenBalance_revert() public {
218
+ s_token.mint(s_bridge, 110);
219
+
220
+ s_token.freeze(s_bridge, 60, "");
221
+
222
+ vm.stopPrank();
223
+ vm.startPrank(s_bridge);
224
+
225
+ vm.expectRevert("amount exceeds available balance");
226
+ s_token.burn(55);
227
+ }
228
+
229
+ function test_burnFrom_success() public {
230
+ address alice = makeAddr("alice");
231
+
232
+ s_token.mint(alice, 110);
233
+
234
+ s_token.burnFrom(alice, 50);
235
+
236
+ assertEq(s_token.balanceOf(alice), 60);
237
+ assertEq(s_token.totalSupply(), 60);
238
+ }
239
+
240
+ function test_burnFrom_bridge_success() public {
241
+ address alice = makeAddr("alice");
242
+ s_token.mint(alice, 120);
243
+ assertEq(s_token.balanceOf(alice), 120);
244
+
245
+ vm.stopPrank();
246
+ vm.startPrank(s_bridge);
247
+
248
+ s_token.burnFrom(alice, 70);
249
+
250
+ assertEq(s_token.balanceOf(alice), 50);
251
+ assertEq(s_token.totalSupply(), 50);
252
+ }
253
+
254
+ function test_burnFrom_notAuthorized_failure() public {
255
+ address alice = makeAddr("alice");
256
+ s_token.mint(alice, 120);
257
+ assertEq(s_token.balanceOf(alice), 120);
258
+
259
+ vm.stopPrank();
260
+ vm.startPrank(alice);
261
+
262
+ vm.expectRevert(
263
+ abi.encodeWithSelector(
264
+ IPolicyEngine.PolicyRunRejected.selector,
265
+ ComplianceTokenERC20.burnFrom.selector,
266
+ address(minterBurnerList),
267
+ "sender is not authorized"
268
+ )
269
+ );
270
+ s_token.burnFrom(alice, 70);
271
+ }
272
+
273
+ function test_transfer_success() public {
274
+ address alice = makeAddr("alice");
275
+ address bob = makeAddr("bob");
276
+
277
+ s_token.mint(alice, 170);
278
+
279
+ vm.stopPrank();
280
+ vm.startPrank(alice);
281
+
282
+ s_token.transfer(bob, 110);
283
+
284
+ assertEq(s_token.balanceOf(alice), 60);
285
+ assertEq(s_token.balanceOf(bob), 110);
286
+ }
287
+
288
+ function test_transfer_over_failure() public {
289
+ address alice = makeAddr("alice");
290
+ address bob = makeAddr("bob");
291
+
292
+ s_token.mint(alice, 120);
293
+ s_token.mint(alice, 120);
294
+ assertEq(s_token.balanceOf(alice), 240);
295
+
296
+ vm.stopPrank();
297
+ vm.startPrank(alice);
298
+
299
+ vm.expectRevert(
300
+ abi.encodeWithSelector(
301
+ IPolicyEngine.PolicyRunRejected.selector,
302
+ IERC20.transfer.selector,
303
+ address(volumePolicy),
304
+ "amount outside allowed volume limits"
305
+ )
306
+ );
307
+ s_token.transfer(bob, 210);
308
+ }
309
+
310
+ function test_transfer_under_failure() public {
311
+ address alice = makeAddr("alice");
312
+ address bob = makeAddr("bob");
313
+
314
+ s_token.mint(alice, 120);
315
+
316
+ vm.stopPrank();
317
+ vm.startPrank(alice);
318
+
319
+ vm.expectRevert(
320
+ abi.encodeWithSelector(
321
+ IPolicyEngine.PolicyRunRejected.selector,
322
+ IERC20.transfer.selector,
323
+ address(volumePolicy),
324
+ "amount outside allowed volume limits"
325
+ )
326
+ );
327
+ s_token.transfer(bob, 50);
328
+ }
329
+
330
+ function test_transferFrom_success() public {
331
+ address alice = makeAddr("alice");
332
+ address bob = makeAddr("bob");
333
+ address charlie = makeAddr("charlie");
334
+
335
+ s_token.mint(alice, 170);
336
+
337
+ vm.stopPrank();
338
+ vm.startPrank(alice);
339
+
340
+ s_token.approve(charlie, 110);
341
+
342
+ vm.stopPrank();
343
+ vm.startPrank(charlie);
344
+
345
+ s_token.transferFrom(alice, bob, 110);
346
+
347
+ assertEq(s_token.balanceOf(alice), 60);
348
+ assertEq(s_token.balanceOf(bob), 110);
349
+ }
350
+
351
+ function test_transferFrom_insufficientAllowance_revert() public {
352
+ address alice = makeAddr("alice");
353
+ address bob = makeAddr("bob");
354
+ address charlie = makeAddr("charlie");
355
+
356
+ s_token.mint(alice, 170);
357
+
358
+ vm.stopPrank();
359
+ vm.startPrank(alice);
360
+
361
+ s_token.approve(charlie, 110);
362
+
363
+ vm.stopPrank();
364
+ vm.startPrank(charlie);
365
+
366
+ vm.expectRevert("ERC20: transfer amount exceeds allowance");
367
+ s_token.transferFrom(alice, bob, 111);
368
+ }
369
+
370
+ function test_transferFrom_frozenBalance_revert() public {
371
+ address alice = makeAddr("alice");
372
+ address bob = makeAddr("bob");
373
+ address charlie = makeAddr("charlie");
374
+
375
+ s_token.mint(alice, 120);
376
+
377
+ s_token.freeze(alice, 110, "");
378
+
379
+ vm.stopPrank();
380
+ vm.startPrank(alice);
381
+
382
+ s_token.approve(charlie, 101);
383
+
384
+ vm.stopPrank();
385
+ vm.startPrank(charlie);
386
+
387
+ vm.expectRevert("amount exceeds available balance");
388
+ s_token.transferFrom(alice, bob, 101);
389
+ }
390
+
391
+ function test_freeze_transferPartialFrozen_revert() public {
392
+ address alice = makeAddr("alice");
393
+ address bob = makeAddr("bob");
394
+
395
+ s_token.mint(alice, 170);
396
+ s_token.freeze(alice, 110, "");
397
+
398
+ vm.stopPrank();
399
+ vm.startPrank(alice);
400
+
401
+ vm.expectRevert("amount exceeds available balance");
402
+ s_token.transfer(bob, 120);
403
+ }
404
+
405
+ function test_freeze_transferPartialUnfrozen_success() public {
406
+ address alice = makeAddr("alice");
407
+ address bob = makeAddr("bob");
408
+
409
+ s_token.mint(alice, 170);
410
+ s_token.freeze(alice, 50, "");
411
+
412
+ vm.stopPrank();
413
+ vm.startPrank(alice);
414
+
415
+ s_token.transfer(bob, 110);
416
+
417
+ assertEq(s_token.balanceOf(alice), 60);
418
+ assertEq(s_token.balanceOf(bob), 110);
419
+ }
420
+
421
+ function test_freeze_notAuthorized_failure() public {
422
+ address alice = makeAddr("alice");
423
+
424
+ s_token.mint(alice, 120);
425
+
426
+ vm.startPrank(alice);
427
+ vm.expectRevert(
428
+ abi.encodeWithSelector(
429
+ IPolicyEngine.PolicyRunRejected.selector,
430
+ ComplianceTokenERC20.freeze.selector,
431
+ address(freezingList),
432
+ "sender is not authorized"
433
+ )
434
+ );
435
+ s_token.freeze(alice, 100, "");
436
+ }
437
+
438
+ function test_unfreeze_success() public {
439
+ address alice = makeAddr("alice");
440
+ address bob = makeAddr("bob");
441
+
442
+ s_token.mint(alice, 110);
443
+ s_token.freeze(alice, 5, "");
444
+
445
+ vm.stopPrank();
446
+ vm.startPrank(alice);
447
+
448
+ vm.expectRevert("amount exceeds available balance");
449
+ s_token.transfer(bob, 107);
450
+
451
+ vm.stopPrank();
452
+ vm.startPrank(s_owner);
453
+
454
+ s_token.unfreeze(alice, 3, "");
455
+
456
+ vm.stopPrank();
457
+ vm.startPrank(alice);
458
+
459
+ s_token.transfer(bob, 107);
460
+
461
+ assertEq(s_token.balanceOf(alice), 3);
462
+ assertEq(s_token.balanceOf(bob), 107);
463
+ }
464
+
465
+ function test_frozenUnfrozen_enforcer_success() public {
466
+ address alice = makeAddr("alice");
467
+ address bob = makeAddr("bob");
468
+
469
+ s_token.mint(alice, 120);
470
+
471
+ vm.startPrank(s_enforcer);
472
+ s_token.freeze(alice, 100, "");
473
+
474
+ vm.stopPrank();
475
+ vm.startPrank(alice);
476
+
477
+ vm.expectRevert("amount exceeds available balance");
478
+ s_token.transfer(bob, 110);
479
+
480
+ vm.stopPrank();
481
+ vm.startPrank(s_enforcer);
482
+
483
+ s_token.unfreeze(alice, 100, "");
484
+
485
+ vm.stopPrank();
486
+ vm.startPrank(alice);
487
+
488
+ s_token.transfer(bob, 110);
489
+ assertEq(s_token.balanceOf(alice), 10);
490
+ assertEq(s_token.balanceOf(bob), 110);
491
+ }
492
+
493
+ function test_unfreeze_notAuthorized_failure() public {
494
+ address alice = makeAddr("alice");
495
+
496
+ s_token.mint(alice, 120);
497
+ s_token.freeze(alice, 60, "");
498
+
499
+ vm.startPrank(alice);
500
+ vm.expectRevert(
501
+ abi.encodeWithSelector(
502
+ IPolicyEngine.PolicyRunRejected.selector,
503
+ ComplianceTokenERC20.unfreeze.selector,
504
+ address(freezingList),
505
+ "sender is not authorized"
506
+ )
507
+ );
508
+ s_token.unfreeze(alice, 60, "");
509
+ }
510
+
511
+ function test_forceTransfer_success() public {
512
+ address alice = makeAddr("alice");
513
+ address bob = makeAddr("bob");
514
+
515
+ s_token.mint(alice, 170);
516
+
517
+ s_token.forceTransfer(alice, bob, 60, "");
518
+
519
+ assertEq(s_token.balanceOf(alice), 110);
520
+ assertEq(s_token.balanceOf(bob), 60);
521
+ }
522
+
523
+ function test_forceTransfer_frozenBalance_success() public {
524
+ address alice = makeAddr("alice");
525
+ address bob = makeAddr("bob");
526
+
527
+ s_token.mint(alice, 170);
528
+
529
+ s_token.freeze(alice, 60, "");
530
+
531
+ s_token.forceTransfer(alice, bob, 140, "");
532
+
533
+ assertEq(s_token.balanceOf(alice), 30);
534
+ assertEq(s_token.balanceOf(bob), 140);
535
+ }
536
+
537
+ function test_forceTransfer_notOwner_revert() public {
538
+ address alice = makeAddr("alice");
539
+ address bob = makeAddr("bob");
540
+
541
+ s_token.mint(alice, 110);
542
+
543
+ vm.stopPrank();
544
+ vm.startPrank(bob);
545
+
546
+ vm.expectRevert(
547
+ abi.encodeWithSelector(
548
+ IPolicyEngine.PolicyRunRejected.selector,
549
+ ComplianceTokenERC20.forceTransfer.selector,
550
+ address(onlyOwnerPolicy),
551
+ "caller is not the policy owner"
552
+ )
553
+ );
554
+ s_token.forceTransfer(alice, bob, 60, "");
555
+ }
556
+ }
@@ -0,0 +1,75 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
6
+ import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
7
+ import {PolicyEngine} from "@chainlink/policy-management/core/PolicyEngine.sol";
8
+ import {Policy} from "@chainlink/policy-management/core/Policy.sol";
9
+ import {ComplianceTokenERC20} from "../../src/ComplianceTokenERC20.sol";
10
+
11
+ /**
12
+ * @title BaseProxyTest
13
+ * @notice Base contract for ERC-20 token tests that need to deploy upgradeable contracts through proxies
14
+ * @dev Provides helper functions to deploy common ERC-20 token contracts with proper proxy pattern
15
+ */
16
+ abstract contract BaseProxyTest is Test {
17
+ /**
18
+ * @notice Deploy PolicyEngine through proxy
19
+ * @param defaultAllow Whether the default policy engine rule will allow or reject the transaction
20
+ * @return The deployed PolicyEngine proxy instance
21
+ */
22
+ function _deployPolicyEngine(bool defaultAllow, address initialOwner) internal returns (PolicyEngine) {
23
+ PolicyEngine policyEngineImpl = new PolicyEngine();
24
+ bytes memory policyEngineData = abi.encodeWithSelector(PolicyEngine.initialize.selector, defaultAllow, initialOwner);
25
+ ERC1967Proxy policyEngineProxy = new ERC1967Proxy(address(policyEngineImpl), policyEngineData);
26
+ return PolicyEngine(address(policyEngineProxy));
27
+ }
28
+
29
+ /**
30
+ * @notice Deploy any Policy-based contract through proxy
31
+ * @param policyImpl The implementation contract (must inherit from Policy)
32
+ * @param policyEngine The address of the policy engine contract
33
+ * @param owner The address of the policy owner
34
+ * @param parameters ABI-encoded parameters for policy initialization
35
+ * @return The deployed policy proxy address
36
+ */
37
+ function _deployPolicy(
38
+ address policyImpl,
39
+ address policyEngine,
40
+ address owner,
41
+ bytes memory parameters
42
+ )
43
+ internal
44
+ returns (address)
45
+ {
46
+ bytes memory policyData = abi.encodeWithSelector(Policy.initialize.selector, policyEngine, owner, parameters);
47
+ ERC1967Proxy policyProxy = new ERC1967Proxy(policyImpl, policyData);
48
+ return address(policyProxy);
49
+ }
50
+
51
+ /**
52
+ * @notice Deploy ComplianceTokenERC20 through proxy
53
+ * @param tokenName The name of the token
54
+ * @param tokenSymbol The symbol of the token
55
+ * @param tokenDecimals The number of decimals for the token
56
+ * @param policyEngine The address of the policy engine contract
57
+ * @return The deployed ComplianceTokenERC20 proxy instance
58
+ */
59
+ function _deployComplianceTokenERC20(
60
+ string memory tokenName,
61
+ string memory tokenSymbol,
62
+ uint8 tokenDecimals,
63
+ address policyEngine
64
+ )
65
+ internal
66
+ returns (ComplianceTokenERC20)
67
+ {
68
+ ComplianceTokenERC20 tokenImpl = new ComplianceTokenERC20();
69
+ bytes memory tokenData = abi.encodeWithSelector(
70
+ ComplianceTokenERC20.initialize.selector, tokenName, tokenSymbol, tokenDecimals, policyEngine
71
+ );
72
+ ERC1967Proxy tokenProxy = new ERC1967Proxy(address(tokenImpl), tokenData);
73
+ return ComplianceTokenERC20(address(tokenProxy));
74
+ }
75
+ }
@@ -0,0 +1,24 @@
1
+ # Compliance Token
2
+
3
+ The Compliance Token is an implementation of the ERC-3643 token interface as well as the Cross-Chain Identity interface
4
+ and Policy Management interface, as defined in this repository.
5
+
6
+ The following are the differences between this ERC-3643 implementation and the canonical T-Rex implementation:
7
+
8
+ - Utilizes the [Cross-Chain Identity](../../cross-chain-identity/README.md) package defined in this repository as the identity system instead of ONCHAINID.
9
+ - Utilizes the [Policy Management](../../policy-management/README.md) package defined in this repository as the policy management system instead of the T-Rex `ModularCompliance` system.
10
+
11
+ ## Frozen Token Behavior
12
+
13
+ This ERC-3643 implementation follows the standard T-REX approach to frozen tokens during burns and forced transfers:
14
+
15
+ - **Automatic Unfreezing**: When burning or force transferring tokens, if there are insufficient unfrozen tokens, the contract automatically unfreezes the required amount to complete the operation.
16
+ - **Flexible Operations**: This allows administrative operations to proceed even when tokens are frozen, providing operational flexibility.
17
+
18
+ **Comparison with ERC-20 Implementation:**
19
+ Unlike the [ERC-20 compliance token](../erc-20/) in this repository, which preserves frozen status during all operations, this ERC-3643 implementation prioritizes operational flexibility by automatically managing frozen balances as needed.
20
+
21
+ Choose the implementation that best fits your compliance requirements:
22
+
23
+ - **ERC-3643**: For operational flexibility with automatic frozen token management
24
+ - **ERC-20**: For strict frozen token preservation and explicit control