@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.
- package/.foundry-version +1 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/workflows/auto-release-version.yml +107 -0
- package/.github/workflows/create-version-pr.yml +95 -0
- package/.github/workflows/forge-docs.yml +90 -0
- package/.github/workflows/forge-test.yml +59 -0
- package/.solhint-test.json +18 -0
- package/.solhint.json +16 -0
- package/.solhintignore +3 -0
- package/.solhintignore-test +2 -0
- package/Glossary.md +141 -0
- package/LICENSE +59 -0
- package/README.md +218 -0
- package/assets/chainlink-logo.svg +21 -0
- package/chainlink-ace-License-grants +2 -0
- package/foundry.toml +33 -0
- package/getting_started/GETTING_STARTED.md +477 -0
- package/getting_started/MyVault.sol +48 -0
- package/getting_started/advanced/.env.example +36 -0
- package/getting_started/advanced/GETTING_STARTED_ADVANCED.md +431 -0
- package/getting_started/advanced/SanctionsList.sol +25 -0
- package/getting_started/advanced/SanctionsPolicy.sol +58 -0
- package/package.json +41 -0
- package/packages/cross-chain-identity/README.md +148 -0
- package/packages/cross-chain-identity/docs/API_GUIDE.md +120 -0
- package/packages/cross-chain-identity/docs/API_REFERENCE.md +271 -0
- package/packages/cross-chain-identity/docs/CONCEPTS.md +253 -0
- package/packages/cross-chain-identity/docs/CREDENTIAL_FLOW.md +195 -0
- package/packages/cross-chain-identity/docs/SECURITY.md +70 -0
- package/packages/cross-chain-identity/src/CredentialRegistry.sol +245 -0
- package/packages/cross-chain-identity/src/CredentialRegistryIdentityValidator.sol +339 -0
- package/packages/cross-chain-identity/src/CredentialRegistryIdentityValidatorPolicy.sol +71 -0
- package/packages/cross-chain-identity/src/IdentityRegistry.sol +123 -0
- package/packages/cross-chain-identity/src/TrustedIssuerRegistry.sol +140 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialDataValidator.sol +30 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialRegistry.sol +170 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialRequirements.sol +192 -0
- package/packages/cross-chain-identity/src/interfaces/ICredentialValidator.sol +37 -0
- package/packages/cross-chain-identity/src/interfaces/IIdentityRegistry.sol +85 -0
- package/packages/cross-chain-identity/src/interfaces/IIdentityValidator.sol +18 -0
- package/packages/cross-chain-identity/src/interfaces/ITrustedIssuerRegistry.sol +61 -0
- package/packages/cross-chain-identity/test/CredentialRegistry.t.sol +220 -0
- package/packages/cross-chain-identity/test/CredentialRegistryIdentityValidator.t.sol +554 -0
- package/packages/cross-chain-identity/test/CredentialRegistryIdentityValidatorPolicy.t.sol +114 -0
- package/packages/cross-chain-identity/test/IdentityRegistry.t.sol +106 -0
- package/packages/cross-chain-identity/test/IdentityValidator.t.sol +969 -0
- package/packages/cross-chain-identity/test/TrustedIssuerRegistry.t.sol +123 -0
- package/packages/cross-chain-identity/test/helpers/BaseProxyTest.sol +112 -0
- package/packages/cross-chain-identity/test/helpers/MockCredentialDataValidator.sol +26 -0
- package/packages/cross-chain-identity/test/helpers/MockCredentialRegistryReverting.sol +131 -0
- package/packages/policy-management/README.md +197 -0
- package/packages/policy-management/docs/API_GUIDE.md +290 -0
- package/packages/policy-management/docs/API_REFERENCE.md +173 -0
- package/packages/policy-management/docs/CONCEPTS.md +156 -0
- package/packages/policy-management/docs/CUSTOM_POLICIES_TUTORIAL.md +195 -0
- package/packages/policy-management/docs/POLICY_ORDERING_GUIDE.md +91 -0
- package/packages/policy-management/docs/SECURITY.md +57 -0
- package/packages/policy-management/src/core/Policy.sol +124 -0
- package/packages/policy-management/src/core/PolicyEngine.sol +382 -0
- package/packages/policy-management/src/core/PolicyFactory.sol +92 -0
- package/packages/policy-management/src/core/PolicyProtected.sol +126 -0
- package/packages/policy-management/src/extractors/ComplianceTokenForceTransferExtractor.sol +57 -0
- package/packages/policy-management/src/extractors/ComplianceTokenFreezeUnfreezeExtractor.sol +54 -0
- package/packages/policy-management/src/extractors/ComplianceTokenMintBurnExtractor.sol +61 -0
- package/packages/policy-management/src/extractors/ERC20ApproveExtractor.sol +57 -0
- package/packages/policy-management/src/extractors/ERC20TransferExtractor.sol +62 -0
- package/packages/policy-management/src/extractors/ERC3643ForcedTransferExtractor.sol +56 -0
- package/packages/policy-management/src/extractors/ERC3643FreezeUnfreezeExtractor.sol +55 -0
- package/packages/policy-management/src/extractors/ERC3643MintBurnExtractor.sol +51 -0
- package/packages/policy-management/src/extractors/ERC3643SetAddressFrozenExtractor.sol +51 -0
- package/packages/policy-management/src/interfaces/IExtractor.sol +17 -0
- package/packages/policy-management/src/interfaces/IMapper.sol +17 -0
- package/packages/policy-management/src/interfaces/IPolicy.sol +61 -0
- package/packages/policy-management/src/interfaces/IPolicyEngine.sol +264 -0
- package/packages/policy-management/src/interfaces/IPolicyProtected.sol +48 -0
- package/packages/policy-management/src/policies/AllowPolicy.sol +104 -0
- package/packages/policy-management/src/policies/BypassPolicy.sol +90 -0
- package/packages/policy-management/src/policies/IntervalPolicy.sol +223 -0
- package/packages/policy-management/src/policies/MaxPolicy.sol +73 -0
- package/packages/policy-management/src/policies/OnlyAuthorizedSenderPolicy.sol +84 -0
- package/packages/policy-management/src/policies/OnlyOwnerPolicy.sol +35 -0
- package/packages/policy-management/src/policies/PausePolicy.sol +82 -0
- package/packages/policy-management/src/policies/README.md +632 -0
- package/packages/policy-management/src/policies/RejectPolicy.sol +89 -0
- package/packages/policy-management/src/policies/RoleBasedAccessControlPolicy.sol +162 -0
- package/packages/policy-management/src/policies/SecureMintPolicy.sol +271 -0
- package/packages/policy-management/src/policies/VolumePolicy.sol +133 -0
- package/packages/policy-management/src/policies/VolumeRatePolicy.sol +192 -0
- package/packages/policy-management/test/PolicyEngine.t.sol +368 -0
- package/packages/policy-management/test/PolicyFactory.t.sol +114 -0
- package/packages/policy-management/test/PolicyProtectedToken.t.sol +75 -0
- package/packages/policy-management/test/extractors/ComplianceTokenForceTransferExtractor.t.sol +59 -0
- package/packages/policy-management/test/extractors/ComplianceTokenFreezeUnfreezeExtractor.t.sol +74 -0
- package/packages/policy-management/test/extractors/ComplianceTokenMintBurnExtractor.t.sol +92 -0
- package/packages/policy-management/test/extractors/ERC20ApproveExtractor.t.sol +58 -0
- package/packages/policy-management/test/extractors/ERC3643ForcedTransferExtractor.t.sol +59 -0
- package/packages/policy-management/test/extractors/ERC3643FreezeUnfreezeExtractor.t.sol +74 -0
- package/packages/policy-management/test/extractors/ERC3643MintBurnExtractor.t.sol +73 -0
- package/packages/policy-management/test/extractors/ERC3643SetAddressFrozenExtractor.t.sol +56 -0
- package/packages/policy-management/test/helpers/BaseProxyTest.sol +75 -0
- package/packages/policy-management/test/helpers/CustomMapper.sol +26 -0
- package/packages/policy-management/test/helpers/DummyExtractor.sol +11 -0
- package/packages/policy-management/test/helpers/ExpectedParameterPolicy.sol +39 -0
- package/packages/policy-management/test/helpers/MockAggregatorV3.sol +51 -0
- package/packages/policy-management/test/helpers/MockToken.sol +66 -0
- package/packages/policy-management/test/helpers/MockTokenExtractor.sol +34 -0
- package/packages/policy-management/test/helpers/PolicyAlwaysAllowed.sol +45 -0
- package/packages/policy-management/test/helpers/PolicyAlwaysContinue.sol +23 -0
- package/packages/policy-management/test/helpers/PolicyAlwaysRejected.sol +23 -0
- package/packages/policy-management/test/helpers/PolicyFailingRun.sol +22 -0
- package/packages/policy-management/test/policies/AllowPolicy.t.sol +174 -0
- package/packages/policy-management/test/policies/BypassPolicy.t.sol +159 -0
- package/packages/policy-management/test/policies/IntervalPolicy.t.sol +307 -0
- package/packages/policy-management/test/policies/MaxPolicy.t.sol +54 -0
- package/packages/policy-management/test/policies/OnlyAuthorizedSenderPolicy.t.sol +95 -0
- package/packages/policy-management/test/policies/OnlyOwnerPolicy.t.sol +47 -0
- package/packages/policy-management/test/policies/PausePolicy.t.sol +75 -0
- package/packages/policy-management/test/policies/RejectPolicy.t.sol +182 -0
- package/packages/policy-management/test/policies/RoleBasedAccessControlPolicy.t.sol +223 -0
- package/packages/policy-management/test/policies/SecureMintPolicy.t.sol +442 -0
- package/packages/policy-management/test/policies/VolumePolicy.t.sol +158 -0
- package/packages/policy-management/test/policies/VolumeRatePolicy.t.sol +165 -0
- package/packages/tokens/erc-20/src/ComplianceTokenERC20.sol +345 -0
- package/packages/tokens/erc-20/src/ComplianceTokenStoreERC20.sol +29 -0
- package/packages/tokens/erc-20/test/ComplianceTokenERC20.t.sol +556 -0
- package/packages/tokens/erc-20/test/helpers/BaseProxyTest.sol +75 -0
- package/packages/tokens/erc-3643/README.md +24 -0
- package/packages/tokens/erc-3643/src/ComplianceTokenERC3643.sol +564 -0
- package/packages/tokens/erc-3643/src/ComplianceTokenStoreERC3643.sol +30 -0
- package/packages/tokens/erc-3643/test/ComplianceTokenERC3643.t.sol +815 -0
- package/packages/tokens/erc-3643/test/helpers/BaseProxyTest.sol +76 -0
- package/packages/tokens/erc-3643/test/helpers/ExpectedContextPolicy.sol +32 -0
- package/packages/vendor/erc-3643/compliance/modular/IModularCompliance.sol +220 -0
- package/packages/vendor/erc-3643/registry/interface/IClaimTopicsRegistry.sol +101 -0
- package/packages/vendor/erc-3643/registry/interface/IIdentityRegistry.sol +251 -0
- package/packages/vendor/erc-3643/registry/interface/IIdentityRegistryStorage.sol +191 -0
- package/packages/vendor/erc-3643/registry/interface/ITrustedIssuersRegistry.sol +161 -0
- package/packages/vendor/erc-3643/token/IToken.sol +457 -0
- package/packages/vendor/onchain-id/interface/IClaimIssuer.sol +53 -0
- package/packages/vendor/onchain-id/interface/IERC734.sol +110 -0
- package/packages/vendor/onchain-id/interface/IERC735.sol +105 -0
- package/packages/vendor/onchain-id/interface/IIdentity.sol +26 -0
- package/packages/vendor/onchain-id/interface/IImplementationAuthority.sol +21 -0
- package/remappings.txt +6 -0
- package/script/DeployComplianceTokenERC20.s.sol +191 -0
- package/script/DeployComplianceTokenERC3643.s.sol +208 -0
- package/script/DeploySimpleComplianceToken.s.sol +38 -0
- package/script/getting_started/DeployGettingStarted.s.sol +74 -0
- package/script/getting_started/advanced/DeployAdvancedGettingStarted.s.sol +332 -0
- 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
|
+
}
|