@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,746 @@
|
|
|
1
|
+
//! Advanced tests for OFTMintBurnAdapter
|
|
2
|
+
//! Tests for pausable, access control, rate limiting, fees, and upgrades
|
|
3
|
+
|
|
4
|
+
use layerzero::common::structs::packet::Origin;
|
|
5
|
+
use layerzero::endpoint::interfaces::layerzero_receiver::{
|
|
6
|
+
ILayerZeroReceiverDispatcher, ILayerZeroReceiverDispatcherTrait,
|
|
7
|
+
};
|
|
8
|
+
use layerzero::oapps::common::fee::interface::{IFeeDispatcher, IFeeDispatcherTrait};
|
|
9
|
+
use layerzero::oapps::common::rate_limiter::interface::{
|
|
10
|
+
IRateLimiterDispatcher, IRateLimiterDispatcherTrait,
|
|
11
|
+
};
|
|
12
|
+
use layerzero::oapps::common::rate_limiter::structs::{
|
|
13
|
+
RateLimitConfig, RateLimitDirection, RateLimitEnabled,
|
|
14
|
+
};
|
|
15
|
+
use layerzero::oapps::oapp::interface::{IOAppDispatcher, IOAppDispatcherTrait};
|
|
16
|
+
use layerzero::oapps::oft::interface::{IOFTDispatcher, IOFTDispatcherTrait};
|
|
17
|
+
use layerzero::oapps::oft::oft_msg_codec::OFTMsgCodec;
|
|
18
|
+
use layerzero::oapps::oft::structs::SendParam;
|
|
19
|
+
use layerzero::{MessageReceipt, MessagingFee};
|
|
20
|
+
use lz_utils::bytes::{Bytes32, ContractAddressIntoBytes32};
|
|
21
|
+
use oft_mint_burn::constants::{
|
|
22
|
+
FEE_MANAGER_ROLE, PAUSE_MANAGER_ROLE, RATE_LIMITER_MANAGER_ROLE, UPGRADE_MANAGER_ROLE,
|
|
23
|
+
};
|
|
24
|
+
use oft_mint_burn::errors::err_caller_not_owner_or_missing_role;
|
|
25
|
+
use oft_mint_burn::interface::{
|
|
26
|
+
IMintableTokenDispatcher, IMintableTokenDispatcherTrait, IOFTMintBurnAdapterDispatcher,
|
|
27
|
+
IOFTMintBurnAdapterDispatcherTrait, IOFTMintBurnAdapterSafeDispatcher,
|
|
28
|
+
IOFTMintBurnAdapterSafeDispatcherTrait,
|
|
29
|
+
};
|
|
30
|
+
use openzeppelin::access::accesscontrol::interface::{
|
|
31
|
+
IAccessControlDispatcher, IAccessControlDispatcherTrait,
|
|
32
|
+
};
|
|
33
|
+
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
|
|
34
|
+
use snforge_std::{DeclareResultTrait, declare, mock_call, start_cheat_caller_address};
|
|
35
|
+
use starknet::{ContractAddress, SyscallResultTrait};
|
|
36
|
+
use starkware_utils_testing::test_utils::{assert_panic_with_error, cheat_caller_address_once};
|
|
37
|
+
use crate::utils::{
|
|
38
|
+
DIFF_DECIMALS, LZ_ENDPOINT, OFTMintBurnAdapterDeploy, OFT_OWNER, setup_mint_burn_adapter,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const UNAUTHORIZED_USER: ContractAddress = 'unauthorized_user'.try_into().unwrap();
|
|
42
|
+
const FEE_MANAGER: ContractAddress = 'fee_manager'.try_into().unwrap();
|
|
43
|
+
const RATE_LIMITER_MANAGER: ContractAddress = 'rate_limiter_manager'.try_into().unwrap();
|
|
44
|
+
const UPGRADE_MANAGER: ContractAddress = 'upgrade_manager'.try_into().unwrap();
|
|
45
|
+
const PAUSE_MANAGER: ContractAddress = 'pause_manager'.try_into().unwrap();
|
|
46
|
+
|
|
47
|
+
// =============================== Pausable Tests ===============================
|
|
48
|
+
|
|
49
|
+
#[test]
|
|
50
|
+
fn test_pause_and_unpause() {
|
|
51
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
52
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
53
|
+
|
|
54
|
+
// Owner can pause
|
|
55
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
56
|
+
adapter.pause();
|
|
57
|
+
|
|
58
|
+
// Owner can unpause
|
|
59
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
60
|
+
adapter.unpause();
|
|
61
|
+
// If we got here without reverting, pause/unpause works
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
#[feature("safe_dispatcher")]
|
|
66
|
+
fn test_pause_unauthorized() {
|
|
67
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
68
|
+
let adapter = IOFTMintBurnAdapterSafeDispatcher { contract_address: oft_mint_burn_adapter };
|
|
69
|
+
|
|
70
|
+
// Unauthorized user cannot pause (needs owner or PAUSE_MANAGER_ROLE)
|
|
71
|
+
cheat_caller_address_once(oft_mint_burn_adapter, UNAUTHORIZED_USER);
|
|
72
|
+
let result = adapter.pause();
|
|
73
|
+
assert_panic_with_error(result, err_caller_not_owner_or_missing_role(PAUSE_MANAGER_ROLE));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn test_grant_pause_manager_role() {
|
|
78
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
79
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
80
|
+
|
|
81
|
+
// Owner can grant PAUSE_MANAGER_ROLE
|
|
82
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
83
|
+
access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
|
|
84
|
+
assert!(access_control.has_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[test]
|
|
88
|
+
fn test_pause_manager_can_pause_and_unpause() {
|
|
89
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
90
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
91
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
92
|
+
|
|
93
|
+
// Grant PAUSE_MANAGER_ROLE to PAUSE_MANAGER
|
|
94
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
95
|
+
access_control.grant_role(PAUSE_MANAGER_ROLE, PAUSE_MANAGER);
|
|
96
|
+
|
|
97
|
+
// Pause manager can pause
|
|
98
|
+
cheat_caller_address_once(oft_mint_burn_adapter, PAUSE_MANAGER);
|
|
99
|
+
adapter.pause();
|
|
100
|
+
|
|
101
|
+
// Pause manager can unpause
|
|
102
|
+
cheat_caller_address_once(oft_mint_burn_adapter, PAUSE_MANAGER);
|
|
103
|
+
adapter.unpause();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[test]
|
|
107
|
+
#[feature("safe_dispatcher")]
|
|
108
|
+
fn test_unpause_unauthorized() {
|
|
109
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
110
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
111
|
+
let safe_adapter = IOFTMintBurnAdapterSafeDispatcher {
|
|
112
|
+
contract_address: oft_mint_burn_adapter,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// First pause with owner
|
|
116
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
117
|
+
adapter.pause();
|
|
118
|
+
|
|
119
|
+
// Unauthorized user cannot unpause (needs owner or PAUSE_MANAGER_ROLE)
|
|
120
|
+
cheat_caller_address_once(oft_mint_burn_adapter, UNAUTHORIZED_USER);
|
|
121
|
+
let result = safe_adapter.unpause();
|
|
122
|
+
assert_panic_with_error(result, err_caller_not_owner_or_missing_role(PAUSE_MANAGER_ROLE));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#[test]
|
|
126
|
+
#[should_panic(expected: 'Pausable: paused')]
|
|
127
|
+
fn test_send_when_paused() {
|
|
128
|
+
let OFTMintBurnAdapterDeploy {
|
|
129
|
+
oft_mint_burn_adapter, erc20_token, ..,
|
|
130
|
+
} = setup_mint_burn_adapter();
|
|
131
|
+
|
|
132
|
+
let dst_eid = 1_u32;
|
|
133
|
+
let peer = 0x123_u256;
|
|
134
|
+
let amount_ld = 1000_u256 * DIFF_DECIMALS;
|
|
135
|
+
|
|
136
|
+
// Setup peer
|
|
137
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
138
|
+
IOAppDispatcher { contract_address: oft_mint_burn_adapter }.set_peer(dst_eid, peer.into());
|
|
139
|
+
|
|
140
|
+
// Pause contract
|
|
141
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
142
|
+
IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter }.pause();
|
|
143
|
+
|
|
144
|
+
// Try to send - should fail
|
|
145
|
+
let user: ContractAddress = 'user'.try_into().unwrap();
|
|
146
|
+
let send_param = SendParam {
|
|
147
|
+
dst_eid,
|
|
148
|
+
to: peer.into(),
|
|
149
|
+
amount_ld,
|
|
150
|
+
min_amount_ld: amount_ld,
|
|
151
|
+
extra_options: Default::default(),
|
|
152
|
+
compose_msg: Default::default(),
|
|
153
|
+
oft_cmd: Default::default(),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Mint tokens to user
|
|
157
|
+
cheat_caller_address_once(erc20_token, oft_mint_burn_adapter);
|
|
158
|
+
IMintableTokenDispatcher { contract_address: erc20_token }.permissioned_mint(user, amount_ld);
|
|
159
|
+
|
|
160
|
+
// Mock endpoint calls
|
|
161
|
+
mock_call(LZ_ENDPOINT, selector!("quote"), MessagingFee { native_fee: 0, lz_token_fee: 0 }, 1);
|
|
162
|
+
mock_call(
|
|
163
|
+
LZ_ENDPOINT,
|
|
164
|
+
selector!("send"),
|
|
165
|
+
MessageReceipt { guid: Bytes32 { value: 1 }, nonce: 1, payees: array![] },
|
|
166
|
+
1,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// This should panic with "Pausable: paused"
|
|
170
|
+
cheat_caller_address_once(oft_mint_burn_adapter, user);
|
|
171
|
+
IOFTDispatcher { contract_address: oft_mint_burn_adapter }
|
|
172
|
+
.send(send_param, MessagingFee { native_fee: 0, lz_token_fee: 0 }, user);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
#[should_panic(expected: 'Pausable: paused')]
|
|
177
|
+
fn test_receive_when_paused() {
|
|
178
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
179
|
+
|
|
180
|
+
let src_eid = 1_u32;
|
|
181
|
+
let sender = 0x123_u256;
|
|
182
|
+
let receiver: ContractAddress = 'receiver'.try_into().unwrap();
|
|
183
|
+
let amount_sd = 1000_u64;
|
|
184
|
+
|
|
185
|
+
// Setup peer
|
|
186
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
187
|
+
IOAppDispatcher { contract_address: oft_mint_burn_adapter }.set_peer(src_eid, sender.into());
|
|
188
|
+
|
|
189
|
+
// Pause contract
|
|
190
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
191
|
+
IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter }.pause();
|
|
192
|
+
|
|
193
|
+
// Try to receive - should fail
|
|
194
|
+
let receiver_bytes32: Bytes32 = receiver.into();
|
|
195
|
+
let (message, _) = OFTMsgCodec::encode(receiver_bytes32, amount_sd, @"");
|
|
196
|
+
let origin = Origin { src_eid, sender: sender.into(), nonce: 1 };
|
|
197
|
+
|
|
198
|
+
// This should panic with "Pausable: paused"
|
|
199
|
+
start_cheat_caller_address(oft_mint_burn_adapter, LZ_ENDPOINT);
|
|
200
|
+
ILayerZeroReceiverDispatcher { contract_address: oft_mint_burn_adapter }
|
|
201
|
+
.lz_receive(origin, Bytes32 { value: 1 }, message, receiver, Default::default(), 0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// =============================== Access Control Tests ===============================
|
|
205
|
+
|
|
206
|
+
#[test]
|
|
207
|
+
fn test_grant_and_revoke_fee_manager_role() {
|
|
208
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
209
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
210
|
+
|
|
211
|
+
// Owner can grant FEE_MANAGER_ROLE
|
|
212
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
213
|
+
access_control.grant_role(FEE_MANAGER_ROLE, FEE_MANAGER);
|
|
214
|
+
assert!(access_control.has_role(FEE_MANAGER_ROLE, FEE_MANAGER));
|
|
215
|
+
|
|
216
|
+
// Owner can revoke FEE_MANAGER_ROLE
|
|
217
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
218
|
+
access_control.revoke_role(FEE_MANAGER_ROLE, FEE_MANAGER);
|
|
219
|
+
assert!(!access_control.has_role(FEE_MANAGER_ROLE, FEE_MANAGER));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn test_grant_rate_limiter_manager_role() {
|
|
224
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
225
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
226
|
+
|
|
227
|
+
// Owner can grant RATE_LIMITER_MANAGER_ROLE
|
|
228
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
229
|
+
access_control.grant_role(RATE_LIMITER_MANAGER_ROLE, RATE_LIMITER_MANAGER);
|
|
230
|
+
assert!(access_control.has_role(RATE_LIMITER_MANAGER_ROLE, RATE_LIMITER_MANAGER));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[test]
|
|
234
|
+
fn test_grant_upgrade_manager_role() {
|
|
235
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
236
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
237
|
+
|
|
238
|
+
// Owner can grant UPGRADE_MANAGER_ROLE
|
|
239
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
240
|
+
access_control.grant_role(UPGRADE_MANAGER_ROLE, UPGRADE_MANAGER);
|
|
241
|
+
assert!(access_control.has_role(UPGRADE_MANAGER_ROLE, UPGRADE_MANAGER));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#[test]
|
|
245
|
+
fn test_rate_limiter_manager_can_set_rate_limits() {
|
|
246
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
247
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
248
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
249
|
+
|
|
250
|
+
// Grant RATE_LIMITER_MANAGER_ROLE
|
|
251
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
252
|
+
access_control.grant_role(RATE_LIMITER_MANAGER_ROLE, RATE_LIMITER_MANAGER);
|
|
253
|
+
|
|
254
|
+
// Rate limiter manager can set rate limits
|
|
255
|
+
let dst_eid = 1_u32;
|
|
256
|
+
let rate_limits = array![
|
|
257
|
+
RateLimitConfig { dst_eid, limit: 1000000, window: 3600 } // 1M per hour
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
cheat_caller_address_once(oft_mint_burn_adapter, RATE_LIMITER_MANAGER);
|
|
261
|
+
adapter.set_rate_limits(rate_limits, RateLimitDirection::Outbound);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
#[test]
|
|
265
|
+
fn test_owner_can_set_rate_limits_without_role() {
|
|
266
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
267
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
268
|
+
|
|
269
|
+
// Owner can set rate limits even without explicit role
|
|
270
|
+
let dst_eid = 1_u32;
|
|
271
|
+
let rate_limits = array![RateLimitConfig { dst_eid, limit: 1000000, window: 3600 }];
|
|
272
|
+
|
|
273
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
274
|
+
adapter.set_rate_limits(rate_limits, RateLimitDirection::Outbound);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[test]
|
|
278
|
+
#[feature("safe_dispatcher")]
|
|
279
|
+
fn test_unauthorized_cannot_set_rate_limits() {
|
|
280
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
281
|
+
let adapter = IOFTMintBurnAdapterSafeDispatcher { contract_address: oft_mint_burn_adapter };
|
|
282
|
+
|
|
283
|
+
let dst_eid = 1_u32;
|
|
284
|
+
let rate_limits = array![RateLimitConfig { dst_eid, limit: 1000000, window: 3600 }];
|
|
285
|
+
|
|
286
|
+
// Unauthorized user cannot set rate limits
|
|
287
|
+
cheat_caller_address_once(oft_mint_burn_adapter, UNAUTHORIZED_USER);
|
|
288
|
+
let result = adapter.set_rate_limits(rate_limits, RateLimitDirection::Outbound);
|
|
289
|
+
assert_panic_with_error(
|
|
290
|
+
result, err_caller_not_owner_or_missing_role(RATE_LIMITER_MANAGER_ROLE),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// =============================== Fee Management Tests ===============================
|
|
295
|
+
|
|
296
|
+
#[test]
|
|
297
|
+
fn test_fee_balance_initially_zero() {
|
|
298
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
299
|
+
|
|
300
|
+
// Check initial fee balance
|
|
301
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
302
|
+
assert_eq!(adapter.fee_balance(), 0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
#[should_panic]
|
|
307
|
+
fn test_withdraw_fees_when_zero() {
|
|
308
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
309
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
310
|
+
|
|
311
|
+
let recipient: ContractAddress = 'recipient'.try_into().unwrap();
|
|
312
|
+
|
|
313
|
+
// Should fail because fee_balance is 0
|
|
314
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
315
|
+
adapter.withdraw_fees(recipient);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#[test]
|
|
319
|
+
#[feature("safe_dispatcher")]
|
|
320
|
+
fn test_unauthorized_cannot_withdraw_fees() {
|
|
321
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
322
|
+
let adapter = IOFTMintBurnAdapterSafeDispatcher { contract_address: oft_mint_burn_adapter };
|
|
323
|
+
|
|
324
|
+
let recipient: ContractAddress = 'recipient'.try_into().unwrap();
|
|
325
|
+
|
|
326
|
+
// Unauthorized user cannot withdraw fees
|
|
327
|
+
cheat_caller_address_once(oft_mint_burn_adapter, UNAUTHORIZED_USER);
|
|
328
|
+
let result = adapter.withdraw_fees(recipient);
|
|
329
|
+
assert_panic_with_error(result, err_caller_not_owner_or_missing_role(FEE_MANAGER_ROLE));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#[test]
|
|
333
|
+
fn test_fee_manager_role_grant() {
|
|
334
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
335
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
336
|
+
|
|
337
|
+
// Grant FEE_MANAGER_ROLE
|
|
338
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
339
|
+
access_control.grant_role(FEE_MANAGER_ROLE, FEE_MANAGER);
|
|
340
|
+
|
|
341
|
+
// Verify role was granted
|
|
342
|
+
assert!(access_control.has_role(FEE_MANAGER_ROLE, FEE_MANAGER));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#[test]
|
|
346
|
+
fn test_withdraw_fees_successfully() {
|
|
347
|
+
let OFTMintBurnAdapterDeploy {
|
|
348
|
+
oft_mint_burn_adapter, erc20_token, ..,
|
|
349
|
+
} = setup_mint_burn_adapter();
|
|
350
|
+
|
|
351
|
+
let dst_eid = 1_u32;
|
|
352
|
+
let peer = 0x123_u256;
|
|
353
|
+
let user: ContractAddress = 'user'.try_into().unwrap();
|
|
354
|
+
let fee_recipient: ContractAddress = 'fee_recipient'.try_into().unwrap();
|
|
355
|
+
let amount_ld = 10000_u256 * DIFF_DECIMALS; // 10000 tokens
|
|
356
|
+
|
|
357
|
+
// Setup peer
|
|
358
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
359
|
+
IOAppDispatcher { contract_address: oft_mint_burn_adapter }.set_peer(dst_eid, peer.into());
|
|
360
|
+
|
|
361
|
+
// Set fee BPS (1% = 100 bps)
|
|
362
|
+
let fee_dispatcher = IFeeDispatcher { contract_address: oft_mint_burn_adapter };
|
|
363
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
364
|
+
fee_dispatcher.set_fee_bps(dst_eid, 100, true); // 1% fee
|
|
365
|
+
|
|
366
|
+
// Mint tokens to user
|
|
367
|
+
cheat_caller_address_once(erc20_token, oft_mint_burn_adapter);
|
|
368
|
+
IMintableTokenDispatcher { contract_address: erc20_token }.permissioned_mint(user, amount_ld);
|
|
369
|
+
|
|
370
|
+
// Mock endpoint calls
|
|
371
|
+
mock_call(LZ_ENDPOINT, selector!("quote"), MessagingFee { native_fee: 0, lz_token_fee: 0 }, 1);
|
|
372
|
+
mock_call(
|
|
373
|
+
LZ_ENDPOINT,
|
|
374
|
+
selector!("send"),
|
|
375
|
+
MessageReceipt { guid: Bytes32 { value: 1 }, nonce: 1, payees: array![] },
|
|
376
|
+
1,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Send tokens (this should collect fees)
|
|
380
|
+
let send_param = SendParam {
|
|
381
|
+
dst_eid,
|
|
382
|
+
to: peer.into(),
|
|
383
|
+
amount_ld,
|
|
384
|
+
min_amount_ld: 0, // Allow slippage for fee
|
|
385
|
+
extra_options: Default::default(),
|
|
386
|
+
compose_msg: Default::default(),
|
|
387
|
+
oft_cmd: Default::default(),
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
cheat_caller_address_once(oft_mint_burn_adapter, user);
|
|
391
|
+
IOFTDispatcher { contract_address: oft_mint_burn_adapter }
|
|
392
|
+
.send(send_param, MessagingFee { native_fee: 0, lz_token_fee: 0 }, user);
|
|
393
|
+
|
|
394
|
+
// Check that fees were collected
|
|
395
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
396
|
+
let fee_balance = adapter.fee_balance();
|
|
397
|
+
assert!(fee_balance > 0, "Fee balance should be greater than 0");
|
|
398
|
+
|
|
399
|
+
// Check initial balance of fee recipient
|
|
400
|
+
let recipient_balance_before = IERC20Dispatcher { contract_address: erc20_token }
|
|
401
|
+
.balance_of(fee_recipient);
|
|
402
|
+
assert_eq!(recipient_balance_before, 0);
|
|
403
|
+
|
|
404
|
+
// Withdraw fees
|
|
405
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
406
|
+
adapter.withdraw_fees(fee_recipient);
|
|
407
|
+
|
|
408
|
+
// Check that fee balance is now 0
|
|
409
|
+
assert_eq!(adapter.fee_balance(), 0);
|
|
410
|
+
|
|
411
|
+
// Check that recipient received the fees
|
|
412
|
+
let recipient_balance_after = IERC20Dispatcher { contract_address: erc20_token }
|
|
413
|
+
.balance_of(fee_recipient);
|
|
414
|
+
assert_eq!(recipient_balance_after, fee_balance);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#[test]
|
|
418
|
+
fn test_fee_manager_can_withdraw_fees() {
|
|
419
|
+
let OFTMintBurnAdapterDeploy {
|
|
420
|
+
oft_mint_burn_adapter, erc20_token, ..,
|
|
421
|
+
} = setup_mint_burn_adapter();
|
|
422
|
+
|
|
423
|
+
let dst_eid = 1_u32;
|
|
424
|
+
let peer = 0x123_u256;
|
|
425
|
+
let user: ContractAddress = 'user'.try_into().unwrap();
|
|
426
|
+
let fee_recipient: ContractAddress = 'fee_recipient'.try_into().unwrap();
|
|
427
|
+
let amount_ld = 10000_u256 * DIFF_DECIMALS;
|
|
428
|
+
|
|
429
|
+
// Setup peer
|
|
430
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
431
|
+
IOAppDispatcher { contract_address: oft_mint_burn_adapter }.set_peer(dst_eid, peer.into());
|
|
432
|
+
|
|
433
|
+
// Set fee BPS (1% = 100 bps)
|
|
434
|
+
let fee_dispatcher = IFeeDispatcher { contract_address: oft_mint_burn_adapter };
|
|
435
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
436
|
+
fee_dispatcher.set_fee_bps(dst_eid, 100, true);
|
|
437
|
+
|
|
438
|
+
// Grant FEE_MANAGER_ROLE
|
|
439
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
440
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
441
|
+
access_control.grant_role(FEE_MANAGER_ROLE, FEE_MANAGER);
|
|
442
|
+
|
|
443
|
+
// Mint tokens to user
|
|
444
|
+
cheat_caller_address_once(erc20_token, oft_mint_burn_adapter);
|
|
445
|
+
IMintableTokenDispatcher { contract_address: erc20_token }.permissioned_mint(user, amount_ld);
|
|
446
|
+
|
|
447
|
+
// Mock endpoint calls
|
|
448
|
+
mock_call(LZ_ENDPOINT, selector!("quote"), MessagingFee { native_fee: 0, lz_token_fee: 0 }, 1);
|
|
449
|
+
mock_call(
|
|
450
|
+
LZ_ENDPOINT,
|
|
451
|
+
selector!("send"),
|
|
452
|
+
MessageReceipt { guid: Bytes32 { value: 1 }, nonce: 1, payees: array![] },
|
|
453
|
+
1,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
// Send tokens to collect fees
|
|
457
|
+
let send_param = SendParam {
|
|
458
|
+
dst_eid,
|
|
459
|
+
to: peer.into(),
|
|
460
|
+
amount_ld,
|
|
461
|
+
min_amount_ld: 0,
|
|
462
|
+
extra_options: Default::default(),
|
|
463
|
+
compose_msg: Default::default(),
|
|
464
|
+
oft_cmd: Default::default(),
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
cheat_caller_address_once(oft_mint_burn_adapter, user);
|
|
468
|
+
IOFTDispatcher { contract_address: oft_mint_burn_adapter }
|
|
469
|
+
.send(send_param, MessagingFee { native_fee: 0, lz_token_fee: 0 }, user);
|
|
470
|
+
|
|
471
|
+
// Fee manager can withdraw fees
|
|
472
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
473
|
+
let fee_balance = adapter.fee_balance();
|
|
474
|
+
|
|
475
|
+
cheat_caller_address_once(oft_mint_burn_adapter, FEE_MANAGER);
|
|
476
|
+
adapter.withdraw_fees(fee_recipient);
|
|
477
|
+
|
|
478
|
+
// Check that fee balance is now 0 and recipient received fees
|
|
479
|
+
assert_eq!(adapter.fee_balance(), 0);
|
|
480
|
+
let recipient_balance = IERC20Dispatcher { contract_address: erc20_token }
|
|
481
|
+
.balance_of(fee_recipient);
|
|
482
|
+
assert_eq!(recipient_balance, fee_balance);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// =============================== Rate Limiting Tests ===============================
|
|
486
|
+
|
|
487
|
+
#[test]
|
|
488
|
+
fn test_rate_limit_enable_and_disable() {
|
|
489
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
490
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
491
|
+
let rate_limiter = IRateLimiterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
492
|
+
|
|
493
|
+
// Initially both should be disabled
|
|
494
|
+
let enabled = rate_limiter.get_rate_limit_enabled();
|
|
495
|
+
assert!(!enabled.is_outbound_enabled);
|
|
496
|
+
assert!(!enabled.is_inbound_enabled);
|
|
497
|
+
|
|
498
|
+
// Enable outbound rate limiting
|
|
499
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
500
|
+
adapter
|
|
501
|
+
.set_rate_limits_enabled(
|
|
502
|
+
RateLimitEnabled { is_outbound_enabled: true, is_inbound_enabled: false },
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
let enabled = rate_limiter.get_rate_limit_enabled();
|
|
506
|
+
assert!(enabled.is_outbound_enabled);
|
|
507
|
+
assert!(!enabled.is_inbound_enabled);
|
|
508
|
+
|
|
509
|
+
// Enable both
|
|
510
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
511
|
+
adapter
|
|
512
|
+
.set_rate_limits_enabled(
|
|
513
|
+
RateLimitEnabled { is_outbound_enabled: true, is_inbound_enabled: true },
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
let enabled = rate_limiter.get_rate_limit_enabled();
|
|
517
|
+
assert!(enabled.is_outbound_enabled);
|
|
518
|
+
assert!(enabled.is_inbound_enabled);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
#[test]
|
|
522
|
+
#[feature("safe_dispatcher")]
|
|
523
|
+
fn test_unauthorized_cannot_enable_rate_limits() {
|
|
524
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
525
|
+
let adapter = IOFTMintBurnAdapterSafeDispatcher { contract_address: oft_mint_burn_adapter };
|
|
526
|
+
|
|
527
|
+
// Unauthorized user cannot enable rate limits
|
|
528
|
+
cheat_caller_address_once(oft_mint_burn_adapter, UNAUTHORIZED_USER);
|
|
529
|
+
let result = adapter
|
|
530
|
+
.set_rate_limits_enabled(
|
|
531
|
+
RateLimitEnabled { is_outbound_enabled: true, is_inbound_enabled: true },
|
|
532
|
+
);
|
|
533
|
+
assert_panic_with_error(
|
|
534
|
+
result, err_caller_not_owner_or_missing_role(RATE_LIMITER_MANAGER_ROLE),
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
#[test]
|
|
539
|
+
fn test_set_outbound_rate_limit() {
|
|
540
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
541
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
542
|
+
let rate_limiter = IRateLimiterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
543
|
+
|
|
544
|
+
let dst_eid = 1_u32;
|
|
545
|
+
let limit = 1000000_u128;
|
|
546
|
+
let window = 3600_u64;
|
|
547
|
+
|
|
548
|
+
// Set outbound rate limit
|
|
549
|
+
let rate_limits = array![RateLimitConfig { dst_eid, limit, window }];
|
|
550
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
551
|
+
adapter.set_rate_limits(rate_limits, RateLimitDirection::Outbound);
|
|
552
|
+
|
|
553
|
+
// Check the rate limit was set
|
|
554
|
+
let rate_limit = rate_limiter.get_outbound_rate_limit(dst_eid);
|
|
555
|
+
assert_eq!(rate_limit.limit, limit);
|
|
556
|
+
assert_eq!(rate_limit.window, window);
|
|
557
|
+
assert_eq!(rate_limit.amount_in_flight, 0);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
#[test]
|
|
561
|
+
fn test_set_inbound_rate_limit() {
|
|
562
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
563
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
564
|
+
let rate_limiter = IRateLimiterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
565
|
+
|
|
566
|
+
let src_eid = 1_u32;
|
|
567
|
+
let limit = 500000_u128;
|
|
568
|
+
let window = 1800_u64;
|
|
569
|
+
|
|
570
|
+
// Set inbound rate limit
|
|
571
|
+
let rate_limits = array![RateLimitConfig { dst_eid: src_eid, limit, window }];
|
|
572
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
573
|
+
adapter.set_rate_limits(rate_limits, RateLimitDirection::Inbound);
|
|
574
|
+
|
|
575
|
+
// Check the rate limit was set
|
|
576
|
+
let rate_limit = rate_limiter.get_inbound_rate_limit(src_eid);
|
|
577
|
+
assert_eq!(rate_limit.limit, limit);
|
|
578
|
+
assert_eq!(rate_limit.window, window);
|
|
579
|
+
assert_eq!(rate_limit.amount_in_flight, 0);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// =============================== Upgrade Tests ===============================
|
|
583
|
+
|
|
584
|
+
#[test]
|
|
585
|
+
fn test_upgrade_manager_can_upgrade() {
|
|
586
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
587
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
588
|
+
let access_control = IAccessControlDispatcher { contract_address: oft_mint_burn_adapter };
|
|
589
|
+
|
|
590
|
+
// Grant UPGRADE_MANAGER_ROLE
|
|
591
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
592
|
+
access_control.grant_role(UPGRADE_MANAGER_ROLE, UPGRADE_MANAGER);
|
|
593
|
+
|
|
594
|
+
// Declare a new class (we'll use the same contract class for testing)
|
|
595
|
+
let new_class = declare("OFTMintBurnAdapter").unwrap_syscall();
|
|
596
|
+
let new_class_hash = *new_class.contract_class().class_hash;
|
|
597
|
+
|
|
598
|
+
// Upgrade manager can upgrade
|
|
599
|
+
cheat_caller_address_once(oft_mint_burn_adapter, UPGRADE_MANAGER);
|
|
600
|
+
adapter.upgrade(new_class_hash);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
#[test]
|
|
604
|
+
fn test_owner_can_upgrade() {
|
|
605
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
606
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
607
|
+
|
|
608
|
+
// Declare a new class
|
|
609
|
+
let new_class = declare("OFTMintBurnAdapter").unwrap_syscall();
|
|
610
|
+
let new_class_hash = *new_class.contract_class().class_hash;
|
|
611
|
+
|
|
612
|
+
// Owner can upgrade even without explicit role
|
|
613
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
614
|
+
adapter.upgrade(new_class_hash);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
#[test]
|
|
618
|
+
#[feature("safe_dispatcher")]
|
|
619
|
+
fn test_unauthorized_cannot_upgrade() {
|
|
620
|
+
let OFTMintBurnAdapterDeploy { oft_mint_burn_adapter, .. } = setup_mint_burn_adapter();
|
|
621
|
+
let adapter = IOFTMintBurnAdapterSafeDispatcher { contract_address: oft_mint_burn_adapter };
|
|
622
|
+
|
|
623
|
+
// Declare a new class
|
|
624
|
+
let new_class = declare("OFTMintBurnAdapter").unwrap_syscall();
|
|
625
|
+
let new_class_hash = *new_class.contract_class().class_hash;
|
|
626
|
+
|
|
627
|
+
// Unauthorized user cannot upgrade
|
|
628
|
+
cheat_caller_address_once(oft_mint_burn_adapter, UNAUTHORIZED_USER);
|
|
629
|
+
let result = adapter.upgrade(new_class_hash);
|
|
630
|
+
assert_panic_with_error(result, err_caller_not_owner_or_missing_role(UPGRADE_MANAGER_ROLE));
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// =============================== Integration Tests ===============================
|
|
634
|
+
|
|
635
|
+
#[test]
|
|
636
|
+
fn test_send_with_pausable_and_rate_limit() {
|
|
637
|
+
let OFTMintBurnAdapterDeploy {
|
|
638
|
+
oft_mint_burn_adapter, erc20_token, ..,
|
|
639
|
+
} = setup_mint_burn_adapter();
|
|
640
|
+
|
|
641
|
+
let dst_eid = 1_u32;
|
|
642
|
+
let peer = 0x123_u256;
|
|
643
|
+
let user: ContractAddress = 'user'.try_into().unwrap();
|
|
644
|
+
let amount_ld = 100_u256 * DIFF_DECIMALS;
|
|
645
|
+
|
|
646
|
+
// Setup peer
|
|
647
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
648
|
+
IOAppDispatcher { contract_address: oft_mint_burn_adapter }.set_peer(dst_eid, peer.into());
|
|
649
|
+
|
|
650
|
+
// Set rate limit
|
|
651
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
652
|
+
let rate_limits = array![
|
|
653
|
+
RateLimitConfig {
|
|
654
|
+
dst_eid, limit: 1000_u128 * DIFF_DECIMALS.try_into().unwrap(), window: 3600,
|
|
655
|
+
},
|
|
656
|
+
];
|
|
657
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
658
|
+
adapter.set_rate_limits(rate_limits, RateLimitDirection::Outbound);
|
|
659
|
+
|
|
660
|
+
// Enable outbound rate limiting
|
|
661
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
662
|
+
adapter
|
|
663
|
+
.set_rate_limits_enabled(
|
|
664
|
+
RateLimitEnabled { is_outbound_enabled: true, is_inbound_enabled: false },
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
// Mint tokens to user
|
|
668
|
+
cheat_caller_address_once(erc20_token, oft_mint_burn_adapter);
|
|
669
|
+
IMintableTokenDispatcher { contract_address: erc20_token }.permissioned_mint(user, amount_ld);
|
|
670
|
+
|
|
671
|
+
// Mock endpoint calls
|
|
672
|
+
mock_call(LZ_ENDPOINT, selector!("quote"), MessagingFee { native_fee: 0, lz_token_fee: 0 }, 1);
|
|
673
|
+
mock_call(
|
|
674
|
+
LZ_ENDPOINT,
|
|
675
|
+
selector!("send"),
|
|
676
|
+
MessageReceipt { guid: Bytes32 { value: 1 }, nonce: 1, payees: array![] },
|
|
677
|
+
1,
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
// Send tokens (should succeed as it's under rate limit)
|
|
681
|
+
let send_param = SendParam {
|
|
682
|
+
dst_eid,
|
|
683
|
+
to: peer.into(),
|
|
684
|
+
amount_ld,
|
|
685
|
+
min_amount_ld: amount_ld,
|
|
686
|
+
extra_options: Default::default(),
|
|
687
|
+
compose_msg: Default::default(),
|
|
688
|
+
oft_cmd: Default::default(),
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
cheat_caller_address_once(oft_mint_burn_adapter, user);
|
|
692
|
+
IOFTDispatcher { contract_address: oft_mint_burn_adapter }
|
|
693
|
+
.send(send_param, MessagingFee { native_fee: 0, lz_token_fee: 0 }, user);
|
|
694
|
+
|
|
695
|
+
// Verify tokens were burned
|
|
696
|
+
let balance = IERC20Dispatcher { contract_address: erc20_token }.balance_of(user);
|
|
697
|
+
assert_eq!(balance, 0);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
#[test]
|
|
701
|
+
fn test_receive_with_pausable_and_rate_limit() {
|
|
702
|
+
let OFTMintBurnAdapterDeploy {
|
|
703
|
+
oft_mint_burn_adapter, erc20_token, ..,
|
|
704
|
+
} = setup_mint_burn_adapter();
|
|
705
|
+
|
|
706
|
+
let src_eid = 1_u32;
|
|
707
|
+
let sender = 0x123_u256;
|
|
708
|
+
let receiver: ContractAddress = 'receiver'.try_into().unwrap();
|
|
709
|
+
let amount_sd = 100_u64;
|
|
710
|
+
let amount_ld = amount_sd.into() * DIFF_DECIMALS;
|
|
711
|
+
|
|
712
|
+
// Setup peer
|
|
713
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
714
|
+
IOAppDispatcher { contract_address: oft_mint_burn_adapter }.set_peer(src_eid, sender.into());
|
|
715
|
+
|
|
716
|
+
// Set inbound rate limit
|
|
717
|
+
let adapter = IOFTMintBurnAdapterDispatcher { contract_address: oft_mint_burn_adapter };
|
|
718
|
+
let rate_limits = array![
|
|
719
|
+
RateLimitConfig {
|
|
720
|
+
dst_eid: src_eid, limit: 1000_u128 * DIFF_DECIMALS.try_into().unwrap(), window: 3600,
|
|
721
|
+
},
|
|
722
|
+
];
|
|
723
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
724
|
+
adapter.set_rate_limits(rate_limits, RateLimitDirection::Inbound);
|
|
725
|
+
|
|
726
|
+
// Enable inbound rate limiting
|
|
727
|
+
cheat_caller_address_once(oft_mint_burn_adapter, OFT_OWNER);
|
|
728
|
+
adapter
|
|
729
|
+
.set_rate_limits_enabled(
|
|
730
|
+
RateLimitEnabled { is_outbound_enabled: false, is_inbound_enabled: true },
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
// Receive tokens (should succeed as it's under rate limit)
|
|
734
|
+
let receiver_bytes32: Bytes32 = receiver.into();
|
|
735
|
+
let (message, _) = OFTMsgCodec::encode(receiver_bytes32, amount_sd, @"");
|
|
736
|
+
let origin = Origin { src_eid, sender: sender.into(), nonce: 1 };
|
|
737
|
+
|
|
738
|
+
start_cheat_caller_address(oft_mint_burn_adapter, LZ_ENDPOINT);
|
|
739
|
+
ILayerZeroReceiverDispatcher { contract_address: oft_mint_burn_adapter }
|
|
740
|
+
.lz_receive(origin, Bytes32 { value: 1 }, message, receiver, Default::default(), 0);
|
|
741
|
+
|
|
742
|
+
// Verify tokens were minted
|
|
743
|
+
let balance = IERC20Dispatcher { contract_address: erc20_token }.balance_of(receiver);
|
|
744
|
+
assert_eq!(balance, amount_ld);
|
|
745
|
+
}
|
|
746
|
+
|