@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,165 @@
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 {PolicyEngine} from "@chainlink/policy-management/core/PolicyEngine.sol";
6
+ import {VolumeRatePolicy} from "@chainlink/policy-management/policies/VolumeRatePolicy.sol";
7
+ import {ERC20TransferExtractor} from "@chainlink/policy-management/extractors/ERC20TransferExtractor.sol";
8
+ import {MockToken} from "../helpers/MockToken.sol";
9
+ import {BaseProxyTest} from "../helpers/BaseProxyTest.sol";
10
+
11
+ contract VolumeRatePolicyTest is BaseProxyTest {
12
+ PolicyEngine public policyEngine;
13
+ VolumeRatePolicy public volumeRatePolicy;
14
+ ERC20TransferExtractor public extractor;
15
+ MockToken token;
16
+ address public deployer;
17
+ address public txSender;
18
+
19
+ function setUp() public {
20
+ deployer = makeAddr("deployer");
21
+ txSender = makeAddr("txSender");
22
+
23
+ vm.startPrank(deployer);
24
+
25
+ policyEngine = _deployPolicyEngine(true, deployer);
26
+
27
+ token = MockToken(_deployMockToken(address(policyEngine)));
28
+
29
+ extractor = new ERC20TransferExtractor();
30
+ bytes32[] memory parameterOutputFormat = new bytes32[](2);
31
+ parameterOutputFormat[0] = extractor.PARAM_AMOUNT();
32
+ parameterOutputFormat[1] = extractor.PARAM_FROM();
33
+
34
+ VolumeRatePolicy volumeRatePolicyImpl = new VolumeRatePolicy();
35
+ volumeRatePolicy = VolumeRatePolicy(
36
+ _deployPolicy(address(volumeRatePolicyImpl), address(policyEngine), deployer, abi.encode(1 days, 200))
37
+ );
38
+
39
+ policyEngine.setExtractor(MockToken.transfer.selector, address(extractor));
40
+
41
+ policyEngine.addPolicy(
42
+ address(token), MockToken.transfer.selector, address(volumeRatePolicy), parameterOutputFormat
43
+ );
44
+ vm.warp(1737583804);
45
+ }
46
+
47
+ function test_setTimePeriodDuration_succeeds() public {
48
+ vm.startPrank(deployer, deployer);
49
+
50
+ vm.expectEmit();
51
+ emit VolumeRatePolicy.TimePeriodDurationSet(2 days);
52
+ volumeRatePolicy.setTimePeriodDuration(2 days);
53
+ vm.assertEq(volumeRatePolicy.getTimePeriodDuration(), 2 days);
54
+ }
55
+
56
+ function test_setTimePeriodDuration_sameAmount_fails() public {
57
+ vm.startPrank(deployer, deployer);
58
+
59
+ vm.expectRevert("new duration same as current duration");
60
+ volumeRatePolicy.setTimePeriodDuration(1 days);
61
+ }
62
+
63
+ function test_setMaxAmount_succeeds() public {
64
+ vm.startPrank(deployer, deployer);
65
+
66
+ vm.expectEmit();
67
+ emit VolumeRatePolicy.MaxAmountSet(999);
68
+ volumeRatePolicy.setMaxAmount(999);
69
+ vm.assertEq(volumeRatePolicy.getMaxAmount(), 999);
70
+ }
71
+
72
+ function test_setMaxAmount_sameAmount_fails() public {
73
+ vm.startPrank(deployer, deployer);
74
+
75
+ vm.expectRevert("new max amount same as current max amount");
76
+ volumeRatePolicy.setMaxAmount(200);
77
+ }
78
+
79
+ function test_transfer_volumeBelowMaxAllowed_succeeds() public {
80
+ address recipient = makeAddr("recipient");
81
+
82
+ vm.startPrank(txSender);
83
+
84
+ token.transfer(recipient, 100);
85
+
86
+ assert(token.balanceOf(recipient) == 100);
87
+ }
88
+
89
+ function test_transfer_volumeAboveMaxAllowed_fails() public {
90
+ address recipient = makeAddr("recipient");
91
+
92
+ vm.startPrank(txSender);
93
+
94
+ token.transfer(recipient, 100);
95
+
96
+ vm.expectRevert(
97
+ _encodeRejectedRevert(
98
+ MockToken.transfer.selector, address(volumeRatePolicy), "volume rate limit exceeded for time period"
99
+ )
100
+ );
101
+ token.transfer(recipient, 101);
102
+
103
+ assert(token.balanceOf(recipient) == 100);
104
+ }
105
+
106
+ function test_transfer_volumeBelowMaxAllowedMultipleTimes_succeeds() public {
107
+ address recipient = makeAddr("recipient");
108
+
109
+ vm.startPrank(txSender);
110
+
111
+ token.transfer(recipient, 100);
112
+ token.transfer(recipient, 50);
113
+ token.transfer(recipient, 49);
114
+
115
+ assert(token.balanceOf(recipient) == 199);
116
+ }
117
+
118
+ function test_transfer_volumeTrackingResetsAfterDurationChange_succeeds() public {
119
+ address recipient = makeAddr("recipient");
120
+
121
+ vm.startPrank(txSender);
122
+
123
+ token.transfer(recipient, 100);
124
+ vm.warp(block.timestamp + 1 days);
125
+
126
+ token.transfer(recipient, 200);
127
+ assert(token.balanceOf(recipient) == 300);
128
+
129
+ vm.expectRevert(
130
+ _encodeRejectedRevert(
131
+ MockToken.transfer.selector, address(volumeRatePolicy), "volume rate limit exceeded for time period"
132
+ )
133
+ );
134
+ token.transfer(recipient, 1);
135
+ }
136
+
137
+ function test_transfer_sameTimePeriodAfterDurationChange_succeeds() public {
138
+ address recipient = makeAddr("recipient");
139
+
140
+ vm.startPrank(txSender);
141
+ vm.warp(1000 days);
142
+
143
+ // first transfer at time period 1000 (succeeds)
144
+ token.transfer(recipient, 200);
145
+ vm.assertEq(token.balanceOf(recipient), 200);
146
+
147
+ // second transfer at time period 1000 (fails)
148
+ vm.expectRevert(
149
+ _encodeRejectedRevert(
150
+ MockToken.transfer.selector, address(volumeRatePolicy), "volume rate limit exceeded for time period"
151
+ )
152
+ );
153
+ token.transfer(recipient, 200);
154
+
155
+ // change time period duration to 2 days and wrap to 2000 days
156
+ vm.startPrank(deployer);
157
+ volumeRatePolicy.setTimePeriodDuration(2 days);
158
+ vm.warp(2000 days);
159
+
160
+ // third transfer at time period 1000 (succeeds)
161
+ vm.startPrank(txSender);
162
+ token.transfer(recipient, 200);
163
+ vm.assertEq(token.balanceOf(recipient), 400);
164
+ }
165
+ }
@@ -0,0 +1,345 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5
+ import {ComplianceTokenStoreERC20} from "./ComplianceTokenStoreERC20.sol";
6
+ import {PolicyProtected} from "@chainlink/policy-management/core/PolicyProtected.sol";
7
+ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
8
+
9
+ /**
10
+ * @title ComplianceTokenERC20
11
+ * @notice A policy-protected ERC-20 compliant token with strict frozen token preservation.
12
+ *
13
+ * @dev This implementation enforces a strict separation between frozen and free balances:
14
+ *
15
+ * **Frozen Token Behavior:**
16
+ * - Frozen tokens remain immutably frozen during all operations
17
+ * - All operations (transfers, burns, etc.) require sufficient unfrozen balance
18
+ * - No automatic unfreezing occurs - frozen status must be explicitly managed
19
+ * - Supports "pre-freezing" tokens before they are received by an account
20
+ *
21
+ * **Balance Management:**
22
+ * - Total Balance = Free Balance + Frozen Balance
23
+ * - Available Balance = Total Balance - Frozen Balance
24
+ * - Operations only proceed if Available Balance >= Required Amount
25
+ *
26
+ * **Key Features:**
27
+ * - Maximum compliance control through explicit frozen token management
28
+ * - Administrative functions (freeze/unfreeze) are policy-protected
29
+ * - Force transfer capability for administrative operations
30
+ * - Integration with policy engine for complex compliance rules
31
+ *
32
+ * @dev Note: Alternative implementations may handle frozen tokens differently,
33
+ * such as automatically unfreezing tokens during operations for flexibility.
34
+ */
35
+ contract ComplianceTokenERC20 is Initializable, PolicyProtected, ComplianceTokenStoreERC20, IERC20 {
36
+ /**
37
+ * @notice Emitted when a freeze has been placed on an account.
38
+ * @param account The address of the account whose tokens were frozen.
39
+ * @param amount The amount of tokens frozen.
40
+ */
41
+ event Frozen(address indexed account, uint256 amount);
42
+
43
+ /**
44
+ * @notice Emitted when a freeze has been removed on an account.
45
+ * @param account The address of the account whose tokens were unfrozen.
46
+ * @param amount The amount of tokens unfrozen.
47
+ */
48
+ event Unfrozen(address indexed account, uint256 amount);
49
+
50
+ /**
51
+ * @notice Emitted when tokens are administratively transferred.
52
+ * @param from The address whose token balance is being reduced.
53
+ * @param to The address whose token balance is being increased.
54
+ * @param amount The amount of tokens transferred.
55
+ */
56
+ event ForceTransfer(address indexed from, address indexed to, uint256 amount);
57
+
58
+ /**
59
+ * @dev Initializes the contract with the provided token metadata and assigns policy engine.
60
+ * @param tokenName The name of the token.
61
+ * @param tokenSymbol The symbol of the token.
62
+ * @param tokenDecimals The number of decimals to use for display purposes.
63
+ * @param policyEngine The address of the policy engine contract.
64
+ */
65
+ function initialize(
66
+ string calldata tokenName,
67
+ string calldata tokenSymbol,
68
+ uint8 tokenDecimals,
69
+ address policyEngine
70
+ )
71
+ public
72
+ virtual
73
+ initializer
74
+ {
75
+ __ComplianceTokenERC20_init(tokenName, tokenSymbol, tokenDecimals, policyEngine);
76
+ }
77
+
78
+ /**
79
+ * @dev Upgradeable init function to be used by a token implementation contract.
80
+ * @param tokenName The name of the token.
81
+ * @param tokenSymbol The symbol of the token.
82
+ * @param tokenDecimals The number of decimals to use for display purposes.
83
+ * @param policyEngine The address of the policy engine contract.
84
+ */
85
+ function __ComplianceTokenERC20_init(
86
+ string memory tokenName,
87
+ string memory tokenSymbol,
88
+ uint8 tokenDecimals,
89
+ address policyEngine
90
+ )
91
+ internal
92
+ onlyInitializing
93
+ {
94
+ __ComplianceTokenERC20_init_unchained(tokenName, tokenSymbol, tokenDecimals);
95
+ __PolicyProtected_init(msg.sender, policyEngine);
96
+ }
97
+
98
+ /**
99
+ * @dev Unchained upgradeable init function to be used by a token implementation contract.
100
+ * @param tokenName The name of the token.
101
+ * @param tokenSymbol The symbol of the token.
102
+ * @param tokenDecimals The number of decimals to use for display purposes.
103
+ */
104
+ function __ComplianceTokenERC20_init_unchained(
105
+ string memory tokenName,
106
+ string memory tokenSymbol,
107
+ uint8 tokenDecimals
108
+ )
109
+ internal
110
+ onlyInitializing
111
+ {
112
+ ComplianceTokenStorage storage $ = getComplianceTokenStorage();
113
+ $.name = tokenName;
114
+ $.symbol = tokenSymbol;
115
+ $.decimals = tokenDecimals;
116
+ }
117
+
118
+ // ** ERC-20 Methods **
119
+ function totalSupply() public view virtual override returns (uint256) {
120
+ return getComplianceTokenStorage().totalSupply;
121
+ }
122
+
123
+ function balanceOf(address account) public view virtual override returns (uint256) {
124
+ return getComplianceTokenStorage().balances[account];
125
+ }
126
+
127
+ function transfer(address to, uint256 amount) public virtual override runPolicy returns (bool) {
128
+ _transfer(msg.sender, to, amount);
129
+ return true;
130
+ }
131
+
132
+ function allowance(address owner, address spender) public view virtual override returns (uint256) {
133
+ return getComplianceTokenStorage().allowances[owner][spender];
134
+ }
135
+
136
+ function approve(address spender, uint256 amount) public virtual override runPolicy returns (bool) {
137
+ _approve(msg.sender, spender, amount, true);
138
+ return true;
139
+ }
140
+
141
+ function transferFrom(address from, address to, uint256 amount) public virtual override runPolicy returns (bool) {
142
+ _spendAllowance(from, msg.sender, amount);
143
+ _transfer(from, to, amount);
144
+ return true;
145
+ }
146
+
147
+ // ** End ERC-20 Methods **
148
+
149
+ function name() public view virtual returns (string memory) {
150
+ return getComplianceTokenStorage().name;
151
+ }
152
+
153
+ function symbol() public view virtual returns (string memory) {
154
+ return getComplianceTokenStorage().symbol;
155
+ }
156
+
157
+ function decimals() public view virtual returns (uint8) {
158
+ return getComplianceTokenStorage().decimals;
159
+ }
160
+
161
+ function freeze(address account, uint256 amount, bytes calldata context) public virtual runPolicyWithContext(context) {
162
+ getComplianceTokenStorage().frozenBalances[account] += amount;
163
+ emit Frozen(account, amount);
164
+ }
165
+
166
+ function unfreeze(
167
+ address account,
168
+ uint256 amount,
169
+ bytes calldata context
170
+ )
171
+ public
172
+ virtual
173
+ runPolicyWithContext(context)
174
+ {
175
+ require(getComplianceTokenStorage().frozenBalances[account] >= amount, "amount exceeds frozen balance");
176
+
177
+ getComplianceTokenStorage().frozenBalances[account] -= amount;
178
+ emit Unfrozen(account, amount);
179
+ }
180
+
181
+ function frozenBalanceOf(address account) public view virtual returns (uint256) {
182
+ return getComplianceTokenStorage().frozenBalances[account];
183
+ }
184
+
185
+ /**
186
+ * @notice Administratively transfers tokens between accounts.
187
+ * @dev Performs a policy-protected transfer while preserving frozen token status.
188
+ *
189
+ * **Frozen Token Handling:**
190
+ * - Frozen tokens remain frozen during the transfer
191
+ * - Operation will revert if insufficient unfrozen balance exists
192
+ * - Explicit unfreezing is required before transfer if needed
193
+ *
194
+ * This ensures administrative transfers maintain strict compliance with
195
+ * frozen token restrictions.
196
+ *
197
+ * @param from The address whose token balance is being reduced
198
+ * @param to The address whose token balance is being increased
199
+ * @param amount The amount of tokens to transfer
200
+ * @param context Additional context for policy validation
201
+ */
202
+ function forceTransfer(
203
+ address from,
204
+ address to,
205
+ uint256 amount,
206
+ bytes calldata context
207
+ )
208
+ public
209
+ virtual
210
+ runPolicyWithContext(context)
211
+ {
212
+ require(from != address(0), "transfer from the zero address");
213
+ require(to != address(0), "transfer to the zero address");
214
+
215
+ _update(from, to, amount);
216
+ emit ForceTransfer(from, to, amount);
217
+ }
218
+
219
+ function mint(address to, uint256 amount) public virtual runPolicy {
220
+ _mint(to, amount);
221
+ }
222
+
223
+ function burn(uint256 amount) public virtual runPolicy {
224
+ _burn(msg.sender, amount);
225
+ }
226
+
227
+ function burnFrom(address from, uint256 amount) public virtual runPolicy {
228
+ _burn(from, amount);
229
+ }
230
+
231
+ function getCCIPAdmin() public view virtual returns (address) {
232
+ return owner();
233
+ }
234
+
235
+ function supportsInterface(bytes4 interfaceId) public view virtual override(PolicyProtected) returns (bool) {
236
+ return super.supportsInterface(interfaceId);
237
+ }
238
+
239
+ function _transfer(address from, address to, uint256 amount) internal {
240
+ require(from != address(0), "ERC20: transfer from the zero address");
241
+ require(to != address(0), "ERC20: transfer to the zero address");
242
+
243
+ _checkFrozenBalance(from, amount);
244
+ _update(from, to, amount);
245
+ }
246
+
247
+ function _approve(address owner, address spender, uint256 amount, bool emitEvent) internal {
248
+ require(owner != address(0), "ERC20: approve owner the zero address");
249
+ require(spender != address(0), "ERC20: approve spender the zero address");
250
+
251
+ getComplianceTokenStorage().allowances[owner][spender] = amount;
252
+ if (emitEvent) {
253
+ emit Approval(owner, spender, amount);
254
+ }
255
+ }
256
+
257
+ function _spendAllowance(address owner, address spender, uint256 amount) internal {
258
+ uint256 currentAllowance = allowance(owner, spender);
259
+ if (currentAllowance != type(uint256).max) {
260
+ require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
261
+ unchecked {
262
+ _approve(owner, spender, currentAllowance - amount, false);
263
+ }
264
+ }
265
+ }
266
+
267
+ /**
268
+ * @notice Checks if an account has sufficient unfrozen balance for an operation.
269
+ * @dev Enforces strict frozen token preservation by ensuring operations only
270
+ * use available (unfrozen) tokens:
271
+ *
272
+ * **Calculation:** `availableBalance = totalBalance - frozenBalance`
273
+ *
274
+ * The operation is allowed only if `availableBalance >= amount`.
275
+ * Frozen tokens remain frozen and cannot be used for any operations.
276
+ *
277
+ * @param account The account to check
278
+ * @param amount The amount needed for the operation
279
+ */
280
+ function _checkFrozenBalance(address account, uint256 amount) internal view {
281
+ require(
282
+ getComplianceTokenStorage().balances[account] >= amount + getComplianceTokenStorage().frozenBalances[account],
283
+ "amount exceeds available balance"
284
+ );
285
+ }
286
+
287
+ function _mint(address to, uint256 amount) internal {
288
+ require(to != address(0), "ERC20: mint to the zero address");
289
+
290
+ _update(address(0), to, amount);
291
+ }
292
+
293
+ /**
294
+ * @notice Burns tokens from an account.
295
+ * @dev Destroys tokens while preserving frozen token status.
296
+ *
297
+ * **Frozen Token Handling:**
298
+ * - Frozen tokens remain frozen during the burn operation
299
+ * - Operation will revert if insufficient unfrozen balance exists
300
+ * - Explicit unfreezing is required before burning if needed
301
+ *
302
+ * This ensures burn operations maintain strict compliance with
303
+ * frozen token restrictions, only destroying genuinely available tokens.
304
+ *
305
+ * @param from The address to burn tokens from
306
+ * @param amount The amount of tokens to burn
307
+ */
308
+ function _burn(address from, uint256 amount) internal {
309
+ require(from != address(0), "ERC20: burn from the zero address");
310
+
311
+ _checkFrozenBalance(from, amount);
312
+ _update(from, address(0), amount);
313
+ }
314
+
315
+ function _update(address from, address to, uint256 amount) internal virtual {
316
+ ComplianceTokenStorage storage $ = getComplianceTokenStorage();
317
+ if (from == address(0)) {
318
+ // Overflow check required: The rest of the code assumes that totalSupply never overflows
319
+ $.totalSupply += amount;
320
+ } else {
321
+ uint256 fromBalance = $.balances[from];
322
+ if (fromBalance < amount) {
323
+ revert("ERC20: transfer amount exceeds balance");
324
+ }
325
+ unchecked {
326
+ // Overflow not possible: amount <= fromBalance <= totalSupply.
327
+ $.balances[from] = fromBalance - amount;
328
+ }
329
+ }
330
+
331
+ if (to == address(0)) {
332
+ unchecked {
333
+ // Overflow not possible: amount <= totalSupply or amount <= fromBalance <= totalSupply.
334
+ $.totalSupply -= amount;
335
+ }
336
+ } else {
337
+ unchecked {
338
+ // Overflow not possible: balance + amount is at most totalSupply, which we know fits into a uint256.
339
+ $.balances[to] += amount;
340
+ }
341
+ }
342
+
343
+ emit Transfer(from, to, amount);
344
+ }
345
+ }
@@ -0,0 +1,29 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity 0.8.26;
3
+
4
+ contract ComplianceTokenStoreERC20 {
5
+ /// @custom:storage-location erc7201:compliance-token-erc20.ComplianceTokenStoreERC20
6
+ struct ComplianceTokenStorage {
7
+ string name;
8
+ string symbol;
9
+ uint8 decimals;
10
+ uint256 totalSupply;
11
+ mapping(address account => uint256 balance) balances;
12
+ mapping(address account => mapping(address spender => uint256 allowance)) allowances;
13
+ mapping(address account => uint256 amount) frozenBalances;
14
+ mapping(bytes32 key => bytes data) data;
15
+ }
16
+
17
+ // keccak256(abi.encode(uint256(keccak256("compliance-token-erc20.ComplianceTokenStoreERC20")) - 1)) &
18
+ // ~bytes32(uint256(0xff))
19
+ // solhint-disable-next-line const-name-snakecase
20
+ bytes32 private constant complianceTokenStorageLocation =
21
+ 0xeb7f727e418fdce2e00aa1f4b00053561f65d67239278319bdbcc2711bc43500;
22
+
23
+ function getComplianceTokenStorage() internal pure returns (ComplianceTokenStorage storage $) {
24
+ // solhint-disable-next-line no-inline-assembly
25
+ assembly {
26
+ $.slot := complianceTokenStorageLocation
27
+ }
28
+ }
29
+ }