@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,95 @@
1
+ use layerzero::oapps::common::rate_limiter::structs::{
2
+ RateLimitConfig, RateLimitDirection, RateLimitEnabled,
3
+ };
4
+ use starknet::{ClassHash, ContractAddress};
5
+
6
+ #[starknet::interface]
7
+ pub trait IMintableToken<TContractState> {
8
+ fn permissioned_mint(ref self: TContractState, account: ContractAddress, amount: u256);
9
+ fn permissioned_burn(ref self: TContractState, account: ContractAddress, amount: u256);
10
+ }
11
+
12
+ #[starknet::interface]
13
+ pub trait IOFTMintBurnAdapter<TContractState> {
14
+ // =============================== View ===============================
15
+
16
+ /// Gets the fee balance
17
+ ///
18
+ /// # Returns
19
+ ///
20
+ /// * `u256` - The fee balance
21
+ fn fee_balance(self: @TContractState) -> u256;
22
+
23
+ /// Gets the minter burner contract address
24
+ ///
25
+ /// # Returns
26
+ ///
27
+ /// * `ContractAddress` - The minter burner contract address
28
+ fn get_minter_burner(self: @TContractState) -> ContractAddress;
29
+
30
+ // =============================== Only Owner or Role ===============================
31
+
32
+ /// Sets the rate limits
33
+ ///
34
+ /// # Arguments
35
+ /// * `rate_limits` - The rate limits
36
+ /// * `direction` - The rate limit direction
37
+ fn set_rate_limits(
38
+ ref self: TContractState,
39
+ rate_limits: Array<RateLimitConfig>,
40
+ direction: RateLimitDirection,
41
+ );
42
+
43
+ /// Sets the rate limits enabled
44
+ ///
45
+ /// # Arguments
46
+ /// * `enabled` - The rate limits enabled
47
+ fn set_rate_limits_enabled(ref self: TContractState, enabled: RateLimitEnabled);
48
+
49
+ /// Resets the rate limits for the given endpoint IDs.
50
+ ///
51
+ /// This sets `amount_in_flight` to zero for both outbound and inbound directions.
52
+ /// Useful when limit thresholds are reduced below current amount in flight, or for
53
+ /// emergency recovery.
54
+ ///
55
+ /// # Arguments
56
+ /// * `eids` - The endpoint IDs to reset rate limits for
57
+ fn reset_rate_limits(ref self: TContractState, eids: Array<u32>);
58
+
59
+ /// Withdraws the fees
60
+ ///
61
+ /// # Arguments
62
+ /// * `to` - The address to withdraw the fees to
63
+ fn withdraw_fees(ref self: TContractState, to: ContractAddress);
64
+
65
+ // =============================== Upgrade ===============================
66
+
67
+ /// Upgrades the contract
68
+ ///
69
+ /// # Arguments
70
+ /// * `new_class_hash` - The new class hash to upgrade to
71
+ fn upgrade(ref self: TContractState, new_class_hash: ClassHash);
72
+
73
+ /// Upgrades the contract and calls a function
74
+ ///
75
+ /// # Arguments
76
+ /// * `new_class_hash` - The new class hash to upgrade to
77
+ /// * `selector` - The selector to call
78
+ /// * `calldata` - The calldata to pass to the function
79
+ ///
80
+ /// # Returns
81
+ fn upgrade_and_call(
82
+ ref self: TContractState,
83
+ new_class_hash: ClassHash,
84
+ selector: felt252,
85
+ calldata: Span<felt252>,
86
+ ) -> Span<felt252>;
87
+
88
+ // =============================== Pause ===============================
89
+
90
+ /// Pauses the contract
91
+ fn pause(ref self: TContractState);
92
+
93
+ /// Unpauses the contract
94
+ fn unpause(ref self: TContractState);
95
+ }
@@ -0,0 +1,10 @@
1
+ pub mod constants;
2
+ pub mod errors;
3
+ pub mod interface;
4
+ pub mod oft_mint_burn_adapter;
5
+
6
+ pub mod erc20_mint_burn_upgradeable {
7
+ pub mod constants;
8
+ pub mod erc20_mint_burn_upgradeable;
9
+ pub mod interface;
10
+ }
@@ -0,0 +1,382 @@
1
+ /// A mint/burn OFT adapter with fees, rate limiting, and role-based access control.
2
+ ///
3
+ /// This contract should be used to mint and burn tokens for ERC-20 token contracts
4
+ /// that implement the IMintableBurnable interface.
5
+ #[starknet::contract]
6
+ pub mod OFTMintBurnAdapter {
7
+ use core::num::traits::Zero;
8
+ use layerzero::common::constants::DEAD_ADDRESS;
9
+ use layerzero::oapps::common::fee::fee::{FeeComponent, FeeHooksDefaultImpl};
10
+ use layerzero::oapps::common::oapp_options_type_3::oapp_options_type_3::OAppOptionsType3Component;
11
+ use layerzero::oapps::common::rate_limiter::rate_limiter::{
12
+ RateLimiterComponent, RateLimiterHooksDefaultImpl,
13
+ };
14
+ use layerzero::oapps::common::rate_limiter::structs::{
15
+ RateLimitConfig, RateLimitDirection, RateLimitEnabled,
16
+ };
17
+ use layerzero::oapps::oapp::oapp_core::OAppCoreComponent;
18
+ use layerzero::oapps::oft::errors::err_slippage_exceeded;
19
+ use layerzero::oapps::oft::oft_core::default_oapp_hooks::OFTCoreOAppHooksDefaultImpl;
20
+ use layerzero::oapps::oft::oft_core::oft_core::OFTCoreComponent;
21
+ use layerzero::oapps::oft::structs::OFTDebit;
22
+ use lz_utils::error::assert_with_byte_array;
23
+ use openzeppelin::access::accesscontrol::AccessControlComponent;
24
+
25
+ // Access control roles
26
+ pub use openzeppelin::access::accesscontrol::AccessControlComponent::DEFAULT_ADMIN_ROLE;
27
+ use openzeppelin::access::ownable::OwnableComponent;
28
+ use openzeppelin::introspection::src5::SRC5Component;
29
+ use openzeppelin::security::pausable::PausableComponent;
30
+ use openzeppelin::token::erc20::interface::{
31
+ IERC20Dispatcher, IERC20DispatcherTrait, IERC20MetadataDispatcher,
32
+ IERC20MetadataDispatcherTrait,
33
+ };
34
+ use openzeppelin::upgrades::UpgradeableComponent;
35
+ use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
36
+ use starknet::{ClassHash, ContractAddress, get_contract_address};
37
+ use crate::constants::{
38
+ FEE_MANAGER_ROLE, PAUSE_MANAGER_ROLE, RATE_LIMITER_MANAGER_ROLE, UPGRADE_MANAGER_ROLE,
39
+ };
40
+ use crate::errors::{
41
+ err_caller_not_owner_or_missing_role, err_no_fees_to_withdraw, err_transfer_failed,
42
+ };
43
+ use crate::interface::{
44
+ IMintableTokenDispatcher, IMintableTokenDispatcherTrait, IOFTMintBurnAdapter,
45
+ };
46
+
47
+
48
+ // =============================== Components ===============================
49
+
50
+ component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent);
51
+ component!(path: SRC5Component, storage: src5, event: SRC5Event);
52
+ component!(path: PausableComponent, storage: pausable, event: PausableEvent);
53
+ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
54
+ component!(path: OAppCoreComponent, storage: oapp_core, event: OAppCoreEvent);
55
+ component!(path: OFTCoreComponent, storage: oft_core, event: OFTCoreEvent);
56
+ component!(
57
+ path: OAppOptionsType3Component, storage: oapp_options_type_3, event: OAppOptionsType3Event,
58
+ );
59
+ component!(path: FeeComponent, storage: fee, event: FeeEvent);
60
+ component!(path: RateLimiterComponent, storage: rate_limiter, event: RateLimiterEvent);
61
+ component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
62
+
63
+ // =============================== Impls ===============================
64
+
65
+ #[abi(embed_v0)]
66
+ impl AccessControlImpl =
67
+ AccessControlComponent::AccessControlImpl<ContractState>;
68
+ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl<ContractState>;
69
+
70
+ #[abi(embed_v0)]
71
+ impl PausableImpl = PausableComponent::PausableImpl<ContractState>;
72
+ impl PausableInternalImpl = PausableComponent::InternalImpl<ContractState>;
73
+
74
+ #[abi(embed_v0)]
75
+ impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl<ContractState>;
76
+ impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;
77
+
78
+ #[abi(embed_v0)]
79
+ impl OAppCoreImpl = OAppCoreComponent::OAppCoreImpl<ContractState>;
80
+ impl OAppCoreInternalImpl = OAppCoreComponent::InternalImpl<ContractState>;
81
+
82
+ #[abi(embed_v0)]
83
+ impl IOAppReceiverImpl = OAppCoreComponent::OAppReceiverImpl<ContractState>;
84
+ #[abi(embed_v0)]
85
+ impl ILayerZeroReceiverImpl =
86
+ OAppCoreComponent::LayerZeroReceiverImpl<ContractState>;
87
+
88
+ #[abi(embed_v0)]
89
+ impl OFTCoreImpl = OFTCoreComponent::OFTCoreImpl<ContractState>;
90
+ impl OFTCoreInternalImpl = OFTCoreComponent::InternalImpl<ContractState>;
91
+
92
+ #[abi(embed_v0)]
93
+ impl OAppOptionsType3Impl =
94
+ OAppOptionsType3Component::OAppOptionsType3Impl<ContractState>;
95
+ impl OAppOptionsType3InternalImpl = OAppOptionsType3Component::InternalImpl<ContractState>;
96
+
97
+ #[abi(embed_v0)]
98
+ impl FeeImpl = FeeComponent::FeeImpl<ContractState>;
99
+ impl FeeInternalImpl = FeeComponent::InternalImpl<ContractState>;
100
+
101
+ #[abi(embed_v0)]
102
+ impl RateLimiterImpl = RateLimiterComponent::RateLimiterImpl<ContractState>;
103
+
104
+ impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;
105
+
106
+ // =============================== Storage ===============================
107
+
108
+ #[storage]
109
+ struct Storage {
110
+ erc20_token: ContractAddress,
111
+ minter_burner: ContractAddress,
112
+ fee_balance: u256,
113
+ #[substorage(v0)]
114
+ access_control: AccessControlComponent::Storage,
115
+ #[substorage(v0)]
116
+ src5: SRC5Component::Storage,
117
+ #[substorage(v0)]
118
+ pausable: PausableComponent::Storage,
119
+ #[substorage(v0)]
120
+ ownable: OwnableComponent::Storage,
121
+ #[substorage(v0)]
122
+ oapp_core: OAppCoreComponent::Storage,
123
+ #[substorage(v0)]
124
+ oft_core: OFTCoreComponent::Storage,
125
+ #[substorage(v0)]
126
+ oapp_options_type_3: OAppOptionsType3Component::Storage,
127
+ #[substorage(v0)]
128
+ fee: FeeComponent::Storage,
129
+ #[substorage(v0)]
130
+ rate_limiter: RateLimiterComponent::Storage,
131
+ #[substorage(v0)]
132
+ upgradeable: UpgradeableComponent::Storage,
133
+ }
134
+
135
+ // =============================== Events ===============================
136
+
137
+ #[event]
138
+ #[derive(Drop, starknet::Event)]
139
+ pub enum Event {
140
+ FeeWithdrawn: FeeWithdrawn,
141
+ #[flat]
142
+ AccessControlEvent: AccessControlComponent::Event,
143
+ #[flat]
144
+ SRC5Event: SRC5Component::Event,
145
+ #[flat]
146
+ PausableEvent: PausableComponent::Event,
147
+ #[flat]
148
+ OwnableEvent: OwnableComponent::Event,
149
+ #[flat]
150
+ OAppCoreEvent: OAppCoreComponent::Event,
151
+ #[flat]
152
+ OFTCoreEvent: OFTCoreComponent::Event,
153
+ #[flat]
154
+ OAppOptionsType3Event: OAppOptionsType3Component::Event,
155
+ #[flat]
156
+ FeeEvent: FeeComponent::Event,
157
+ #[flat]
158
+ RateLimiterEvent: RateLimiterComponent::Event,
159
+ #[flat]
160
+ UpgradeableEvent: UpgradeableComponent::Event,
161
+ }
162
+
163
+ #[derive(Drop, starknet::Event)]
164
+ pub struct FeeWithdrawn {
165
+ pub to: ContractAddress,
166
+ pub amount_ld: u256,
167
+ }
168
+
169
+ // =============================== Constructor ===============================
170
+
171
+ #[constructor]
172
+ fn constructor(
173
+ ref self: ContractState,
174
+ erc20_token: ContractAddress,
175
+ minter_burner: ContractAddress,
176
+ lz_endpoint: ContractAddress,
177
+ owner: ContractAddress,
178
+ native_token: ContractAddress,
179
+ shared_decimals: u8,
180
+ ) {
181
+ self.erc20_token.write(erc20_token);
182
+ self.minter_burner.write(minter_burner);
183
+
184
+ self.ownable.initializer(owner);
185
+ self.oapp_core.initializer(lz_endpoint, owner, native_token);
186
+
187
+ // Get local decimals from the ERC20 token using ERC20Metadata interface
188
+ let token = IERC20MetadataDispatcher { contract_address: erc20_token };
189
+ let local_decimals = token.decimals();
190
+ self.oft_core.initializer(local_decimals, shared_decimals);
191
+
192
+ // Initialize access control roles - only grant DEFAULT_ADMIN_ROLE to owner
193
+ // Other roles should be granted explicitly after deployment
194
+ self.access_control._grant_role(DEFAULT_ADMIN_ROLE, owner);
195
+ }
196
+
197
+ #[abi(embed_v0)]
198
+ impl OFTMintBurnAdapterImpl of IOFTMintBurnAdapter<ContractState> {
199
+ // =============================== View ===============================
200
+
201
+ fn get_minter_burner(self: @ContractState) -> ContractAddress {
202
+ self.minter_burner.read()
203
+ }
204
+
205
+ fn fee_balance(self: @ContractState) -> u256 {
206
+ self.fee_balance.read()
207
+ }
208
+
209
+ // =============================== Rate Limiter Manager ===============================
210
+
211
+ fn set_rate_limits(
212
+ ref self: ContractState,
213
+ rate_limits: Array<RateLimitConfig>,
214
+ direction: RateLimitDirection,
215
+ ) {
216
+ self._assert_owner_or_role(RATE_LIMITER_MANAGER_ROLE);
217
+ self.rate_limiter._set_rate_limits(rate_limits, direction);
218
+ }
219
+
220
+ fn set_rate_limits_enabled(ref self: ContractState, enabled: RateLimitEnabled) {
221
+ self._assert_owner_or_role(RATE_LIMITER_MANAGER_ROLE);
222
+ self.rate_limiter._set_rate_limit_enabled(enabled);
223
+ }
224
+
225
+ fn reset_rate_limits(ref self: ContractState, eids: Array<u32>) {
226
+ self._assert_owner_or_role(RATE_LIMITER_MANAGER_ROLE);
227
+ self.rate_limiter._reset_rate_limits(eids);
228
+ }
229
+
230
+ // =============================== Fee Manager ===============================
231
+
232
+ fn withdraw_fees(ref self: ContractState, to: ContractAddress) {
233
+ self._assert_owner_or_role(FEE_MANAGER_ROLE);
234
+
235
+ let fee_balance = self.fee_balance.read();
236
+ assert_with_byte_array(fee_balance > 0, err_no_fees_to_withdraw());
237
+
238
+ self.fee_balance.write(0);
239
+ let token = IERC20Dispatcher { contract_address: self.erc20_token.read() };
240
+ let result = token.transfer(to, fee_balance);
241
+ assert_with_byte_array(result, err_transfer_failed(to, fee_balance));
242
+
243
+ self.emit(FeeWithdrawn { to, amount_ld: fee_balance });
244
+ }
245
+
246
+ // =============================== Upgrade Manager ===============================
247
+
248
+ fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
249
+ self._assert_owner_or_role(UPGRADE_MANAGER_ROLE);
250
+ self.upgradeable.upgrade(new_class_hash);
251
+ }
252
+
253
+ fn upgrade_and_call(
254
+ ref self: ContractState,
255
+ new_class_hash: ClassHash,
256
+ selector: felt252,
257
+ calldata: Span<felt252>,
258
+ ) -> Span<felt252> {
259
+ self._assert_owner_or_role(UPGRADE_MANAGER_ROLE);
260
+ self.upgradeable.upgrade_and_call(new_class_hash, selector, calldata)
261
+ }
262
+
263
+ // =============================== Pausable ===============================
264
+
265
+ fn pause(ref self: ContractState) {
266
+ self._assert_owner_or_role(PAUSE_MANAGER_ROLE);
267
+ self.pausable.pause();
268
+ }
269
+
270
+ fn unpause(ref self: ContractState) {
271
+ self._assert_owner_or_role(PAUSE_MANAGER_ROLE);
272
+ self.pausable.unpause();
273
+ }
274
+ }
275
+
276
+ // =============================== Internal Access Control ===============================
277
+
278
+ #[generate_trait]
279
+ pub impl InternalAccessControlImpl of InternalAccessControlTrait {
280
+ fn _assert_owner_or_role(self: @ContractState, role: felt252) {
281
+ let caller = starknet::get_caller_address();
282
+ let is_owner = caller == self.ownable.owner();
283
+ let has_role = self.access_control.has_role(role, caller);
284
+
285
+ assert_with_byte_array(
286
+ is_owner || has_role, err_caller_not_owner_or_missing_role(role),
287
+ );
288
+ }
289
+ }
290
+
291
+ // =============================== OFT Hooks ===============================
292
+
293
+ impl OFTHooksImpl of OFTCoreComponent::OFTHooks<ContractState> {
294
+ fn _debit(
295
+ ref self: OFTCoreComponent::ComponentState<ContractState>,
296
+ from: ContractAddress,
297
+ amount: u256,
298
+ min_amount: u256,
299
+ dst_eid: u32,
300
+ ) -> OFTDebit {
301
+ // Check contract is not paused
302
+ let contract = self.get_contract();
303
+ contract.pausable.assert_not_paused();
304
+
305
+ let oft_debit = self._debit_view(amount, min_amount, dst_eid);
306
+ let fee = oft_debit.amount_sent_ld - oft_debit.amount_received_ld;
307
+
308
+ let mut contract = self.get_contract_mut();
309
+ contract.rate_limiter._outflow(dst_eid, oft_debit.amount_received_ld);
310
+
311
+ let minter_burner = IMintableTokenDispatcher {
312
+ contract_address: contract.minter_burner.read(),
313
+ };
314
+
315
+ if fee > 0 {
316
+ contract.fee_balance.write(contract.fee_balance.read() + fee);
317
+ minter_burner.permissioned_mint(get_contract_address(), fee);
318
+ }
319
+
320
+ minter_burner.permissioned_burn(from, oft_debit.amount_sent_ld);
321
+
322
+ oft_debit
323
+ }
324
+
325
+ fn _debit_view(
326
+ self: @OFTCoreComponent::ComponentState<ContractState>,
327
+ amount_ld: u256,
328
+ min_amount_ld: u256,
329
+ dst_eid: u32,
330
+ ) -> OFTDebit {
331
+ // Calculate a fee based on the amount BEFORE the dust is deducted.
332
+ let fee = self.get_contract().fee.get_fee(dst_eid, amount_ld);
333
+ let amount_received_ld = self._remove_dust(amount_ld - fee);
334
+
335
+ assert_with_byte_array(
336
+ amount_received_ld >= min_amount_ld,
337
+ err_slippage_exceeded(amount_received_ld, min_amount_ld),
338
+ );
339
+
340
+ let amount_sent_ld = amount_received_ld + fee;
341
+
342
+ OFTDebit { amount_sent_ld, amount_received_ld }
343
+ }
344
+
345
+ fn _credit(
346
+ ref self: OFTCoreComponent::ComponentState<ContractState>,
347
+ to: ContractAddress,
348
+ amount: u256,
349
+ src_eid: u32,
350
+ ) -> u256 {
351
+ // Check contract is not paused
352
+ let contract = self.get_contract();
353
+ contract.pausable.assert_not_paused();
354
+
355
+ // Handle the zero address case
356
+ let to = if to.is_zero() {
357
+ DEAD_ADDRESS
358
+ } else {
359
+ to
360
+ };
361
+
362
+ let mut contract = self.get_contract_mut();
363
+ contract.rate_limiter._inflow(src_eid, amount);
364
+
365
+ let minter_burner = IMintableTokenDispatcher {
366
+ contract_address: contract.minter_burner.read(),
367
+ };
368
+ minter_burner.permissioned_mint(to, amount);
369
+
370
+ amount
371
+ }
372
+
373
+ fn _token(self: @OFTCoreComponent::ComponentState<ContractState>) -> ContractAddress {
374
+ self.get_contract().erc20_token.read()
375
+ }
376
+
377
+ fn _approval_required(self: @OFTCoreComponent::ComponentState<ContractState>) -> bool {
378
+ // No approval since we mint/burn.
379
+ false
380
+ }
381
+ }
382
+ }
@@ -0,0 +1,21 @@
1
+ //! Fuzzable contract addresses for ofts tests
2
+
3
+ use core::num::traits::Zero;
4
+ use snforge_std::fuzzable::Fuzzable;
5
+ use starknet::ContractAddress;
6
+
7
+ /// Generate a random contract address
8
+ pub(crate) impl FuzzableContractAddress of Fuzzable<ContractAddress> {
9
+ fn generate() -> ContractAddress {
10
+ loop {
11
+ if let Some(address) = Fuzzable::<felt252>::generate().try_into() {
12
+ return address;
13
+ }
14
+ }
15
+ }
16
+
17
+ fn blank() -> ContractAddress {
18
+ Zero::zero()
19
+ }
20
+ }
21
+
@@ -0,0 +1,8 @@
1
+ pub mod fuzzable {
2
+ pub mod contract_address;
3
+ }
4
+ pub mod test_erc20_mint_burn_upgradeable;
5
+ pub mod test_mint_burn_adapter;
6
+ pub mod test_mint_burn_adapter_advanced;
7
+ pub mod utils;
8
+