@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,442 @@
|
|
|
1
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
5
|
+
import {IPolicyEngine} from "@chainlink/policy-management/interfaces/IPolicyEngine.sol";
|
|
6
|
+
import {PolicyEngine} from "@chainlink/policy-management/core/PolicyEngine.sol";
|
|
7
|
+
import {ERC3643MintBurnExtractor} from "@chainlink/policy-management/extractors/ERC3643MintBurnExtractor.sol";
|
|
8
|
+
import {SecureMintPolicy} from "@chainlink/policy-management/policies/SecureMintPolicy.sol";
|
|
9
|
+
import {MockToken} from "../helpers/MockToken.sol";
|
|
10
|
+
import {MockAggregatorV3} from "../helpers/MockAggregatorV3.sol";
|
|
11
|
+
import {BaseProxyTest} from "../helpers/BaseProxyTest.sol";
|
|
12
|
+
|
|
13
|
+
contract SecureMintPolicyTest is BaseProxyTest {
|
|
14
|
+
PolicyEngine public policyEngine;
|
|
15
|
+
SecureMintPolicy public policy;
|
|
16
|
+
ERC3643MintBurnExtractor public extractor;
|
|
17
|
+
MockToken public token;
|
|
18
|
+
MockAggregatorV3 public porFeed;
|
|
19
|
+
address public deployer;
|
|
20
|
+
address public recipient;
|
|
21
|
+
|
|
22
|
+
function setUp() public {
|
|
23
|
+
deployer = makeAddr("deployer");
|
|
24
|
+
recipient = makeAddr("recipient");
|
|
25
|
+
|
|
26
|
+
vm.startPrank(deployer, deployer);
|
|
27
|
+
|
|
28
|
+
policyEngine = _deployPolicyEngine(true, deployer);
|
|
29
|
+
|
|
30
|
+
token = MockToken(_deployMockToken(address(policyEngine)));
|
|
31
|
+
|
|
32
|
+
porFeed = new MockAggregatorV3(42 ether, 18);
|
|
33
|
+
|
|
34
|
+
extractor = new ERC3643MintBurnExtractor();
|
|
35
|
+
bytes32[] memory parameterOutputFormat = new bytes32[](1);
|
|
36
|
+
parameterOutputFormat[0] = extractor.PARAM_AMOUNT();
|
|
37
|
+
|
|
38
|
+
SecureMintPolicy policyImpl = new SecureMintPolicy();
|
|
39
|
+
policy = SecureMintPolicy(
|
|
40
|
+
_deployPolicy(
|
|
41
|
+
address(policyImpl),
|
|
42
|
+
address(policyEngine),
|
|
43
|
+
deployer,
|
|
44
|
+
abi.encode(address(porFeed), SecureMintPolicy.ReserveMarginMode.None, 0, 0)
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
policyEngine.setExtractor(MockToken.mint.selector, address(extractor));
|
|
49
|
+
policyEngine.addPolicy(address(token), MockToken.mint.selector, address(policy), parameterOutputFormat);
|
|
50
|
+
|
|
51
|
+
vm.warp(1737583804);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function test_initialize_configEvents_succeeds() public {
|
|
55
|
+
SecureMintPolicy policyImpl = new SecureMintPolicy();
|
|
56
|
+
|
|
57
|
+
// Set the reserves feed to a new address
|
|
58
|
+
vm.expectEmit();
|
|
59
|
+
emit SecureMintPolicy.ReservesFeedSet(address(porFeed));
|
|
60
|
+
vm.expectEmit();
|
|
61
|
+
emit SecureMintPolicy.ReserveMarginSet(SecureMintPolicy.ReserveMarginMode.None, 0);
|
|
62
|
+
vm.expectEmit();
|
|
63
|
+
emit SecureMintPolicy.MaxStalenessSecondsSet(600);
|
|
64
|
+
policy = SecureMintPolicy(
|
|
65
|
+
_deployPolicy(
|
|
66
|
+
address(policyImpl),
|
|
67
|
+
address(policyEngine),
|
|
68
|
+
deployer,
|
|
69
|
+
abi.encode(address(porFeed), SecureMintPolicy.ReserveMarginMode.None, 0, 600)
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function test_setReservesFeed_succeeds() public {
|
|
75
|
+
MockAggregatorV3 newPorFeed = new MockAggregatorV3(42 ether, 18);
|
|
76
|
+
|
|
77
|
+
vm.startPrank(deployer, deployer);
|
|
78
|
+
|
|
79
|
+
// Set the reserves feed to a new address
|
|
80
|
+
vm.expectEmit();
|
|
81
|
+
emit SecureMintPolicy.ReservesFeedSet(address(newPorFeed));
|
|
82
|
+
policy.setReservesFeed(address(newPorFeed));
|
|
83
|
+
vm.assertEq(address(policy.reservesFeed()), address(newPorFeed));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function test_setReservesFeed_notOwner_reverts() public {
|
|
87
|
+
MockAggregatorV3 newPorFeed = new MockAggregatorV3(42 ether, 18);
|
|
88
|
+
|
|
89
|
+
vm.startPrank(recipient, recipient);
|
|
90
|
+
|
|
91
|
+
vm.expectPartialRevert(OwnableUpgradeable.OwnableUnauthorizedAccount.selector);
|
|
92
|
+
policy.setReservesFeed(address(newPorFeed));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function test_setReservesFeed_sameAsCurrent_reverts() public {
|
|
96
|
+
vm.startPrank(deployer, deployer);
|
|
97
|
+
|
|
98
|
+
// Set the reserves feed to the same address
|
|
99
|
+
vm.expectRevert("feed same as current");
|
|
100
|
+
policy.setReservesFeed(address(porFeed));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function test_setReserveMargin_succeeds() public {
|
|
104
|
+
vm.startPrank(deployer, deployer);
|
|
105
|
+
|
|
106
|
+
vm.expectEmit();
|
|
107
|
+
emit SecureMintPolicy.ReserveMarginSet(SecureMintPolicy.ReserveMarginMode.PositivePercentage, 1000);
|
|
108
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositivePercentage, 1000);
|
|
109
|
+
assertEq(uint256(policy.reserveMarginMode()), 1);
|
|
110
|
+
assertEq(policy.reserveMarginAmount(), 1000);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function test_setReserveMargin_notOwner_reverts() public {
|
|
114
|
+
vm.startPrank(recipient, recipient);
|
|
115
|
+
|
|
116
|
+
vm.expectPartialRevert(OwnableUpgradeable.OwnableUnauthorizedAccount.selector);
|
|
117
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositivePercentage, 1000);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function test_setReserveMargin_sameAsCurrent_reverts() public {
|
|
121
|
+
vm.startPrank(deployer, deployer);
|
|
122
|
+
|
|
123
|
+
vm.expectRevert("margin same as current");
|
|
124
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.None, 0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function test_setReserveMargin_positivePercentageExceedsMax_reverts() public {
|
|
128
|
+
vm.startPrank(deployer, deployer);
|
|
129
|
+
|
|
130
|
+
vm.expectRevert("margin must be <= BASIS_POINTS for percentage modes");
|
|
131
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositivePercentage, 10001);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function test_setReserveMargin_negativePercentageExceedsMax_reverts() public {
|
|
135
|
+
vm.startPrank(deployer, deployer);
|
|
136
|
+
|
|
137
|
+
vm.expectRevert("margin must be <= BASIS_POINTS for percentage modes");
|
|
138
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.NegativePercentage, 10001);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function test_setReserveMargin_positiveAbsoluteZeroAmount_reverts() public {
|
|
142
|
+
vm.startPrank(deployer, deployer);
|
|
143
|
+
|
|
144
|
+
vm.expectRevert("margin must be > 0 for absolute modes");
|
|
145
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositiveAbsolute, 0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function test_setReserveMargin_negativeAbsoluteZeroAmount_reverts() public {
|
|
149
|
+
vm.startPrank(deployer, deployer);
|
|
150
|
+
|
|
151
|
+
vm.expectRevert("margin must be > 0 for absolute modes");
|
|
152
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.NegativeAbsolute, 0);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function test_setMaxStalenessSeconds_succeeds() public {
|
|
156
|
+
vm.startPrank(deployer, deployer);
|
|
157
|
+
|
|
158
|
+
vm.expectEmit();
|
|
159
|
+
emit SecureMintPolicy.MaxStalenessSecondsSet(600);
|
|
160
|
+
policy.setMaxStalenessSeconds(600);
|
|
161
|
+
assertEq(policy.maxStalenessSeconds(), 600);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function test_setMaxStalenessSeconds_notOwner_reverts() public {
|
|
165
|
+
vm.startPrank(recipient, recipient);
|
|
166
|
+
|
|
167
|
+
vm.expectPartialRevert(OwnableUpgradeable.OwnableUnauthorizedAccount.selector);
|
|
168
|
+
policy.setMaxStalenessSeconds(600);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function test_setMaxStalenessSeconds_sameAsCurrent_reverts() public {
|
|
172
|
+
vm.startPrank(deployer, deployer);
|
|
173
|
+
|
|
174
|
+
vm.expectRevert("value same as current");
|
|
175
|
+
policy.setMaxStalenessSeconds(0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function test_mint_marginModeNone_succeeds() public {
|
|
179
|
+
vm.startPrank(deployer, deployer);
|
|
180
|
+
|
|
181
|
+
// 0 + 40 <= 42, succeeds
|
|
182
|
+
token.mint(recipient, 40 ether);
|
|
183
|
+
assertEq(token.balanceOf(recipient), 40 ether);
|
|
184
|
+
|
|
185
|
+
// 40 + 2 <= 42, succeeds
|
|
186
|
+
token.mint(recipient, 2 ether);
|
|
187
|
+
assertEq(token.balanceOf(recipient), 42 ether);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function test_mint_marginModeNone_reverts() public {
|
|
191
|
+
vm.startPrank(deployer, deployer);
|
|
192
|
+
|
|
193
|
+
// 0 + 40 <= 42, succeeds
|
|
194
|
+
token.mint(recipient, 40 ether);
|
|
195
|
+
assertEq(token.balanceOf(recipient), 40 ether);
|
|
196
|
+
|
|
197
|
+
// 40 + 3 > 42, reverts
|
|
198
|
+
vm.expectRevert(
|
|
199
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
200
|
+
);
|
|
201
|
+
token.mint(recipient, 3 ether);
|
|
202
|
+
|
|
203
|
+
// 40 + 2 <= 42, succeeds
|
|
204
|
+
token.mint(recipient, 2 ether);
|
|
205
|
+
assertEq(token.balanceOf(recipient), 42 ether);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function test_mint_marginModePositivePercentage_succeeds() public {
|
|
209
|
+
vm.startPrank(deployer, deployer);
|
|
210
|
+
|
|
211
|
+
// set margin (mintable amount = 42 ether * (1 - 20%) = 33.6 ether)
|
|
212
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositivePercentage, 2000); // 20%
|
|
213
|
+
|
|
214
|
+
// 0 + 30 <= 33.6, succeeds
|
|
215
|
+
token.mint(recipient, 30 ether);
|
|
216
|
+
assertEq(token.balanceOf(recipient), 30 ether);
|
|
217
|
+
|
|
218
|
+
// 30 + 3.6 <= 33.6, succeeds
|
|
219
|
+
token.mint(recipient, 3.6 ether);
|
|
220
|
+
assertEq(token.balanceOf(recipient), 33.6 ether);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function test_mint_marginModePositivePercentage100_reverts() public {
|
|
224
|
+
vm.startPrank(deployer, deployer);
|
|
225
|
+
|
|
226
|
+
// set margin (mintable amount = 42 ether * (1 - 100%) = 0 ether)
|
|
227
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositivePercentage, 10000); // 100%
|
|
228
|
+
|
|
229
|
+
// 0 + 0.1 > 0, reverts
|
|
230
|
+
vm.expectRevert(
|
|
231
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
232
|
+
);
|
|
233
|
+
token.mint(recipient, 0.1 ether);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function test_mint_marginModePositivePercentage_reverts() public {
|
|
237
|
+
vm.startPrank(deployer, deployer);
|
|
238
|
+
|
|
239
|
+
// set margin (mintable amount = 42 ether * (1 - 20%) = 33.6 ether)
|
|
240
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositivePercentage, 2000); // 20%
|
|
241
|
+
|
|
242
|
+
// 0 + 30 <= 33.6, succeeds
|
|
243
|
+
token.mint(recipient, 30 ether);
|
|
244
|
+
assertEq(token.balanceOf(recipient), 30 ether);
|
|
245
|
+
|
|
246
|
+
// 30 + 4 > 33.6, reverts
|
|
247
|
+
vm.expectRevert(
|
|
248
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
249
|
+
);
|
|
250
|
+
token.mint(recipient, 4 ether);
|
|
251
|
+
|
|
252
|
+
// 30 + 3 <= 33.6, succeeds
|
|
253
|
+
token.mint(recipient, 3 ether);
|
|
254
|
+
assertEq(token.balanceOf(recipient), 33 ether);
|
|
255
|
+
|
|
256
|
+
// 35 + 1 > 33.6, reverts
|
|
257
|
+
vm.expectRevert(
|
|
258
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
259
|
+
);
|
|
260
|
+
token.mint(recipient, 1 ether);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function test_mint_marginModePositiveAbsolute_succeeds() public {
|
|
264
|
+
vm.startPrank(deployer, deployer);
|
|
265
|
+
|
|
266
|
+
// set margin (mintable amount = 42 ether - 2 ether = 40 ether)
|
|
267
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositiveAbsolute, 2 ether); // 2 ether
|
|
268
|
+
|
|
269
|
+
// 0 + 30 <= 40, succeeds
|
|
270
|
+
token.mint(recipient, 30 ether);
|
|
271
|
+
assertEq(token.balanceOf(recipient), 30 ether);
|
|
272
|
+
|
|
273
|
+
// 30 + 10 <= 40, succeeds
|
|
274
|
+
token.mint(recipient, 10 ether);
|
|
275
|
+
assertEq(token.balanceOf(recipient), 40 ether);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function test_mint_marginModePositiveAbsolute_reverts() public {
|
|
279
|
+
vm.startPrank(deployer, deployer);
|
|
280
|
+
|
|
281
|
+
// set margin (mintable amount = 42 ether - 2 ether = 40 ether)
|
|
282
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositiveAbsolute, 2 ether); // 2 ether
|
|
283
|
+
|
|
284
|
+
// 0 + 30 <= 40, succeeds
|
|
285
|
+
token.mint(recipient, 30 ether);
|
|
286
|
+
assertEq(token.balanceOf(recipient), 30 ether);
|
|
287
|
+
|
|
288
|
+
// 30 + 15 > 40, reverts
|
|
289
|
+
vm.expectRevert(
|
|
290
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
291
|
+
);
|
|
292
|
+
token.mint(recipient, 12 ether);
|
|
293
|
+
|
|
294
|
+
// 30 + 10 <= 40, succeeds
|
|
295
|
+
token.mint(recipient, 10 ether);
|
|
296
|
+
assertEq(token.balanceOf(recipient), 40 ether);
|
|
297
|
+
|
|
298
|
+
// 40 + 1 > 40, reverts
|
|
299
|
+
vm.expectRevert(
|
|
300
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
301
|
+
);
|
|
302
|
+
token.mint(recipient, 1 ether);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function test_mint_marginModePositiveAbsoluteExceedsReserves_reverts() public {
|
|
306
|
+
vm.startPrank(deployer, deployer);
|
|
307
|
+
|
|
308
|
+
// set margin greater than reserves (mintable amount = max(0, 42 ether - 50 ether) = 0 ether)
|
|
309
|
+
// 50 ether > 42 ether reserves
|
|
310
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.PositiveAbsolute, 50 ether);
|
|
311
|
+
|
|
312
|
+
// 0 + 0.1 > 0, reverts (any minting should be blocked)
|
|
313
|
+
vm.expectRevert(
|
|
314
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
315
|
+
);
|
|
316
|
+
token.mint(recipient, 0.1 ether);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function test_mint_marginModeNegativePercentage_succeeds() public {
|
|
320
|
+
vm.startPrank(deployer, deployer);
|
|
321
|
+
|
|
322
|
+
// set margin (mintable amount = 42 ether * (1 - (-20%)) = 50.4 ether)
|
|
323
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.NegativePercentage, 2000); // -20%
|
|
324
|
+
|
|
325
|
+
// 0 + 50 <= 50.4, succeeds
|
|
326
|
+
token.mint(recipient, 50 ether);
|
|
327
|
+
assertEq(token.balanceOf(recipient), 50 ether);
|
|
328
|
+
|
|
329
|
+
// 50 + 0.4 <= 50.4, succeeds
|
|
330
|
+
token.mint(recipient, 0.4 ether);
|
|
331
|
+
assertEq(token.balanceOf(recipient), 50.4 ether);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function test_mint_marginModeNegativePercentage100_succeeds() public {
|
|
335
|
+
vm.startPrank(deployer, deployer);
|
|
336
|
+
|
|
337
|
+
// set margin (mintable amount = 42 ether * (1 - (-100%)) = 84 ether)
|
|
338
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.NegativePercentage, 10000); // -100%
|
|
339
|
+
|
|
340
|
+
// 0 + 50 <= 84, succeeds
|
|
341
|
+
token.mint(recipient, 50 ether);
|
|
342
|
+
assertEq(token.balanceOf(recipient), 50 ether);
|
|
343
|
+
|
|
344
|
+
// 50 + 34 <= 84, succeeds
|
|
345
|
+
token.mint(recipient, 34 ether);
|
|
346
|
+
assertEq(token.balanceOf(recipient), 84 ether);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function test_mint_marginModeNegativePercentage_reverts() public {
|
|
350
|
+
vm.startPrank(deployer, deployer);
|
|
351
|
+
|
|
352
|
+
// set margin (mintable amount = 42 ether * (1 - (-20%)) = 50.4 ether)
|
|
353
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.NegativePercentage, 2000); // -20%
|
|
354
|
+
|
|
355
|
+
// 0 + 50 <= 50.4, succeeds
|
|
356
|
+
token.mint(recipient, 50 ether);
|
|
357
|
+
assertEq(token.balanceOf(recipient), 50 ether);
|
|
358
|
+
|
|
359
|
+
// 50 + 0.5 <= 50.4, reverts
|
|
360
|
+
vm.expectRevert(
|
|
361
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
362
|
+
);
|
|
363
|
+
token.mint(recipient, 0.5 ether);
|
|
364
|
+
|
|
365
|
+
// 50 + 0.4 <= 50.4, succeeds
|
|
366
|
+
token.mint(recipient, 0.4 ether);
|
|
367
|
+
assertEq(token.balanceOf(recipient), 50.4 ether);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function test_mint_marginModeNegativeAbsolute_succeeds() public {
|
|
371
|
+
vm.startPrank(deployer, deployer);
|
|
372
|
+
|
|
373
|
+
// set margin (mintable amount = 42 ether + 2 ether = 44 ether)
|
|
374
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.NegativeAbsolute, 2 ether); // 2 ether
|
|
375
|
+
|
|
376
|
+
// 0 + 30 <= 44, succeeds
|
|
377
|
+
token.mint(recipient, 30 ether);
|
|
378
|
+
assertEq(token.balanceOf(recipient), 30 ether);
|
|
379
|
+
|
|
380
|
+
// 30 + 14 <= 44, succeeds
|
|
381
|
+
token.mint(recipient, 14 ether);
|
|
382
|
+
assertEq(token.balanceOf(recipient), 44 ether);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function test_mint_marginModeNegativeAbsolute_reverts() public {
|
|
386
|
+
vm.startPrank(deployer, deployer);
|
|
387
|
+
|
|
388
|
+
// set margin (mintable amount = 42 ether + 2 ether = 44 ether)
|
|
389
|
+
policy.setReserveMargin(SecureMintPolicy.ReserveMarginMode.NegativeAbsolute, 2 ether); // 2 ether
|
|
390
|
+
|
|
391
|
+
// 0 + 30 <= 44, succeeds
|
|
392
|
+
token.mint(recipient, 30 ether);
|
|
393
|
+
assertEq(token.balanceOf(recipient), 30 ether);
|
|
394
|
+
|
|
395
|
+
// 30 + 14.1 <= 44, reverts
|
|
396
|
+
vm.expectRevert(
|
|
397
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
398
|
+
);
|
|
399
|
+
token.mint(recipient, 14.1 ether);
|
|
400
|
+
|
|
401
|
+
// 30 + 14 <= 44, succeeds
|
|
402
|
+
token.mint(recipient, 14 ether);
|
|
403
|
+
assertEq(token.balanceOf(recipient), 44 ether);
|
|
404
|
+
|
|
405
|
+
// 44 + 1 > 44, reverts
|
|
406
|
+
vm.expectRevert(
|
|
407
|
+
_encodeRejectedRevert(MockToken.mint.selector, address(policy), "mint would exceed available reserves")
|
|
408
|
+
);
|
|
409
|
+
token.mint(recipient, 1 ether);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function test_mint_maxStalenessSeconds_succeeds() public {
|
|
413
|
+
vm.startPrank(deployer, deployer);
|
|
414
|
+
|
|
415
|
+
policy.setMaxStalenessSeconds(600);
|
|
416
|
+
|
|
417
|
+
porFeed.setUpdatedAt(block.timestamp - 600);
|
|
418
|
+
token.mint(recipient, 1 ether);
|
|
419
|
+
assertEq(token.balanceOf(recipient), 1 ether);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function test_mint_maxStalenessSeconds_reverts() public {
|
|
423
|
+
vm.startPrank(deployer, deployer);
|
|
424
|
+
|
|
425
|
+
policy.setMaxStalenessSeconds(600);
|
|
426
|
+
|
|
427
|
+
porFeed.setUpdatedAt(block.timestamp - 601);
|
|
428
|
+
vm.expectRevert(_encodeRejectedRevert(MockToken.mint.selector, address(policy), "reserve data is stale"));
|
|
429
|
+
token.mint(recipient, 1 ether);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function test_mint_negativeReserve_reverts() public {
|
|
433
|
+
vm.startPrank(deployer, deployer);
|
|
434
|
+
|
|
435
|
+
// mocks a malfunctioned that returns a negative value
|
|
436
|
+
porFeed.setPrice(-1 ether);
|
|
437
|
+
|
|
438
|
+
// should revert because the reserve is negative
|
|
439
|
+
vm.expectRevert(_encodeRejectedRevert(MockToken.mint.selector, address(policy), "reserve value is negative"));
|
|
440
|
+
token.mint(recipient, 1 ether);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
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 {VolumePolicy} from "@chainlink/policy-management/policies/VolumePolicy.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 VolumePolicyTest is BaseProxyTest {
|
|
12
|
+
PolicyEngine public policyEngine;
|
|
13
|
+
VolumePolicy public volumePolicy;
|
|
14
|
+
ERC20TransferExtractor public extractor;
|
|
15
|
+
MockToken public 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[](1);
|
|
31
|
+
parameterOutputFormat[0] = extractor.PARAM_AMOUNT();
|
|
32
|
+
|
|
33
|
+
VolumePolicy volumePolicyImpl = new VolumePolicy();
|
|
34
|
+
volumePolicy =
|
|
35
|
+
VolumePolicy(_deployPolicy(address(volumePolicyImpl), address(policyEngine), deployer, abi.encode(100, 200)));
|
|
36
|
+
|
|
37
|
+
policyEngine.setExtractor(MockToken.transfer.selector, address(extractor));
|
|
38
|
+
|
|
39
|
+
policyEngine.addPolicy(address(token), MockToken.transfer.selector, address(volumePolicy), parameterOutputFormat);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function test_policy_initMaxBelowMin_fails() public {
|
|
43
|
+
vm.startPrank(deployer);
|
|
44
|
+
VolumePolicy invalidVolumePolicyImpl = new VolumePolicy();
|
|
45
|
+
vm.expectRevert("maxAmount must be greater than minAmount");
|
|
46
|
+
_deployPolicy(address(invalidVolumePolicyImpl), address(policyEngine), deployer, abi.encode(200, 100));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function test_policy_initMaxEqualsMin_fails() public {
|
|
50
|
+
vm.startPrank(deployer);
|
|
51
|
+
VolumePolicy invalidVolumePolicyImpl = new VolumePolicy();
|
|
52
|
+
vm.expectRevert("maxAmount must be greater than minAmount");
|
|
53
|
+
_deployPolicy(address(invalidVolumePolicyImpl), address(policyEngine), deployer, abi.encode(200, 200));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function test_setMax_succeeds() public {
|
|
57
|
+
vm.startPrank(deployer, deployer);
|
|
58
|
+
|
|
59
|
+
// Set the max amount to 100
|
|
60
|
+
vm.expectEmit();
|
|
61
|
+
emit VolumePolicy.MaxVolumeSet(150);
|
|
62
|
+
volumePolicy.setMax(150);
|
|
63
|
+
vm.assertEq(volumePolicy.getMax(), 150);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function test_setMax_sameAsCurrent_fails() public {
|
|
67
|
+
vm.startPrank(deployer, deployer);
|
|
68
|
+
|
|
69
|
+
// Set the max amount to 100 (sanity check)
|
|
70
|
+
vm.expectEmit();
|
|
71
|
+
emit VolumePolicy.MaxVolumeSet(150);
|
|
72
|
+
volumePolicy.setMax(150);
|
|
73
|
+
vm.assertEq(volumePolicy.getMax(), 150);
|
|
74
|
+
|
|
75
|
+
// Set the max amount to 100 again (revert)
|
|
76
|
+
vm.expectRevert("maxAmount cannot be the same as current maxAmount");
|
|
77
|
+
volumePolicy.setMax(150);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function test_setMin_succeeds() public {
|
|
81
|
+
vm.startPrank(deployer, deployer);
|
|
82
|
+
|
|
83
|
+
// Set the min amount to 1
|
|
84
|
+
vm.expectEmit();
|
|
85
|
+
emit VolumePolicy.MinVolumeSet(1);
|
|
86
|
+
volumePolicy.setMin(1);
|
|
87
|
+
vm.assertEq(volumePolicy.getMin(), 1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function test_setMin_sameAsCurrent_fails() public {
|
|
91
|
+
vm.startPrank(deployer, deployer);
|
|
92
|
+
|
|
93
|
+
// Set the min amount to 1 (sanity check)
|
|
94
|
+
vm.expectEmit();
|
|
95
|
+
emit VolumePolicy.MinVolumeSet(1);
|
|
96
|
+
volumePolicy.setMin(1);
|
|
97
|
+
vm.assertEq(volumePolicy.getMin(), 1);
|
|
98
|
+
|
|
99
|
+
// Set the min amount to 1 again (revert)
|
|
100
|
+
vm.expectRevert("minAmount cannot be the same as current minAmount");
|
|
101
|
+
volumePolicy.setMin(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function test_transfer_extractorWithoutPriceFeedAndAmountBelowMaxVolumePolicy_succeeds() public {
|
|
105
|
+
address recipient = makeAddr("recipient");
|
|
106
|
+
|
|
107
|
+
vm.startPrank(txSender);
|
|
108
|
+
|
|
109
|
+
token.transfer(recipient, 199);
|
|
110
|
+
|
|
111
|
+
assert(token.balanceOf(recipient) == 199);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function test_transfer_extractorWithoutPriceFeedAndAmountAboveMaxVolumePolicy_reverts() public {
|
|
115
|
+
address recipient = makeAddr("recipient");
|
|
116
|
+
|
|
117
|
+
vm.startPrank(txSender);
|
|
118
|
+
|
|
119
|
+
vm.expectRevert(
|
|
120
|
+
_encodeRejectedRevert(MockToken.transfer.selector, address(volumePolicy), "amount outside allowed volume limits")
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
token.transfer(recipient, 201);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function test_transfer_extractorWithoutPriceAndAmountAboveMinVolumePolicy_succeeds() public {
|
|
127
|
+
address recipient = makeAddr("recipient");
|
|
128
|
+
|
|
129
|
+
vm.startPrank(txSender);
|
|
130
|
+
|
|
131
|
+
token.transfer(recipient, 101);
|
|
132
|
+
|
|
133
|
+
assert(token.balanceOf(recipient) == 101);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function test_transfer_extractorWithoutPriceAndAmountBelowMinVolumePolicy_reverts() public {
|
|
137
|
+
address recipient = makeAddr("recipient");
|
|
138
|
+
|
|
139
|
+
vm.startPrank(txSender);
|
|
140
|
+
|
|
141
|
+
vm.expectRevert(
|
|
142
|
+
_encodeRejectedRevert(MockToken.transfer.selector, address(volumePolicy), "amount outside allowed volume limits")
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
token.transfer(recipient, 99);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function test_transfer_setMaxBelowMinVolumePolicy_reverts() public {
|
|
149
|
+
vm.expectRevert("maxAmount must be greater than minAmount");
|
|
150
|
+
volumePolicy.setMax(99);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function test_transfer_setMinAboveMaxVolumePolicy_reverts() public {
|
|
154
|
+
vm.expectRevert("minAmount must be less than maxAmount");
|
|
155
|
+
|
|
156
|
+
volumePolicy.setMin(201);
|
|
157
|
+
}
|
|
158
|
+
}
|