@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.
- package/Scarb.lock +190 -0
- package/Scarb.toml +3 -0
- package/contracts/oft_mint_burn/Scarb.toml +29 -0
- package/contracts/oft_mint_burn/src/constants.cairo +11 -0
- package/contracts/oft_mint_burn/src/erc20_mint_burn_upgradeable/constants.cairo +17 -0
- package/contracts/oft_mint_burn/src/erc20_mint_burn_upgradeable/erc20_mint_burn_upgradeable.cairo +309 -0
- package/contracts/oft_mint_burn/src/erc20_mint_burn_upgradeable/interface.cairo +86 -0
- package/contracts/oft_mint_burn/src/errors.cairo +39 -0
- package/contracts/oft_mint_burn/src/interface.cairo +95 -0
- package/contracts/oft_mint_burn/src/lib.cairo +10 -0
- package/contracts/oft_mint_burn/src/oft_mint_burn_adapter.cairo +382 -0
- package/contracts/oft_mint_burn/tests/fuzzable/contract_address.cairo +21 -0
- package/contracts/oft_mint_burn/tests/lib.cairo +8 -0
- package/contracts/oft_mint_burn/tests/test_erc20_mint_burn_upgradeable.cairo +621 -0
- package/contracts/oft_mint_burn/tests/test_mint_burn_adapter.cairo +194 -0
- package/contracts/oft_mint_burn/tests/test_mint_burn_adapter_advanced.cairo +746 -0
- package/contracts/oft_mint_burn/tests/utils.cairo +84 -0
- package/dist/3UUTAAI4.js +9 -0
- package/dist/3UUTAAI4.js.map +1 -0
- package/dist/4EGWSIX7.js +18 -0
- package/dist/4EGWSIX7.js.map +1 -0
- package/dist/54KHZH3Y.cjs +22 -0
- package/dist/54KHZH3Y.cjs.map +1 -0
- package/dist/5KFUKPR2.js +1033 -0
- package/dist/5KFUKPR2.js.map +1 -0
- package/dist/5R6WMZVR.cjs +22 -0
- package/dist/5R6WMZVR.cjs.map +1 -0
- package/dist/CCHLUK5E.cjs +1035 -0
- package/dist/CCHLUK5E.cjs.map +1 -0
- package/dist/CGU4EJTF.cjs +14 -0
- package/dist/CGU4EJTF.cjs.map +1 -0
- package/dist/DJIRVXJ3.cjs +1914 -0
- package/dist/DJIRVXJ3.cjs.map +1 -0
- package/dist/E63KEOR5.cjs +11 -0
- package/dist/E63KEOR5.cjs.map +1 -0
- package/dist/FL52ASKH.cjs +16 -0
- package/dist/FL52ASKH.cjs.map +1 -0
- package/dist/GZXOKWQY.js +1303 -0
- package/dist/GZXOKWQY.js.map +1 -0
- package/dist/IQR2DIUY.cjs +1305 -0
- package/dist/IQR2DIUY.cjs.map +1 -0
- package/dist/MNNO3GDF.js +14 -0
- package/dist/MNNO3GDF.js.map +1 -0
- package/dist/NZRVZWHQ.js +1912 -0
- package/dist/NZRVZWHQ.js.map +1 -0
- package/dist/QYG4SI7W.js +18 -0
- package/dist/QYG4SI7W.js.map +1 -0
- package/dist/UE6XWQTX.js +12 -0
- package/dist/UE6XWQTX.js.map +1 -0
- package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.cjs +13 -0
- package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.cjs.map +1 -0
- package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.d.ts +760 -0
- package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.d.ts.map +1 -0
- package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.js +4 -0
- package/dist/generated/abi/e-r-c20-mint-burn-upgradeable.js.map +1 -0
- package/dist/generated/abi/o-f-t-mint-burn-adapter.cjs +13 -0
- package/dist/generated/abi/o-f-t-mint-burn-adapter.cjs.map +1 -0
- package/dist/generated/abi/o-f-t-mint-burn-adapter.d.ts +1408 -0
- package/dist/generated/abi/o-f-t-mint-burn-adapter.d.ts.map +1 -0
- package/dist/generated/abi/o-f-t-mint-burn-adapter.js +4 -0
- package/dist/generated/abi/o-f-t-mint-burn-adapter.js.map +1 -0
- package/dist/generated/abi.cjs +19 -0
- package/dist/generated/abi.cjs.map +1 -0
- package/dist/generated/abi.d.ts +3 -0
- package/dist/generated/abi.d.ts.map +1 -0
- package/dist/generated/abi.js +6 -0
- package/dist/generated/abi.js.map +1 -0
- package/dist/generated/casm.cjs +17 -0
- package/dist/generated/casm.cjs.map +1 -0
- package/dist/generated/casm.d.ts +3 -0
- package/dist/generated/casm.d.ts.map +1 -0
- package/dist/generated/casm.js +4 -0
- package/dist/generated/casm.js.map +1 -0
- package/dist/generated/sierra.cjs +17 -0
- package/dist/generated/sierra.cjs.map +1 -0
- package/dist/generated/sierra.d.ts +3 -0
- package/dist/generated/sierra.d.ts.map +1 -0
- package/dist/generated/sierra.js +4 -0
- package/dist/generated/sierra.js.map +1 -0
- package/dist/generated/verification/index.cjs +14 -0
- package/dist/generated/verification/index.cjs.map +1 -0
- package/dist/generated/verification/index.d.ts +2 -0
- package/dist/generated/verification/index.d.ts.map +1 -0
- package/dist/generated/verification/index.js +5 -0
- package/dist/generated/verification/index.js.map +1 -0
- package/dist/generated/verification/oft_mint_burn.cjs +10 -0
- package/dist/generated/verification/oft_mint_burn.cjs.map +1 -0
- package/dist/generated/verification/oft_mint_burn.d.ts +4 -0
- package/dist/generated/verification/oft_mint_burn.d.ts.map +1 -0
- package/dist/generated/verification/oft_mint_burn.js +4 -0
- package/dist/generated/verification/oft_mint_burn.js.map +1 -0
- package/dist/index.cjs +31 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- 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
|
+
}
|