@layerzerolabs/oft-mint-burn-starknet 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/Scarb.lock +190 -0
  2. package/Scarb.toml +3 -0
  3. package/contracts/oft_mint_burn/Scarb.toml +29 -0
  4. package/contracts/oft_mint_burn/src/constants.cairo +11 -0
  5. package/contracts/oft_mint_burn/src/erc20_mint_burn_upgradeable/constants.cairo +17 -0
  6. package/contracts/oft_mint_burn/src/erc20_mint_burn_upgradeable/erc20_mint_burn_upgradeable.cairo +309 -0
  7. package/contracts/oft_mint_burn/src/erc20_mint_burn_upgradeable/interface.cairo +86 -0
  8. package/contracts/oft_mint_burn/src/errors.cairo +39 -0
  9. package/contracts/oft_mint_burn/src/interface.cairo +95 -0
  10. package/contracts/oft_mint_burn/src/lib.cairo +10 -0
  11. package/contracts/oft_mint_burn/src/oft_mint_burn_adapter.cairo +382 -0
  12. package/contracts/oft_mint_burn/tests/fuzzable/contract_address.cairo +21 -0
  13. package/contracts/oft_mint_burn/tests/lib.cairo +8 -0
  14. package/contracts/oft_mint_burn/tests/test_erc20_mint_burn_upgradeable.cairo +621 -0
  15. package/contracts/oft_mint_burn/tests/test_mint_burn_adapter.cairo +194 -0
  16. package/contracts/oft_mint_burn/tests/test_mint_burn_adapter_advanced.cairo +746 -0
  17. package/contracts/oft_mint_burn/tests/utils.cairo +84 -0
  18. package/dist/3UUTAAI4.js +9 -0
  19. package/dist/3UUTAAI4.js.map +1 -0
  20. package/dist/4EGWSIX7.js +18 -0
  21. package/dist/4EGWSIX7.js.map +1 -0
  22. package/dist/54KHZH3Y.cjs +22 -0
  23. package/dist/54KHZH3Y.cjs.map +1 -0
  24. package/dist/5KFUKPR2.js +1033 -0
  25. package/dist/5KFUKPR2.js.map +1 -0
  26. package/dist/5R6WMZVR.cjs +22 -0
  27. package/dist/5R6WMZVR.cjs.map +1 -0
  28. package/dist/CCHLUK5E.cjs +1035 -0
  29. package/dist/CCHLUK5E.cjs.map +1 -0
  30. package/dist/CGU4EJTF.cjs +14 -0
  31. package/dist/CGU4EJTF.cjs.map +1 -0
  32. package/dist/DJIRVXJ3.cjs +1914 -0
  33. package/dist/DJIRVXJ3.cjs.map +1 -0
  34. package/dist/E63KEOR5.cjs +11 -0
  35. package/dist/E63KEOR5.cjs.map +1 -0
  36. package/dist/FL52ASKH.cjs +16 -0
  37. package/dist/FL52ASKH.cjs.map +1 -0
  38. package/dist/GZXOKWQY.js +1303 -0
  39. package/dist/GZXOKWQY.js.map +1 -0
  40. package/dist/IQR2DIUY.cjs +1305 -0
  41. package/dist/IQR2DIUY.cjs.map +1 -0
  42. package/dist/MNNO3GDF.js +14 -0
  43. package/dist/MNNO3GDF.js.map +1 -0
  44. package/dist/NZRVZWHQ.js +1912 -0
  45. package/dist/NZRVZWHQ.js.map +1 -0
  46. package/dist/QYG4SI7W.js +18 -0
  47. package/dist/QYG4SI7W.js.map +1 -0
  48. package/dist/UE6XWQTX.js +12 -0
  49. package/dist/UE6XWQTX.js.map +1 -0
  50. package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.cjs +13 -0
  51. package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.cjs.map +1 -0
  52. package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.d.ts +760 -0
  53. package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.d.ts.map +1 -0
  54. package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.js +4 -0
  55. package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.js.map +1 -0
  56. package/dist/generated/abi/o-f-t-mint-burn-adapter.cjs +13 -0
  57. package/dist/generated/abi/o-f-t-mint-burn-adapter.cjs.map +1 -0
  58. package/dist/generated/abi/o-f-t-mint-burn-adapter.d.ts +1408 -0
  59. package/dist/generated/abi/o-f-t-mint-burn-adapter.d.ts.map +1 -0
  60. package/dist/generated/abi/o-f-t-mint-burn-adapter.js +4 -0
  61. package/dist/generated/abi/o-f-t-mint-burn-adapter.js.map +1 -0
  62. package/dist/generated/abi.cjs +19 -0
  63. package/dist/generated/abi.cjs.map +1 -0
  64. package/dist/generated/abi.d.ts +3 -0
  65. package/dist/generated/abi.d.ts.map +1 -0
  66. package/dist/generated/abi.js +6 -0
  67. package/dist/generated/abi.js.map +1 -0
  68. package/dist/generated/casm.cjs +17 -0
  69. package/dist/generated/casm.cjs.map +1 -0
  70. package/dist/generated/casm.d.ts +3 -0
  71. package/dist/generated/casm.d.ts.map +1 -0
  72. package/dist/generated/casm.js +4 -0
  73. package/dist/generated/casm.js.map +1 -0
  74. package/dist/generated/sierra.cjs +17 -0
  75. package/dist/generated/sierra.cjs.map +1 -0
  76. package/dist/generated/sierra.d.ts +3 -0
  77. package/dist/generated/sierra.d.ts.map +1 -0
  78. package/dist/generated/sierra.js +4 -0
  79. package/dist/generated/sierra.js.map +1 -0
  80. package/dist/generated/verification/index.cjs +14 -0
  81. package/dist/generated/verification/index.cjs.map +1 -0
  82. package/dist/generated/verification/index.d.ts +2 -0
  83. package/dist/generated/verification/index.d.ts.map +1 -0
  84. package/dist/generated/verification/index.js +5 -0
  85. package/dist/generated/verification/index.js.map +1 -0
  86. package/dist/generated/verification/oft_mint_burn.cjs +10 -0
  87. package/dist/generated/verification/oft_mint_burn.cjs.map +1 -0
  88. package/dist/generated/verification/oft_mint_burn.d.ts +4 -0
  89. package/dist/generated/verification/oft_mint_burn.d.ts.map +1 -0
  90. package/dist/generated/verification/oft_mint_burn.js +4 -0
  91. package/dist/generated/verification/oft_mint_burn.js.map +1 -0
  92. package/dist/index.cjs +31 -0
  93. package/dist/index.cjs.map +1 -0
  94. package/dist/index.d.ts +5 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +10 -0
  97. package/dist/index.js.map +1 -0
  98. package/package.json +53 -0
@@ -0,0 +1,621 @@
1
+ //! ERC20MintBurnUpgradeable component tests
2
+
3
+ use core::num::traits::Bounded;
4
+ use oft_mint_burn::erc20_mint_burn_upgradeable::constants::{
5
+ BURNER_ROLE, MINTER_ROLE, PAUSE_MANAGER_ROLE,
6
+ };
7
+ use oft_mint_burn::erc20_mint_burn_upgradeable::interface::{
8
+ IERC20MintBurnUpgradeableDispatcher, IERC20MintBurnUpgradeableDispatcherTrait,
9
+ };
10
+ use oft_mint_burn::interface::{
11
+ IMintableTokenDispatcher, IMintableTokenDispatcherTrait, IMintableTokenSafeDispatcher,
12
+ IMintableTokenSafeDispatcherTrait,
13
+ };
14
+ use openzeppelin::access::accesscontrol::interface::{
15
+ IAccessControlDispatcher, IAccessControlDispatcherTrait,
16
+ };
17
+ use openzeppelin::security::interface::{IPausableDispatcher, IPausableDispatcherTrait};
18
+ use openzeppelin::security::pausable::PausableComponent::Errors as PausableErrors;
19
+ use openzeppelin::token::erc20::interface::{
20
+ IERC20Dispatcher, IERC20DispatcherTrait, IERC20MetadataDispatcher,
21
+ IERC20MetadataDispatcherTrait, IERC20SafeDispatcher, IERC20SafeDispatcherTrait,
22
+ };
23
+ use snforge_std::{ContractClassTrait, DeclareResultTrait, declare, get_class_hash};
24
+ use starknet::{ContractAddress, SyscallResultTrait};
25
+ use starkware_utils_testing::test_utils::{assert_panic_with_felt_error, cheat_caller_address_once};
26
+
27
+ const ADMIN: ContractAddress = 'admin'.try_into().unwrap();
28
+ const MINTER: ContractAddress = 'minter'.try_into().unwrap();
29
+ const BURNER: ContractAddress = 'burner'.try_into().unwrap();
30
+ const USER: ContractAddress = 'user'.try_into().unwrap();
31
+ const PAUSE_MANAGER: ContractAddress = 'pause_manager'.try_into().unwrap();
32
+ const RECIPIENT: ContractAddress = 'recipient'.try_into().unwrap();
33
+ const SHARED_DECIMALS: u8 = 6;
34
+
35
+ const DEFAULT_DECIMALS: u8 = 18;
36
+
37
+ fn deploy_erc20_mint_burn_upgradeable(
38
+ name: ByteArray, symbol: ByteArray, admin: ContractAddress,
39
+ ) -> ContractAddress {
40
+ deploy_erc20_mint_burn_upgradeable_with_decimals(name, symbol, DEFAULT_DECIMALS, admin)
41
+ }
42
+
43
+ fn deploy_erc20_mint_burn_upgradeable_with_decimals(
44
+ name: ByteArray, symbol: ByteArray, decimals: u8, admin: ContractAddress,
45
+ ) -> ContractAddress {
46
+ let contract = declare("ERC20MintBurnUpgradeable").unwrap_syscall().contract_class();
47
+ let mut calldata = array![];
48
+ name.serialize(ref calldata);
49
+ symbol.serialize(ref calldata);
50
+ decimals.serialize(ref calldata);
51
+ admin.serialize(ref calldata);
52
+ let (contract_address, _) = contract.deploy(@calldata).unwrap_syscall();
53
+ contract_address
54
+ }
55
+
56
+ #[test]
57
+ fn test_mint_burn_roles() {
58
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
59
+ let access_control = IAccessControlDispatcher { contract_address: token };
60
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
61
+ let erc20 = IERC20Dispatcher { contract_address: token };
62
+
63
+ // Grant MINTER_ROLE to MINTER
64
+ cheat_caller_address_once(token, ADMIN);
65
+ access_control.grant_role(MINTER_ROLE, MINTER);
66
+
67
+ // Grant BURNER_ROLE to BURNER
68
+ cheat_caller_address_once(token, ADMIN);
69
+ access_control.grant_role(BURNER_ROLE, BURNER);
70
+
71
+ // Test minting
72
+ let mint_amount = 1000_u256;
73
+ cheat_caller_address_once(token, MINTER);
74
+ mintable_burnable.permissioned_mint(USER, mint_amount);
75
+ assert_eq!(erc20.balance_of(USER), mint_amount);
76
+ assert_eq!(erc20.total_supply(), mint_amount);
77
+
78
+ // Test burning
79
+ let burn_amount = 400_u256;
80
+ cheat_caller_address_once(token, BURNER);
81
+ mintable_burnable.permissioned_burn(USER, burn_amount);
82
+ assert_eq!(erc20.balance_of(USER), mint_amount - burn_amount);
83
+ assert_eq!(erc20.total_supply(), mint_amount - burn_amount);
84
+ }
85
+
86
+ #[test]
87
+ #[should_panic(expected: ('Caller is missing role',))]
88
+ fn test_mint_without_role() {
89
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
90
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
91
+
92
+ // Try to mint without MINTER_ROLE
93
+ cheat_caller_address_once(token, USER);
94
+ mintable_burnable.permissioned_mint(USER, 100_u256);
95
+ }
96
+
97
+ #[test]
98
+ #[should_panic(expected: ('Caller is missing role',))]
99
+ fn test_burn_without_role() {
100
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
101
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
102
+ let access_control = IAccessControlDispatcher { contract_address: token };
103
+
104
+ // First mint some tokens
105
+ cheat_caller_address_once(token, ADMIN);
106
+ access_control.grant_role(MINTER_ROLE, MINTER);
107
+ cheat_caller_address_once(token, MINTER);
108
+ mintable_burnable.permissioned_mint(USER, 100_u256);
109
+
110
+ // Try to burn without BURNER_ROLE
111
+ cheat_caller_address_once(token, USER);
112
+ mintable_burnable.permissioned_burn(USER, 50_u256);
113
+ }
114
+
115
+ #[test]
116
+ fn test_multiple_roles() {
117
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
118
+ let access_control = IAccessControlDispatcher { contract_address: token };
119
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
120
+ let erc20 = IERC20Dispatcher { contract_address: token };
121
+
122
+ // Grant both MINTER_ROLE and BURNER_ROLE to the same address
123
+ cheat_caller_address_once(token, ADMIN);
124
+ access_control.grant_role(MINTER_ROLE, USER);
125
+ cheat_caller_address_once(token, ADMIN);
126
+ access_control.grant_role(BURNER_ROLE, USER);
127
+
128
+ // USER can now both mint and burn
129
+ let amount = 1000_u256;
130
+ cheat_caller_address_once(token, USER);
131
+ mintable_burnable.permissioned_mint(USER, amount);
132
+ assert_eq!(erc20.balance_of(USER), amount);
133
+
134
+ cheat_caller_address_once(token, USER);
135
+ mintable_burnable.permissioned_burn(USER, amount / 2);
136
+ assert_eq!(erc20.balance_of(USER), amount / 2);
137
+ }
138
+
139
+ #[test]
140
+ fn test_revoke_role() {
141
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
142
+ let access_control = IAccessControlDispatcher { contract_address: token };
143
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
144
+
145
+ // Grant and then revoke MINTER_ROLE
146
+ cheat_caller_address_once(token, ADMIN);
147
+ access_control.grant_role(MINTER_ROLE, MINTER);
148
+
149
+ // Minting should work
150
+ cheat_caller_address_once(token, MINTER);
151
+ mintable_burnable.permissioned_mint(USER, 100_u256);
152
+
153
+ // Revoke role
154
+ cheat_caller_address_once(token, ADMIN);
155
+ access_control.revoke_role(MINTER_ROLE, MINTER);
156
+
157
+ // Minting should now fail - expect panic
158
+ cheat_caller_address_once(token, MINTER);
159
+ // This will panic with "Caller does not have role"
160
+ // We can't test this here without another should_panic test
161
+ }
162
+
163
+ #[test]
164
+ #[fuzzer(runs: 10)]
165
+ fn test_fuzz_mint_burn(amount: u256) {
166
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
167
+ let access_control = IAccessControlDispatcher { contract_address: token };
168
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
169
+ let erc20 = IERC20Dispatcher { contract_address: token };
170
+
171
+ // Grant roles
172
+ cheat_caller_address_once(token, ADMIN);
173
+ access_control.grant_role(MINTER_ROLE, MINTER);
174
+ cheat_caller_address_once(token, ADMIN);
175
+ access_control.grant_role(BURNER_ROLE, BURNER);
176
+
177
+ // Limit amount to prevent overflow
178
+ let mint_amount = amount % Bounded::<u128>::MAX.into();
179
+
180
+ // Mint
181
+ cheat_caller_address_once(token, MINTER);
182
+ mintable_burnable.permissioned_mint(USER, mint_amount);
183
+ assert_eq!(erc20.balance_of(USER), mint_amount);
184
+ assert_eq!(erc20.total_supply(), mint_amount);
185
+
186
+ // Burn half
187
+ let burn_amount = mint_amount / 2;
188
+ if burn_amount > 0 {
189
+ cheat_caller_address_once(token, BURNER);
190
+ mintable_burnable.permissioned_burn(USER, burn_amount);
191
+ assert_eq!(erc20.balance_of(USER), mint_amount - burn_amount);
192
+ assert_eq!(erc20.total_supply(), mint_amount - burn_amount);
193
+ }
194
+ }
195
+
196
+ ///////////////////
197
+ // Upgrade tests //
198
+ ///////////////////
199
+
200
+ #[test]
201
+ fn test_upgrade_succeeds_when_admin() {
202
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
203
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
204
+
205
+ // Deploy a mock contract to use as new implementation
206
+ let new_class_hash = declare("ERC20MintBurnUpgradeable")
207
+ .unwrap_syscall()
208
+ .contract_class()
209
+ .class_hash;
210
+
211
+ // Upgrade should succeed when called by admin
212
+ cheat_caller_address_once(token, ADMIN);
213
+ upgradeable.upgrade(*new_class_hash);
214
+
215
+ // Verify the upgrade
216
+ assert_eq!(get_class_hash(token), *new_class_hash);
217
+ }
218
+
219
+ #[test]
220
+ #[should_panic(expected: ('Caller is missing role',))]
221
+ fn test_upgrade_fails_when_not_admin() {
222
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
223
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
224
+
225
+ // Deploy a mock contract to use as new implementation
226
+ let new_class_hash = declare("ERC20MintBurnUpgradeable")
227
+ .unwrap_syscall()
228
+ .contract_class()
229
+ .class_hash;
230
+
231
+ // Upgrade should fail when called by non-admin
232
+ cheat_caller_address_once(token, USER);
233
+ upgradeable.upgrade(*new_class_hash);
234
+ }
235
+
236
+ #[test]
237
+ fn test_upgrade_and_call_succeeds_when_admin() {
238
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
239
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
240
+ let erc20 = IERC20Dispatcher { contract_address: token };
241
+ let access_control = IAccessControlDispatcher { contract_address: token };
242
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
243
+
244
+ // First mint some tokens to have a non-zero total supply
245
+ cheat_caller_address_once(token, ADMIN);
246
+ access_control.grant_role(MINTER_ROLE, MINTER);
247
+ cheat_caller_address_once(token, MINTER);
248
+ mintable_burnable.permissioned_mint(USER, 1000_u256);
249
+
250
+ // Deploy same contract class (simulating an upgrade to same version for testing)
251
+ let new_class_hash = declare("ERC20MintBurnUpgradeable")
252
+ .unwrap_syscall()
253
+ .contract_class()
254
+ .class_hash;
255
+
256
+ // Call upgrade_and_call to upgrade and then call total_supply
257
+ cheat_caller_address_once(token, ADMIN);
258
+ let mut result = upgradeable
259
+ .upgrade_and_call(*new_class_hash, selector!("total_supply"), array![].span());
260
+
261
+ // Verify the upgrade happened
262
+ assert_eq!(get_class_hash(token), *new_class_hash);
263
+
264
+ // Verify the call returned the expected result
265
+ let expected_supply = erc20.total_supply();
266
+ let actual_supply: u256 = Serde::deserialize(ref result).unwrap();
267
+ assert_eq!(actual_supply, expected_supply);
268
+ assert_eq!(actual_supply, 1000_u256);
269
+ }
270
+
271
+ #[test]
272
+ #[should_panic(expected: ('Caller is missing role',))]
273
+ fn test_upgrade_and_call_fails_when_not_admin() {
274
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
275
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
276
+
277
+ // Deploy a mock contract to use as new implementation
278
+ let new_class_hash = declare("ERC20MintBurnUpgradeable")
279
+ .unwrap_syscall()
280
+ .contract_class()
281
+ .class_hash;
282
+
283
+ // upgrade_and_call should fail when called by non-admin
284
+ cheat_caller_address_once(token, USER);
285
+ upgradeable.upgrade_and_call(*new_class_hash, selector!("total_supply"), array![].span());
286
+ }
287
+
288
+ #[test]
289
+ fn test_upgrade_preserves_state() {
290
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
291
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
292
+ let access_control = IAccessControlDispatcher { contract_address: token };
293
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
294
+ let erc20 = IERC20Dispatcher { contract_address: token };
295
+
296
+ // Set up some state
297
+ cheat_caller_address_once(token, ADMIN);
298
+ access_control.grant_role(MINTER_ROLE, MINTER);
299
+ cheat_caller_address_once(token, ADMIN);
300
+ access_control.grant_role(BURNER_ROLE, BURNER);
301
+
302
+ // Mint some tokens
303
+ cheat_caller_address_once(token, MINTER);
304
+ mintable_burnable.permissioned_mint(USER, 5000_u256);
305
+
306
+ // Store state before upgrade
307
+ let balance_before = erc20.balance_of(USER);
308
+ let total_supply_before = erc20.total_supply();
309
+ let has_minter_role = access_control.has_role(MINTER_ROLE, MINTER);
310
+ let has_burner_role = access_control.has_role(BURNER_ROLE, BURNER);
311
+
312
+ // Upgrade
313
+ let new_class_hash = declare("ERC20MintBurnUpgradeable")
314
+ .unwrap_syscall()
315
+ .contract_class()
316
+ .class_hash;
317
+ cheat_caller_address_once(token, ADMIN);
318
+ upgradeable.upgrade(*new_class_hash);
319
+
320
+ // Verify state is preserved
321
+ assert_eq!(erc20.balance_of(USER), balance_before);
322
+ assert_eq!(erc20.total_supply(), total_supply_before);
323
+ assert(
324
+ access_control.has_role(MINTER_ROLE, MINTER) == has_minter_role,
325
+ 'Minter role not preserved',
326
+ );
327
+ assert(
328
+ access_control.has_role(BURNER_ROLE, BURNER) == has_burner_role,
329
+ 'Burner role not preserved',
330
+ );
331
+
332
+ // Verify functionality still works after upgrade
333
+ cheat_caller_address_once(token, BURNER);
334
+ mintable_burnable.permissioned_burn(USER, 1000_u256);
335
+ assert_eq!(erc20.balance_of(USER), 4000_u256);
336
+ }
337
+
338
+ /////////////////
339
+ // Pause tests //
340
+ /////////////////
341
+
342
+ #[test]
343
+ fn test_pause_succeeds_when_pause_manager() {
344
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
345
+ let access_control = IAccessControlDispatcher { contract_address: token };
346
+ let pausable = IPausableDispatcher { contract_address: token };
347
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
348
+
349
+ // Grant PAUSE_MANAGER_ROLE
350
+ cheat_caller_address_once(token, ADMIN);
351
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
352
+
353
+ // Pause should succeed
354
+ assert!(!pausable.is_paused(), "Contract should not be paused initially");
355
+ cheat_caller_address_once(token, PAUSE_MANAGER);
356
+ upgradeable.pause();
357
+ assert!(pausable.is_paused(), "Contract should be paused");
358
+ }
359
+
360
+ #[test]
361
+ fn test_unpause_succeeds_when_pause_manager() {
362
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
363
+ let access_control = IAccessControlDispatcher { contract_address: token };
364
+ let pausable = IPausableDispatcher { contract_address: token };
365
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
366
+
367
+ // Grant PAUSE_MANAGER_ROLE
368
+ cheat_caller_address_once(token, ADMIN);
369
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
370
+
371
+ // Pause first
372
+ cheat_caller_address_once(token, PAUSE_MANAGER);
373
+ upgradeable.pause();
374
+ assert!(pausable.is_paused(), "Contract should be paused");
375
+
376
+ // Unpause should succeed
377
+ cheat_caller_address_once(token, PAUSE_MANAGER);
378
+ upgradeable.unpause();
379
+ assert!(!pausable.is_paused(), "Contract should be unpaused");
380
+ }
381
+
382
+ #[test]
383
+ #[should_panic(expected: ('Caller is missing role',))]
384
+ fn test_pause_fails_without_role() {
385
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
386
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
387
+
388
+ // Pause should fail without PAUSE_MANAGER_ROLE
389
+ cheat_caller_address_once(token, USER);
390
+ upgradeable.pause();
391
+ }
392
+
393
+ #[test]
394
+ #[should_panic(expected: ('Caller is missing role',))]
395
+ fn test_unpause_fails_without_role() {
396
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
397
+ let access_control = IAccessControlDispatcher { contract_address: token };
398
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
399
+
400
+ // Grant PAUSE_MANAGER_ROLE to pause first
401
+ cheat_caller_address_once(token, ADMIN);
402
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
403
+ cheat_caller_address_once(token, PAUSE_MANAGER);
404
+ upgradeable.pause();
405
+
406
+ // Unpause should fail without PAUSE_MANAGER_ROLE
407
+ cheat_caller_address_once(token, USER);
408
+ upgradeable.unpause();
409
+ }
410
+
411
+ #[test]
412
+ #[feature("safe_dispatcher")]
413
+ fn test_transfer_fails_when_paused() {
414
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
415
+ let access_control = IAccessControlDispatcher { contract_address: token };
416
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
417
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
418
+ let erc20_safe = IERC20SafeDispatcher { contract_address: token };
419
+
420
+ // Setup: mint tokens to USER
421
+ cheat_caller_address_once(token, ADMIN);
422
+ access_control.grant_role(MINTER_ROLE, MINTER);
423
+ cheat_caller_address_once(token, MINTER);
424
+ mintable_burnable.permissioned_mint(USER, 1000_u256);
425
+
426
+ // Grant PAUSE_MANAGER_ROLE and pause
427
+ cheat_caller_address_once(token, ADMIN);
428
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
429
+ cheat_caller_address_once(token, PAUSE_MANAGER);
430
+ upgradeable.pause();
431
+
432
+ // Transfer should fail when paused
433
+ cheat_caller_address_once(token, USER);
434
+ let res = erc20_safe.transfer(RECIPIENT, 100_u256);
435
+ assert_panic_with_felt_error(res, PausableErrors::PAUSED);
436
+ }
437
+
438
+ #[test]
439
+ #[feature("safe_dispatcher")]
440
+ fn test_transfer_from_fails_when_paused() {
441
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
442
+ let access_control = IAccessControlDispatcher { contract_address: token };
443
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
444
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
445
+ let erc20 = IERC20Dispatcher { contract_address: token };
446
+ let erc20_safe = IERC20SafeDispatcher { contract_address: token };
447
+
448
+ // Setup: mint tokens to USER and approve MINTER to spend
449
+ cheat_caller_address_once(token, ADMIN);
450
+ access_control.grant_role(MINTER_ROLE, MINTER);
451
+ cheat_caller_address_once(token, MINTER);
452
+ mintable_burnable.permissioned_mint(USER, 1000_u256);
453
+ cheat_caller_address_once(token, USER);
454
+ erc20.approve(MINTER, 500_u256);
455
+
456
+ // Grant PAUSE_MANAGER_ROLE and pause
457
+ cheat_caller_address_once(token, ADMIN);
458
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
459
+ cheat_caller_address_once(token, PAUSE_MANAGER);
460
+ upgradeable.pause();
461
+
462
+ // transfer_from should fail when paused
463
+ cheat_caller_address_once(token, MINTER);
464
+ let res = erc20_safe.transfer_from(USER, RECIPIENT, 100_u256);
465
+ assert_panic_with_felt_error(res, PausableErrors::PAUSED);
466
+ }
467
+
468
+ #[test]
469
+ fn test_mint_succeeds_when_paused() {
470
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
471
+ let access_control = IAccessControlDispatcher { contract_address: token };
472
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
473
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
474
+ let erc20 = IERC20Dispatcher { contract_address: token };
475
+ let pausable = IPausableDispatcher { contract_address: token };
476
+
477
+ // Grant MINTER_ROLE
478
+ cheat_caller_address_once(token, ADMIN);
479
+ access_control.grant_role(MINTER_ROLE, MINTER);
480
+
481
+ // Grant PAUSE_MANAGER_ROLE and pause
482
+ cheat_caller_address_once(token, ADMIN);
483
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
484
+ cheat_caller_address_once(token, PAUSE_MANAGER);
485
+ upgradeable.pause();
486
+
487
+ // Verify paused
488
+ assert!(pausable.is_paused(), "Contract should be paused");
489
+
490
+ // Mint should SUCCEED when paused
491
+ cheat_caller_address_once(token, MINTER);
492
+ mintable_burnable.permissioned_mint(USER, 1000_u256);
493
+
494
+ // Verify mint succeeded
495
+ assert_eq!(erc20.balance_of(USER), 1000_u256, "Mint should succeed when paused");
496
+ assert_eq!(erc20.total_supply(), 1000_u256, "Total supply should be updated");
497
+ }
498
+
499
+ #[test]
500
+ #[feature("safe_dispatcher")]
501
+ fn test_burn_fails_when_paused() {
502
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
503
+ let access_control = IAccessControlDispatcher { contract_address: token };
504
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
505
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
506
+ let mintable_burnable_safe = IMintableTokenSafeDispatcher { contract_address: token };
507
+
508
+ // Grant roles and mint tokens first
509
+ cheat_caller_address_once(token, ADMIN);
510
+ access_control.grant_role(MINTER_ROLE, MINTER);
511
+ cheat_caller_address_once(token, ADMIN);
512
+ access_control.grant_role(BURNER_ROLE, BURNER);
513
+
514
+ cheat_caller_address_once(token, MINTER);
515
+ mintable_burnable.permissioned_mint(USER, 1000_u256);
516
+
517
+ // Grant PAUSE_MANAGER_ROLE and pause
518
+ cheat_caller_address_once(token, ADMIN);
519
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
520
+ cheat_caller_address_once(token, PAUSE_MANAGER);
521
+ upgradeable.pause();
522
+
523
+ // Burn should fail when paused
524
+ cheat_caller_address_once(token, BURNER);
525
+ let res = mintable_burnable_safe.permissioned_burn(USER, 500_u256);
526
+ assert_panic_with_felt_error(res, PausableErrors::PAUSED);
527
+ }
528
+
529
+ #[test]
530
+ fn test_transfer_succeeds_after_unpause() {
531
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
532
+ let access_control = IAccessControlDispatcher { contract_address: token };
533
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
534
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
535
+ let erc20 = IERC20Dispatcher { contract_address: token };
536
+
537
+ // Setup: mint tokens to USER
538
+ cheat_caller_address_once(token, ADMIN);
539
+ access_control.grant_role(MINTER_ROLE, MINTER);
540
+ cheat_caller_address_once(token, MINTER);
541
+ mintable_burnable.permissioned_mint(USER, 1000_u256);
542
+
543
+ // Grant PAUSE_MANAGER_ROLE, pause, then unpause
544
+ cheat_caller_address_once(token, ADMIN);
545
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
546
+ cheat_caller_address_once(token, PAUSE_MANAGER);
547
+ upgradeable.pause();
548
+ cheat_caller_address_once(token, PAUSE_MANAGER);
549
+ upgradeable.unpause();
550
+
551
+ // Transfer should succeed after unpause
552
+ cheat_caller_address_once(token, USER);
553
+ erc20.transfer(RECIPIENT, 100_u256);
554
+ assert_eq!(erc20.balance_of(RECIPIENT), 100_u256);
555
+ assert_eq!(erc20.balance_of(USER), 900_u256);
556
+ }
557
+
558
+ #[test]
559
+ fn test_mint_and_burn_succeed_after_unpause() {
560
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
561
+ let access_control = IAccessControlDispatcher { contract_address: token };
562
+ let upgradeable = IERC20MintBurnUpgradeableDispatcher { contract_address: token };
563
+ let mintable_burnable = IMintableTokenDispatcher { contract_address: token };
564
+ let erc20 = IERC20Dispatcher { contract_address: token };
565
+
566
+ // Grant roles
567
+ cheat_caller_address_once(token, ADMIN);
568
+ access_control.grant_role(MINTER_ROLE, MINTER);
569
+ cheat_caller_address_once(token, ADMIN);
570
+ access_control.grant_role(BURNER_ROLE, BURNER);
571
+ cheat_caller_address_once(token, ADMIN);
572
+ access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
573
+
574
+ // Pause then unpause
575
+ cheat_caller_address_once(token, PAUSE_MANAGER);
576
+ upgradeable.pause();
577
+ cheat_caller_address_once(token, PAUSE_MANAGER);
578
+ upgradeable.unpause();
579
+
580
+ // Mint should succeed after unpause
581
+ cheat_caller_address_once(token, MINTER);
582
+ mintable_burnable.permissioned_mint(USER, 1000_u256);
583
+ assert_eq!(erc20.balance_of(USER), 1000_u256);
584
+
585
+ // Burn should succeed after unpause
586
+ cheat_caller_address_once(token, BURNER);
587
+ mintable_burnable.permissioned_burn(USER, 400_u256);
588
+ assert_eq!(erc20.balance_of(USER), 600_u256);
589
+ }
590
+
591
+ ////////////////////
592
+ // Decimals tests //
593
+ ////////////////////
594
+
595
+ #[test]
596
+ fn test_default_decimals() {
597
+ let token = deploy_erc20_mint_burn_upgradeable("TestToken", "TT", ADMIN);
598
+ let metadata = IERC20MetadataDispatcher { contract_address: token };
599
+
600
+ assert_eq!(metadata.decimals(), DEFAULT_DECIMALS);
601
+ assert_eq!(metadata.name(), "TestToken");
602
+ assert_eq!(metadata.symbol(), "TT");
603
+ }
604
+
605
+ #[test]
606
+ fn test_custom_decimals_6() {
607
+ let token = deploy_erc20_mint_burn_upgradeable_with_decimals("USDC", "USDC", 6, ADMIN);
608
+ let metadata = IERC20MetadataDispatcher { contract_address: token };
609
+
610
+ assert_eq!(metadata.decimals(), 6);
611
+ assert_eq!(metadata.name(), "USDC");
612
+ assert_eq!(metadata.symbol(), "USDC");
613
+ }
614
+
615
+ #[test]
616
+ fn test_custom_decimals_8() {
617
+ let token = deploy_erc20_mint_burn_upgradeable_with_decimals("WBTC", "WBTC", 8, ADMIN);
618
+ let metadata = IERC20MetadataDispatcher { contract_address: token };
619
+
620
+ assert_eq!(metadata.decimals(), 8);
621
+ }