@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,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
|