@layerzerolabs/protocol-stellar-v2 0.2.33 → 0.2.34

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 (66) hide show
  1. package/.turbo/turbo-build.log +377 -392
  2. package/.turbo/turbo-lint.log +209 -207
  3. package/.turbo/turbo-test.log +1687 -1753
  4. package/contracts/oapps/oft/integration-tests/extensions/test_oft_fee.rs +5 -11
  5. package/contracts/oapps/oft/integration-tests/extensions/test_pausable.rs +7 -14
  6. package/contracts/oapps/oft/integration-tests/extensions/test_rate_limiter.rs +11 -22
  7. package/contracts/oapps/oft/integration-tests/setup.rs +59 -7
  8. package/contracts/oapps/oft/integration-tests/utils.rs +28 -2
  9. package/contracts/oapps/oft/src/interfaces/mintable.rs +14 -0
  10. package/contracts/oapps/oft/src/interfaces/mod.rs +2 -2
  11. package/contracts/oapps/oft/src/oft.rs +3 -3
  12. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +8 -8
  13. package/contracts/oapps/oft/src/oft_types/mod.rs +3 -4
  14. package/contracts/oapps/oft/src/tests/extensions/rate_limiter.rs +7 -5
  15. package/contracts/oapps/sac-manager/src/errors.rs +14 -0
  16. package/contracts/{sac-manager → oapps/sac-manager}/src/lib.rs +0 -4
  17. package/contracts/oapps/sac-manager/src/sac_manager.rs +115 -0
  18. package/contracts/oapps/sac-manager/src/storage.rs +20 -0
  19. package/contracts/{sac-manager → oapps/sac-manager}/src/tests/mod.rs +0 -4
  20. package/contracts/oapps/sac-manager/src/tests/sac_manager/clawback.rs +86 -0
  21. package/contracts/oapps/sac-manager/src/tests/sac_manager/mint.rs +58 -0
  22. package/contracts/{sac-manager → oapps/sac-manager}/src/tests/sac_manager/mod.rs +1 -3
  23. package/contracts/oapps/sac-manager/src/tests/sac_manager/set_minter.rs +69 -0
  24. package/contracts/oapps/sac-manager/src/tests/sac_manager/test_helper.rs +18 -0
  25. package/contracts/oapps/sac-manager/src/tests/sac_manager/view_functions.rs +28 -0
  26. package/contracts/{sac-manager → oapps/sac-manager}/src/tests/test_helper.rs +16 -59
  27. package/package.json +8 -3
  28. package/sdk/.turbo/turbo-test.log +373 -374
  29. package/sdk/dist/generated/oft.d.ts +3 -3
  30. package/sdk/dist/generated/oft.js +4 -4
  31. package/sdk/dist/generated/sac_manager.d.ts +26 -318
  32. package/sdk/dist/generated/sac_manager.js +23 -129
  33. package/sdk/package.json +6 -1
  34. package/sdk/test/oft-sml.test.ts +72 -36
  35. package/sdk/test/sac-manager-redistribution.test.ts +38 -182
  36. package/contracts/oapps/oft/src/interfaces/mint_burnable.rs +0 -18
  37. package/contracts/sac-manager/src/errors.rs +0 -18
  38. package/contracts/sac-manager/src/extensions/mod.rs +0 -6
  39. package/contracts/sac-manager/src/extensions/redistribution.rs +0 -109
  40. package/contracts/sac-manager/src/extensions/supply_control/mod.rs +0 -488
  41. package/contracts/sac-manager/src/extensions/supply_control/rate_limit.rs +0 -126
  42. package/contracts/sac-manager/src/interfaces/mod.rs +0 -3
  43. package/contracts/sac-manager/src/interfaces/sac_manager.rs +0 -52
  44. package/contracts/sac-manager/src/sac_manager.rs +0 -193
  45. package/contracts/sac-manager/src/storage.rs +0 -20
  46. package/contracts/sac-manager/src/tests/redistribution/mod.rs +0 -1
  47. package/contracts/sac-manager/src/tests/redistribution/redistribute_funds.rs +0 -82
  48. package/contracts/sac-manager/src/tests/sac_manager/admin_mint.rs +0 -206
  49. package/contracts/sac-manager/src/tests/sac_manager/burn.rs +0 -215
  50. package/contracts/sac-manager/src/tests/sac_manager/clawback.rs +0 -209
  51. package/contracts/sac-manager/src/tests/sac_manager/mint.rs +0 -252
  52. package/contracts/sac-manager/src/tests/sac_manager/set_oft_address.rs +0 -47
  53. package/contracts/sac-manager/src/tests/sac_manager/test_helper.rs +0 -75
  54. package/contracts/sac-manager/src/tests/sac_manager/view_functions.rs +0 -60
  55. package/contracts/sac-manager/src/tests/supply_control/enumerable_set.rs +0 -256
  56. package/contracts/sac-manager/src/tests/supply_control/mod.rs +0 -8
  57. package/contracts/sac-manager/src/tests/supply_control/refill.rs +0 -90
  58. package/contracts/sac-manager/src/tests/supply_control/set_mint_whitelist.rs +0 -245
  59. package/contracts/sac-manager/src/tests/supply_control/set_supply_controller.rs +0 -267
  60. package/contracts/sac-manager/src/tests/supply_control/set_supply_controller_manager.rs +0 -122
  61. package/contracts/sac-manager/src/tests/supply_control/test_helper.rs +0 -38
  62. package/contracts/sac-manager/src/tests/supply_control/update_allow_any_mint_burn.rs +0 -114
  63. package/contracts/sac-manager/src/tests/supply_control/update_limit_config.rs +0 -257
  64. /package/contracts/{sac-manager → oapps/sac-manager}/Cargo.toml +0 -0
  65. /package/contracts/{sac-manager → oapps/sac-manager}/src/tests/sac_manager/set_admin.rs +0 -0
  66. /package/contracts/{sac-manager → oapps/sac-manager}/src/tests/sac_manager/set_authorized.rs +0 -0
@@ -8,9 +8,9 @@
8
8
  use crate::integration_tests::{
9
9
  setup::{create_recipient_address, decode_packet, setup, wire_endpoint, wire_oft, TestSetup},
10
10
  utils::{
11
- address_to_peer_bytes32, create_send_param, lz_receive, mint_to, quote_oft, quote_send,
11
+ address_to_peer_bytes32, create_send_param, lz_receive, mint_oft_token_to, mint_to, quote_oft, quote_send,
12
12
  scan_packet_sent_event, send, send_with_fee, set_default_fee_bps, set_fee_bps, set_fee_deposit_address,
13
- token_balance, transfer_sac_admin, validate_packet,
13
+ token_balance, validate_packet,
14
14
  },
15
15
  };
16
16
  use soroban_sdk::{testutils::Address as _, token::TokenClient, Address};
@@ -26,10 +26,8 @@ fn test_cross_chain_with_zero_fee() {
26
26
  let receiver = create_recipient_address(&env);
27
27
  let executor = Address::generate(&env);
28
28
 
29
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 10_000_000);
29
+ mint_oft_token_to(&env, &chain_a, &sender, 10_000_000);
30
30
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
31
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
32
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
33
31
 
34
32
  let to = address_to_peer_bytes32(&receiver);
35
33
  let send_param = create_send_param(&env, chain_b.eid, 1_000_000, 0, &to);
@@ -63,10 +61,8 @@ fn test_cross_chain_with_fee() {
63
61
  let receiver = create_recipient_address(&env);
64
62
  let executor = Address::generate(&env);
65
63
 
66
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 10_000_000);
64
+ mint_oft_token_to(&env, &chain_a, &sender, 10_000_000);
67
65
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
68
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
69
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
70
66
 
71
67
  // Enable 1% fee (100 bps)
72
68
  set_fee_deposit_address(&env, &chain_a, &chain_a.fee_collector);
@@ -116,10 +112,8 @@ fn test_cross_chain_with_destination_specific_fee() {
116
112
  let receiver = create_recipient_address(&env);
117
113
  let executor = Address::generate(&env);
118
114
 
119
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 10_000_000);
115
+ mint_oft_token_to(&env, &chain_a, &sender, 10_000_000);
120
116
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
121
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
122
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
123
117
 
124
118
  // Set default fee 1% and destination-specific fee 2% for chain_b
125
119
  set_fee_deposit_address(&env, &chain_a, &chain_a.fee_collector);
@@ -6,8 +6,8 @@
6
6
  use crate::integration_tests::{
7
7
  setup::{create_recipient_address, decode_packet, setup, wire_endpoint, wire_oft, TestSetup},
8
8
  utils::{
9
- address_to_peer_bytes32, create_send_param, is_paused, lz_receive, mint_to, quote_oft, quote_send,
10
- scan_packet_sent_event, send, set_paused, transfer_sac_admin, try_lz_receive, try_send, validate_packet,
9
+ address_to_peer_bytes32, create_send_param, is_paused, lz_receive, mint_oft_token_to, mint_to, quote_oft,
10
+ quote_send, scan_packet_sent_event, send, set_paused, try_lz_receive, try_send, validate_packet,
11
11
  },
12
12
  };
13
13
  use soroban_sdk::{testutils::Address as _, token::TokenClient, Address};
@@ -22,11 +22,9 @@ fn test_send_succeeds_when_unpaused() {
22
22
  let sender = Address::generate(&env);
23
23
  let receiver = create_recipient_address(&env);
24
24
 
25
- // Mint tokens and transfer admin rights
26
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 10_000_000);
25
+ // Mint tokens (SAC admin is already the mock wrapper from setup)
26
+ mint_oft_token_to(&env, &chain_a, &sender, 10_000_000);
27
27
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
28
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
29
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
30
28
 
31
29
  // Default state should be unpaused
32
30
  assert!(!is_paused(&chain_a));
@@ -63,9 +61,8 @@ fn test_send_fails_when_paused() {
63
61
  let sender = Address::generate(&env);
64
62
  let receiver = create_recipient_address(&env);
65
63
 
66
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 10_000_000);
64
+ mint_oft_token_to(&env, &chain_a, &sender, 10_000_000);
67
65
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
68
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
69
66
 
70
67
  let to = address_to_peer_bytes32(&receiver);
71
68
  let send_param = create_send_param(&env, chain_b.eid, 1_000_000, 0, &to);
@@ -94,10 +91,8 @@ fn test_receive_fails_when_paused() {
94
91
  let receiver = create_recipient_address(&env);
95
92
  let executor = Address::generate(&env);
96
93
 
97
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 10_000_000);
94
+ mint_oft_token_to(&env, &chain_a, &sender, 10_000_000);
98
95
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
99
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
100
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
101
96
 
102
97
  // Send from chain_a to chain_b
103
98
  let to = address_to_peer_bytes32(&receiver);
@@ -130,10 +125,8 @@ fn test_cross_chain_succeeds_after_unpause() {
130
125
  let receiver = create_recipient_address(&env);
131
126
  let executor = Address::generate(&env);
132
127
 
133
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 10_000_000);
128
+ mint_oft_token_to(&env, &chain_a, &sender, 10_000_000);
134
129
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
135
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
136
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
137
130
 
138
131
  // Pause and then unpause
139
132
  set_paused(&env, &chain_a, true);
@@ -7,9 +7,9 @@ use crate::extensions::rate_limiter::{Direction, Mode};
7
7
  use crate::integration_tests::{
8
8
  setup::{create_recipient_address, decode_packet, setup, wire_endpoint, wire_oft, TestSetup},
9
9
  utils::{
10
- address_to_peer_bytes32, advance_time, create_send_param, lz_receive, mint_to, quote_oft, quote_send,
11
- rate_limit_capacity, rate_limit_in_flight, scan_packet_sent_event, send, set_rate_limit, set_rate_limit_with_mode,
12
- transfer_sac_admin, try_send, validate_packet,
10
+ address_to_peer_bytes32, advance_time, create_send_param, lz_receive, mint_oft_token_to, mint_to, quote_oft,
11
+ quote_send, rate_limit_capacity, rate_limit_in_flight, scan_packet_sent_event, send, set_rate_limit,
12
+ set_rate_limit_with_mode, try_send, validate_packet,
13
13
  },
14
14
  };
15
15
  use soroban_sdk::{testutils::Address as _, token::TokenClient, Address};
@@ -25,10 +25,8 @@ fn test_send_without_rate_limit() {
25
25
  let receiver = create_recipient_address(&env);
26
26
  let executor = Address::generate(&env);
27
27
 
28
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 100_000_000);
28
+ mint_oft_token_to(&env, &chain_a, &sender, 100_000_000);
29
29
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
30
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
31
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
32
30
 
33
31
  // Default capacity should be i128::MAX (unlimited)
34
32
  let capacity = rate_limit_capacity(&chain_a, &Direction::Outbound, chain_b.eid);
@@ -62,10 +60,8 @@ fn test_send_within_rate_limit() {
62
60
  let receiver = create_recipient_address(&env);
63
61
  let executor = Address::generate(&env);
64
62
 
65
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 100_000_000);
63
+ mint_oft_token_to(&env, &chain_a, &sender, 100_000_000);
66
64
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
67
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
68
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
69
65
 
70
66
  // Set rate limit: 10M per 3600 seconds (1 hour)
71
67
  set_rate_limit(&env, &chain_a, &Direction::Outbound, chain_b.eid, 10_000_000, 3600);
@@ -112,9 +108,8 @@ fn test_send_exceeds_rate_limit() {
112
108
  let sender = Address::generate(&env);
113
109
  let receiver = create_recipient_address(&env);
114
110
 
115
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 100_000_000);
111
+ mint_oft_token_to(&env, &chain_a, &sender, 100_000_000);
116
112
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
117
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
118
113
 
119
114
  // Set rate limit: 1M per 3600 seconds
120
115
  set_rate_limit(&env, &chain_a, &Direction::Outbound, chain_b.eid, 1_000_000, 3600);
@@ -140,10 +135,8 @@ fn test_rate_limit_decay() {
140
135
  let receiver = create_recipient_address(&env);
141
136
  let executor = Address::generate(&env);
142
137
 
143
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender, 100_000_000);
138
+ mint_oft_token_to(&env, &chain_a, &sender, 100_000_000);
144
139
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender, 10_000_000_000);
145
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
146
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
147
140
 
148
141
  // Set rate limit: 10M per 1000 seconds
149
142
  set_rate_limit(&env, &chain_a, &Direction::Outbound, chain_b.eid, 10_000_000, 1000);
@@ -199,12 +192,10 @@ fn test_gross_mode_does_not_release() {
199
192
  let executor = Address::generate(&env);
200
193
 
201
194
  // Setup tokens for both chains
202
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender_a, 100_000_000);
195
+ mint_oft_token_to(&env, &chain_a, &sender_a, 100_000_000);
203
196
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender_a, 10_000_000_000);
204
- mint_to(&env, &chain_b.owner, &chain_b.oft_token, &sender_b, 100_000_000);
197
+ mint_oft_token_to(&env, &chain_b, &sender_b, 100_000_000);
205
198
  mint_to(&env, &chain_b.owner, &chain_b.native_token, &sender_b, 10_000_000_000);
206
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
207
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
208
199
 
209
200
  // Set Gross mode rate limit on chain_a outbound: 10M per 3600 seconds
210
201
  set_rate_limit_with_mode(&env, &chain_a, &Direction::Outbound, chain_b.eid, 10_000_000, 3600, Mode::Gross);
@@ -267,12 +258,10 @@ fn test_net_mode_does_release() {
267
258
  let executor = Address::generate(&env);
268
259
 
269
260
  // Setup tokens for both chains
270
- mint_to(&env, &chain_a.owner, &chain_a.oft_token, &sender_a, 100_000_000);
261
+ mint_oft_token_to(&env, &chain_a, &sender_a, 100_000_000);
271
262
  mint_to(&env, &chain_a.owner, &chain_a.native_token, &sender_a, 10_000_000_000);
272
- mint_to(&env, &chain_b.owner, &chain_b.oft_token, &sender_b, 100_000_000);
263
+ mint_oft_token_to(&env, &chain_b, &sender_b, 100_000_000);
273
264
  mint_to(&env, &chain_b.owner, &chain_b.native_token, &sender_b, 10_000_000_000);
274
- transfer_sac_admin(&env, &chain_a.owner, &chain_a.oft_token, &chain_a.oft.address);
275
- transfer_sac_admin(&env, &chain_b.owner, &chain_b.oft_token, &chain_b.oft.address);
276
265
 
277
266
  // Set Net mode rate limit on chain_a outbound: 10M per 3600 seconds
278
267
  set_rate_limit_with_mode(&env, &chain_a, &Direction::Outbound, chain_b.eid, 10_000_000, 3600, Mode::Net);
@@ -12,12 +12,40 @@ use crate::{
12
12
  use endpoint_v2::{EndpointV2, EndpointV2Client};
13
13
  use simple_message_lib::{SimpleMessageLib, SimpleMessageLibClient};
14
14
  use soroban_sdk::{
15
- contract, contractimpl, log,
15
+ contract, contractimpl, contracttype, log,
16
16
  testutils::{Address as _, MockAuth, MockAuthInvoke},
17
- token::TokenClient,
17
+ token::{StellarAssetClient, TokenClient},
18
18
  Address, BytesN, Env, IntoVal,
19
19
  };
20
20
 
21
+ // ============================================================================
22
+ // Mock SAC Wrapper - implements Mintable for integration tests
23
+ // ============================================================================
24
+ // Wraps a Stellar Asset Contract (SAC). The wrapper is set as SAC admin and
25
+ // implements mint(env, to, amount, operation) so the OFT uses it for credit (mint).
26
+ // OFT burns on the token directly; this mock still has burn for completeness.
27
+
28
+ #[contracttype]
29
+ pub enum MockSacWrapperKey {
30
+ Sac,
31
+ }
32
+
33
+ #[contract]
34
+ pub struct MockSacWrapper;
35
+
36
+ #[contractimpl]
37
+ impl MockSacWrapper {
38
+ pub fn __constructor(env: &Env, sac: Address) {
39
+ env.storage().instance().set(&MockSacWrapperKey::Sac, &sac);
40
+ }
41
+
42
+ /// Mintable::mint - mints on the underlying SAC (wrapper must be SAC admin).
43
+ pub fn mint(env: &Env, to: &Address, amount: i128, _operation: &Address) {
44
+ let sac: Address = env.storage().instance().get(&MockSacWrapperKey::Sac).unwrap();
45
+ StellarAssetClient::new(env, &sac).mint(to, &amount);
46
+ }
47
+ }
48
+
21
49
  // ============================================================================
22
50
  // Dummy Recipient - used to create valid contract addresses for recipients
23
51
  // ============================================================================
@@ -45,7 +73,10 @@ pub struct ChainSetup<'a> {
45
73
  pub owner: Address,
46
74
  pub native_token: Address,
47
75
  pub zro_token: Address,
76
+ /// Underlying SAC for the OFT token (used for balance, transfer, mint_to).
48
77
  pub oft_token: Address,
78
+ /// Mock SAC wrapper implementing Mintable; OFT uses it for mint on credit.
79
+ pub sac_wrapper: Address,
49
80
  pub endpoint: EndpointV2Client<'a>,
50
81
  pub sml: SimpleMessageLibClient<'a>,
51
82
  pub oft: OFTClient<'a>,
@@ -72,9 +103,20 @@ fn setup_chain<'a>(env: &Env) -> ChainSetup<'a> {
72
103
  let zro_sac = env.register_stellar_asset_contract_v2(owner.clone());
73
104
  let zro_token = zro_sac.address();
74
105
 
75
- // Create OFT token
106
+ // Create OFT token (SAC) and a mock SAC wrapper that implements Mintable.
76
107
  let oft_sac = env.register_stellar_asset_contract_v2(owner.clone());
77
108
  let oft_token = oft_sac.address();
109
+ let sac_wrapper_address = env.register(MockSacWrapper, (&oft_token,));
110
+ env.mock_auths(&[MockAuth {
111
+ address: &owner,
112
+ invoke: &MockAuthInvoke {
113
+ contract: &oft_token,
114
+ fn_name: "set_admin",
115
+ args: (&sac_wrapper_address,).into_val(env),
116
+ sub_invokes: &[],
117
+ },
118
+ }]);
119
+ StellarAssetClient::new(env, &oft_token).set_admin(&sac_wrapper_address);
78
120
 
79
121
  let eid: u32 = 30400; // Test EID
80
122
  let endpoint_address = env.register(EndpointV2, (&owner, eid, &native_token));
@@ -82,9 +124,8 @@ fn setup_chain<'a>(env: &Env) -> ChainSetup<'a> {
82
124
  let sml_address = env.register(SimpleMessageLib, (&owner, &endpoint_address, &fee_recipient));
83
125
  let delegate: Option<Address> = Some(owner.clone());
84
126
  let shared_decimals: u32 = 6; // Default shared decimals
85
- // Both chains use MintBurn the SAC itself implements mint/burn.
86
- // This exercises the MintBurnableClient code path.
87
- let mode = OftType::MintBurn(oft_token.clone());
127
+ // MintBurn with SAC wrapper: OFT uses wrapper for mint on credit; burn is on token directly.
128
+ let mode = OftType::MintBurn(sac_wrapper_address.clone());
88
129
  let oft_address = env.register(OFT, (&oft_token, &shared_decimals, &mode, &owner, &endpoint_address, &delegate));
89
130
 
90
131
  let endpoint = EndpointV2Client::new(env, &endpoint_address);
@@ -104,7 +145,18 @@ fn setup_chain<'a>(env: &Env) -> ChainSetup<'a> {
104
145
  endpoint.set_zro(&zro_token);
105
146
 
106
147
  register_library(env, &owner, &endpoint, &sml.address);
107
- ChainSetup { eid, owner, native_token, zro_token, oft_token, endpoint, sml, oft, fee_collector }
148
+ ChainSetup {
149
+ eid,
150
+ owner,
151
+ native_token,
152
+ zro_token,
153
+ oft_token,
154
+ sac_wrapper: sac_wrapper_address,
155
+ endpoint,
156
+ sml,
157
+ oft,
158
+ fee_collector,
159
+ }
108
160
  }
109
161
 
110
162
  pub fn setup<'a>() -> TestSetup<'a> {
@@ -2,6 +2,7 @@
2
2
 
3
3
  use crate::extensions::rate_limiter::{Direction, Mode, RateLimitConfig};
4
4
  use crate::integration_tests::setup::{decode_packet, ChainSetup};
5
+ use crate::MintableClient;
5
6
  use endpoint_v2::{MessagingFee, Origin, OutboundPacket};
6
7
  use message_lib_common::packet_codec_v1;
7
8
  use oft_core::{OFTFeeDetail, OFTLimit, OFTReceipt, SendParam};
@@ -38,7 +39,11 @@ pub fn create_recipient_address(env: &Env) -> Address {
38
39
  // OFT Core Operations
39
40
  // ============================================================================
40
41
 
41
- pub fn quote_oft(chain: &ChainSetup<'_>, from: &Address, send_param: &SendParam) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt) {
42
+ pub fn quote_oft(
43
+ chain: &ChainSetup<'_>,
44
+ from: &Address,
45
+ send_param: &SendParam,
46
+ ) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt) {
42
47
  chain.oft.quote_oft(from, send_param)
43
48
  }
44
49
 
@@ -61,7 +66,8 @@ pub fn quote_send(
61
66
  chain.oft.quote_send(sender, send_param, &pay_in_zro)
62
67
  }
63
68
 
64
- /// Send without fee (standard OFT send)
69
+ /// Send without fee (standard OFT send).
70
+ /// Sender authorizes OFT send (OFT debits by calling token burn directly) and SAC burn.
65
71
  pub fn send(
66
72
  env: &Env,
67
73
  chain: &ChainSetup<'_>,
@@ -308,6 +314,26 @@ pub fn mint_to(env: &Env, owner: &Address, token: &Address, to: &Address, amount
308
314
  sac.mint(to, &amount);
309
315
  }
310
316
 
317
+ /// Mints the OFT token (via the Mintable wrapper) to the given address.
318
+ /// Use when OFT is MintBurn; the wrapper calls the underlying SAC mint.
319
+ pub fn mint_oft_token_to(env: &Env, chain: &ChainSetup<'_>, to: &Address, amount: i128) {
320
+ env.mock_auths(&[MockAuth {
321
+ address: &chain.owner,
322
+ invoke: &MockAuthInvoke {
323
+ contract: &chain.sac_wrapper,
324
+ fn_name: "mint",
325
+ args: (to, &amount, &chain.owner).into_val(env),
326
+ sub_invokes: &[MockAuthInvoke {
327
+ contract: &chain.oft_token,
328
+ fn_name: "mint",
329
+ args: (to, &amount).into_val(env),
330
+ sub_invokes: &[],
331
+ }],
332
+ },
333
+ }]);
334
+ MintableClient::new(env, &chain.sac_wrapper).mint(to, &amount, &chain.owner);
335
+ }
336
+
311
337
  pub fn transfer_sac_admin(env: &Env, owner: &Address, token: &Address, new_admin: &Address) {
312
338
  env.mock_auths(&[MockAuth {
313
339
  address: owner,
@@ -0,0 +1,14 @@
1
+ //! Mintable trait - the interface the OFT uses to mint tokens on credit (receive).
2
+
3
+ use soroban_sdk::{contractclient, Address, Env};
4
+
5
+ /// The mint interface for OFT MintBurn operations.
6
+ ///
7
+ /// A contract that implements `mint` (e.g. SAC Manager or a token wrapper) is used
8
+ /// for crediting; the OFT calls the token (SAC) directly for burn on debit.
9
+ #[contractclient(name = "MintableClient")]
10
+ pub trait Mintable {
11
+ /// Mints `amount` tokens to `to`. The `operation` address is the caller (e.g. OFT)
12
+ /// requesting the mint, for use by SAC wrappers that enforce authorization.
13
+ fn mint(env: &Env, to: &Address, amount: i128, operation: &Address);
14
+ }
@@ -1,3 +1,3 @@
1
- mod mint_burnable;
1
+ mod mintable;
2
2
 
3
- pub use mint_burnable::*;
3
+ pub use mintable::*;
@@ -110,8 +110,8 @@ impl OFTInternal for OFT {
110
110
  OftType::LockUnlock => {
111
111
  lock_unlock::debit::<Self>(env, &Self::token(env), sender, amount_ld, min_amount_ld, dst_eid)
112
112
  }
113
- OftType::MintBurn(mint_burnable) => {
114
- mint_burn::debit::<Self>(env, &mint_burnable, sender, amount_ld, min_amount_ld, dst_eid)
113
+ OftType::MintBurn(_mintable) => {
114
+ mint_burn::debit::<Self>(env, &Self::token(env), sender, amount_ld, min_amount_ld, dst_eid)
115
115
  }
116
116
  };
117
117
 
@@ -133,7 +133,7 @@ impl OFTInternal for OFT {
133
133
  // Core credit logic (based on mode)
134
134
  let amount_credited = match Self::oft_type(env) {
135
135
  OftType::LockUnlock => lock_unlock::credit::<Self>(env, &Self::token(env), to, amount_ld, src_eid),
136
- OftType::MintBurn(mint_burnable) => mint_burn::credit::<Self>(env, &mint_burnable, to, amount_ld, src_eid),
136
+ OftType::MintBurn(mintable) => mint_burn::credit::<Self>(env, &mintable, to, amount_ld, src_eid),
137
137
  };
138
138
 
139
139
  // Rate limit checks
@@ -1,17 +1,17 @@
1
1
  //! MintBurn type implementation for OFT.
2
2
  //!
3
3
  //! This OFT type burns tokens on debit (send) and mints tokens on credit (receive).
4
- //! Used when the OFT contract has mint/burn authority over the token.
4
+ //! Used when the OFT contract has mint authority over the token.
5
5
 
6
- use crate::interfaces::MintBurnableClient;
6
+ use crate::interfaces::MintableClient;
7
7
  use oft_core::OFTCore;
8
- use soroban_sdk::{Address, Env};
8
+ use soroban_sdk::{token::TokenClient, Address, Env};
9
9
 
10
10
  /// Debit tokens using MintBurn OFT type (burns tokens from sender).
11
11
  ///
12
12
  /// # Parameters
13
13
  /// * `env` - The Soroban environment
14
- /// * `burnable` - Address of the contract responsible for burning tokens
14
+ /// * `token` - Address of the token (SAC) to burn from
15
15
  /// * `sender` - Address of the token sender
16
16
  /// * `amount_ld` - Amount to debit in local decimals
17
17
  /// * `min_amount_ld` - Minimum amount that must be received (for slippage protection)
@@ -22,14 +22,14 @@ use soroban_sdk::{Address, Env};
22
22
  /// * `amount_received_ld` - The amount received in local decimals on the remote
23
23
  pub fn debit<T: OFTCore>(
24
24
  env: &Env,
25
- burnable: &Address,
25
+ token: &Address,
26
26
  sender: &Address,
27
27
  amount_ld: i128,
28
28
  min_amount_ld: i128,
29
29
  dst_eid: u32,
30
30
  ) -> (i128, i128) {
31
31
  let (amount_sent_ld, amount_received_ld) = T::__debit_view(env, amount_ld, min_amount_ld, dst_eid);
32
- MintBurnableClient::new(env, burnable).burn(sender, &amount_received_ld);
32
+ TokenClient::new(env, token).burn(sender, &amount_received_ld);
33
33
  (amount_sent_ld, amount_received_ld)
34
34
  }
35
35
 
@@ -37,7 +37,7 @@ pub fn debit<T: OFTCore>(
37
37
  ///
38
38
  /// # Parameters
39
39
  /// * `env` - The Soroban environment
40
- /// * `mintable` - Address of the contract responsible for minting tokens
40
+ /// * `mintable` - Address of the contract responsible for minting tokens (e.g. SAC wrapper)
41
41
  /// * `to` - Address of the token recipient
42
42
  /// * `amount_ld` - Amount to credit in local decimals
43
43
  /// * `_src_eid` - Source endpoint ID (unused)
@@ -45,6 +45,6 @@ pub fn debit<T: OFTCore>(
45
45
  /// # Returns
46
46
  /// The amount credited
47
47
  pub fn credit<T: OFTCore>(env: &Env, mintable: &Address, to: &Address, amount_ld: i128, _src_eid: u32) -> i128 {
48
- MintBurnableClient::new(env, mintable).mint(to, &amount_ld);
48
+ MintableClient::new(env, mintable).mint(to, &amount_ld, &env.current_contract_address());
49
49
  amount_ld
50
50
  }
@@ -4,9 +4,8 @@
4
4
  //!
5
5
  //! - **LockUnlock**: Locks tokens on send, unlocks on receive. Operates directly on the
6
6
  //! token via standard SEP-41 `transfer`.
7
- //! - **MintBurn**: Burns tokens on send, mints on receive. Operates on any contract
8
- //! that implements [`MintBurnable`](crate::interfaces::MintBurnable)
9
- //! whether it's a raw SAC or a token wrapper (e.g. SAC Manager).
7
+ //! - **MintBurn**: Burns tokens on send (via TokenClient on the token), mints on receive
8
+ //! via a contract that implements [`Mintable`](crate::interfaces::Mintable).
10
9
 
11
10
  use soroban_sdk::{contracttype, Address};
12
11
 
@@ -20,6 +19,6 @@ pub enum OftType {
20
19
  /// Lock tokens on send, unlock on receive.
21
20
  LockUnlock,
22
21
  /// Burn tokens on send, mint on receive.
23
- /// The address is the contract responsible for minting and burning tokens.
22
+ /// The address is the Mintable contract used for minting on credit
24
23
  MintBurn(Address),
25
24
  }
@@ -1,7 +1,9 @@
1
1
  extern crate std;
2
2
 
3
3
  use crate as oft;
4
- use crate::extensions::rate_limiter::{Direction, Mode, RateLimitConfig, RateLimitError, RateLimiter, RateLimiterInternal};
4
+ use crate::extensions::rate_limiter::{
5
+ Direction, Mode, RateLimitConfig, RateLimitError, RateLimiter, RateLimiterInternal,
6
+ };
5
7
  use soroban_sdk::{contract, contractimpl, testutils::Ledger as _, Address, Env};
6
8
  use utils::auth::Auth;
7
9
 
@@ -451,10 +453,10 @@ fn test_switching_from_net_to_gross_mode() {
451
453
 
452
454
  // Switch to Gross mode
453
455
  client.set_rate_limit(&direction, &eid, &Some(config_with_mode(100, 100, Mode::Gross)));
454
-
456
+
455
457
  // in-flight should be checkpointed
456
458
  assert_eq!(client.rate_limit_in_flight(&direction, &eid), 40);
457
-
459
+
458
460
  // Now releases should be no-op
459
461
  client.release(&direction, &eid, &20i128);
460
462
  assert_eq!(client.rate_limit_in_flight(&direction, &eid), 40, "Gross mode should not release");
@@ -474,10 +476,10 @@ fn test_switching_from_gross_to_net_mode() {
474
476
 
475
477
  // Switch to Net mode
476
478
  client.set_rate_limit(&direction, &eid, &Some(config_with_mode(100, 100, Mode::Net)));
477
-
479
+
478
480
  // in-flight should be checkpointed
479
481
  assert_eq!(client.rate_limit_in_flight(&direction, &eid), 60);
480
-
482
+
481
483
  // Now releases should work
482
484
  client.release(&direction, &eid, &20i128);
483
485
  assert_eq!(client.rate_limit_in_flight(&direction, &eid), 40, "Net mode should release");
@@ -0,0 +1,14 @@
1
+ //! General errors for the SAC Manager contract.
2
+
3
+ use common_macros::contract_error;
4
+
5
+ /// General SacManagerError: 3220-3249
6
+ #[contract_error]
7
+ pub enum SacManagerError {
8
+ /// The address is already a minter
9
+ AlreadyMinter,
10
+ /// Minter address not in the minters list (e.g. when deactivating)
11
+ MinterNotFound,
12
+ /// Caller is not authorized for this operation
13
+ Unauthorized,
14
+ }
@@ -1,12 +1,8 @@
1
1
  #![no_std]
2
2
 
3
- pub mod extensions;
4
-
5
3
  mod errors;
6
- mod interfaces;
7
4
 
8
5
  pub use errors::*;
9
- pub use interfaces::*;
10
6
 
11
7
  cfg_if::cfg_if! {
12
8
  // Include implementation when NOT in library mode, OR when testutils is enabled (for tests)