@layerzerolabs/protocol-stellar-v2 0.2.29 → 0.2.30

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 (205) hide show
  1. package/.turbo/turbo-build.log +371 -321
  2. package/.turbo/turbo-lint.log +211 -202
  3. package/.turbo/turbo-test.log +1766 -1673
  4. package/Cargo.lock +11 -1
  5. package/contracts/common-macros/src/lib.rs +0 -2
  6. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +1 -0
  7. package/contracts/endpoint-v2/src/messaging_channel.rs +32 -3
  8. package/contracts/endpoint-v2/src/tests/endpoint_setup.rs +1 -1
  9. package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +1 -1
  10. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +6 -6
  11. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_payload_hash.rs +1 -1
  12. package/contracts/endpoint-v2/src/tests/messaging_channel/outbound.rs +16 -10
  13. package/contracts/macro-integration-tests/tests/runtime/oapp/options_type3.rs +10 -10
  14. package/contracts/macro-integration-tests/tests/runtime/oapp/receiver.rs +3 -3
  15. package/contracts/macro-integration-tests/tests/runtime/oapp/sender.rs +4 -3
  16. package/contracts/macro-integration-tests/tests/runtime/upgradeable/migrate_guard_and_state.rs +1 -57
  17. package/contracts/macro-integration-tests/tests/ui/lz_contract/fail/upgradeable_missing_internal.stderr +0 -30
  18. package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.stderr +5 -3
  19. package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.stderr +6 -4
  20. package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.stderr +5 -3
  21. package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.stderr +5 -3
  22. package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_auth_trait.stderr +0 -30
  23. package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_upgradeable_internal.stderr +0 -30
  24. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/basic.rs +0 -2
  25. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/multisig_contract.rs +0 -2
  26. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/no_migration.rs +0 -2
  27. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/no_user_contractimpl.rs +1 -3
  28. package/contracts/message-libs/message-lib-common/src/packet_codec_v1.rs +3 -6
  29. package/contracts/message-libs/message-lib-common/src/tests/worker_options/extract_type_3_options.rs +10 -0
  30. package/contracts/message-libs/message-lib-common/src/worker_options.rs +6 -2
  31. package/contracts/message-libs/treasury/src/interfaces/zro_fee_lib.rs +3 -3
  32. package/contracts/message-libs/treasury/src/lib.rs +2 -1
  33. package/contracts/message-libs/treasury/src/tests/setup.rs +1 -1
  34. package/contracts/message-libs/treasury/src/treasury.rs +5 -2
  35. package/contracts/message-libs/uln-302/src/errors.rs +2 -0
  36. package/contracts/message-libs/uln-302/src/events.rs +3 -3
  37. package/contracts/message-libs/uln-302/src/interfaces/receive_uln.rs +8 -0
  38. package/contracts/message-libs/uln-302/src/lib.rs +2 -1
  39. package/contracts/message-libs/uln-302/src/receive_uln.rs +16 -13
  40. package/contracts/message-libs/uln-302/src/send_uln.rs +51 -24
  41. package/contracts/message-libs/uln-302/src/storage.rs +2 -2
  42. package/contracts/message-libs/uln-302/src/tests/receive_uln302/effective_receive_uln_config.rs +45 -1
  43. package/contracts/message-libs/uln-302/src/tests/receive_uln302/verifiable.rs +63 -0
  44. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_executor_config.rs +47 -2
  45. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_send_uln_config.rs +50 -1
  46. package/contracts/message-libs/uln-302/src/uln302.rs +0 -8
  47. package/contracts/oapps/counter/Cargo.toml +4 -4
  48. package/contracts/oapps/counter/integration_tests/setup_uln.rs +22 -2
  49. package/contracts/oapps/counter/src/counter.rs +8 -8
  50. package/contracts/oapps/oapp/src/interfaces/oapp_msg_inspector.rs +33 -10
  51. package/contracts/oapps/oapp/src/lib.rs +6 -2
  52. package/contracts/oapps/oapp/src/oapp_core.rs +49 -24
  53. package/contracts/oapps/oapp/src/oapp_options_type3.rs +21 -14
  54. package/contracts/oapps/oapp/src/oapp_receiver.rs +17 -16
  55. package/contracts/oapps/oapp/src/oapp_sender.rs +66 -15
  56. package/contracts/oapps/oapp/src/tests/oapp_core.rs +5 -5
  57. package/contracts/oapps/oapp/src/tests/oapp_options_type3.rs +18 -18
  58. package/contracts/oapps/oapp/src/tests/oapp_receiver.rs +4 -4
  59. package/contracts/oapps/oapp/src/tests/oapp_sender.rs +3 -3
  60. package/contracts/oapps/oapp-macros/Cargo.toml +0 -1
  61. package/contracts/oapps/oapp-macros/src/generators.rs +87 -46
  62. package/contracts/oapps/oapp-macros/src/lib.rs +3 -61
  63. package/contracts/oapps/oapp-macros/src/tests/oapp.rs +9 -23
  64. package/contracts/oapps/oapp-macros/src/tests/parse_custom_impls.rs +15 -11
  65. package/contracts/oapps/oft/Cargo.toml +1 -1
  66. package/contracts/oapps/oft/integration-tests/extensions/test_oft_fee.rs +3 -3
  67. package/contracts/oapps/oft/integration-tests/extensions/test_pausable.rs +4 -4
  68. package/contracts/oapps/oft/integration-tests/extensions/test_rate_limiter.rs +144 -8
  69. package/contracts/oapps/oft/integration-tests/setup.rs +4 -2
  70. package/contracts/oapps/oft/integration-tests/utils.rs +25 -11
  71. package/contracts/oapps/oft/src/extensions/oft_fee.rs +65 -63
  72. package/contracts/oapps/oft/src/extensions/pausable.rs +2 -3
  73. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +22 -5
  74. package/contracts/oapps/oft/src/interfaces/mint_burnable.rs +18 -0
  75. package/contracts/oapps/oft/src/interfaces/mod.rs +3 -0
  76. package/contracts/oapps/oft/src/lib.rs +4 -2
  77. package/contracts/oapps/oft/src/oft.rs +35 -36
  78. package/contracts/oapps/oft/src/oft_types/lock_unlock.rs +13 -9
  79. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +14 -9
  80. package/contracts/oapps/oft/src/oft_types/mod.rs +14 -12
  81. package/contracts/oapps/oft/src/tests/extensions/oft_fee.rs +28 -20
  82. package/contracts/oapps/oft/src/tests/extensions/rate_limiter.rs +136 -2
  83. package/contracts/oapps/oft/src/tests/oft_types/lock_unlock.rs +12 -8
  84. package/contracts/oapps/oft-core/integration-tests/setup.rs +8 -9
  85. package/contracts/oapps/oft-core/integration-tests/test_with_sml.rs +7 -6
  86. package/contracts/oapps/oft-core/integration-tests/utils.rs +5 -4
  87. package/contracts/oapps/oft-core/src/codec/oft_compose_msg_codec.rs +2 -2
  88. package/contracts/oapps/oft-core/src/codec/oft_msg_codec.rs +33 -37
  89. package/contracts/oapps/oft-core/src/errors.rs +2 -1
  90. package/contracts/oapps/oft-core/src/events.rs +6 -0
  91. package/contracts/oapps/oft-core/src/lib.rs +8 -4
  92. package/contracts/oapps/oft-core/src/oft_core.rs +205 -148
  93. package/contracts/oapps/oft-core/src/storage.rs +4 -2
  94. package/contracts/oapps/oft-core/src/tests/test_decimals.rs +2 -2
  95. package/contracts/oapps/oft-core/src/tests/test_lz_receive.rs +6 -6
  96. package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +7 -6
  97. package/contracts/oapps/oft-core/src/tests/test_oft_msg_codec.rs +11 -82
  98. package/contracts/oapps/oft-core/src/tests/test_quote_oft.rs +13 -13
  99. package/contracts/oapps/oft-core/src/tests/test_quote_send.rs +1 -1
  100. package/contracts/oapps/oft-core/src/tests/test_resolve_address.rs +2 -2
  101. package/contracts/oapps/oft-core/src/tests/test_send.rs +22 -22
  102. package/contracts/oapps/oft-core/src/tests/test_utils.rs +20 -22
  103. package/contracts/oapps/oft-core/src/utils.rs +12 -8
  104. package/contracts/sac-manager/Cargo.toml +25 -0
  105. package/contracts/sac-manager/src/errors.rs +18 -0
  106. package/contracts/sac-manager/src/extensions/mod.rs +6 -0
  107. package/contracts/sac-manager/src/extensions/redistribution.rs +109 -0
  108. package/contracts/sac-manager/src/extensions/supply_control/mod.rs +488 -0
  109. package/contracts/sac-manager/src/extensions/supply_control/rate_limit.rs +126 -0
  110. package/contracts/sac-manager/src/interfaces/mod.rs +3 -0
  111. package/contracts/sac-manager/src/interfaces/sac_manager.rs +52 -0
  112. package/contracts/sac-manager/src/lib.rs +23 -0
  113. package/contracts/sac-manager/src/sac_manager.rs +193 -0
  114. package/contracts/sac-manager/src/storage.rs +20 -0
  115. package/contracts/sac-manager/src/tests/mod.rs +14 -0
  116. package/contracts/sac-manager/src/tests/redistribution/mod.rs +1 -0
  117. package/contracts/sac-manager/src/tests/redistribution/redistribute_funds.rs +82 -0
  118. package/contracts/sac-manager/src/tests/sac_manager/admin_mint.rs +206 -0
  119. package/contracts/sac-manager/src/tests/sac_manager/burn.rs +215 -0
  120. package/contracts/sac-manager/src/tests/sac_manager/clawback.rs +209 -0
  121. package/contracts/sac-manager/src/tests/sac_manager/mint.rs +252 -0
  122. package/contracts/sac-manager/src/tests/sac_manager/mod.rs +9 -0
  123. package/contracts/sac-manager/src/tests/sac_manager/set_admin.rs +36 -0
  124. package/contracts/sac-manager/src/tests/sac_manager/set_authorized.rs +43 -0
  125. package/contracts/sac-manager/src/tests/sac_manager/set_oft_address.rs +47 -0
  126. package/contracts/sac-manager/src/tests/sac_manager/test_helper.rs +75 -0
  127. package/contracts/sac-manager/src/tests/sac_manager/view_functions.rs +60 -0
  128. package/contracts/sac-manager/src/tests/supply_control/enumerable_set.rs +256 -0
  129. package/contracts/sac-manager/src/tests/supply_control/mod.rs +8 -0
  130. package/contracts/sac-manager/src/tests/supply_control/refill.rs +90 -0
  131. package/contracts/sac-manager/src/tests/supply_control/set_mint_whitelist.rs +245 -0
  132. package/contracts/sac-manager/src/tests/supply_control/set_supply_controller.rs +267 -0
  133. package/contracts/sac-manager/src/tests/supply_control/set_supply_controller_manager.rs +122 -0
  134. package/contracts/sac-manager/src/tests/supply_control/test_helper.rs +38 -0
  135. package/contracts/sac-manager/src/tests/supply_control/update_allow_any_mint_burn.rs +114 -0
  136. package/contracts/sac-manager/src/tests/supply_control/update_limit_config.rs +257 -0
  137. package/contracts/sac-manager/src/tests/test_helper.rs +190 -0
  138. package/contracts/upgrader/src/lib.rs +2 -1
  139. package/contracts/utils/src/errors.rs +0 -1
  140. package/contracts/utils/src/tests/upgradeable.rs +0 -66
  141. package/contracts/utils/src/upgradeable.rs +0 -18
  142. package/contracts/workers/dvn/src/dvn.rs +2 -2
  143. package/contracts/workers/dvn/src/interfaces/dvn.rs +2 -2
  144. package/contracts/workers/dvn/src/lib.rs +2 -1
  145. package/contracts/workers/dvn-fee-lib/src/lib.rs +3 -1
  146. package/contracts/workers/executor/src/auth.rs +42 -26
  147. package/contracts/workers/executor/src/executor.rs +28 -3
  148. package/contracts/workers/executor/src/lib.rs +4 -2
  149. package/contracts/workers/executor/src/storage.rs +21 -1
  150. package/contracts/workers/executor/src/tests/auth.rs +64 -20
  151. package/contracts/workers/executor/src/tests/executor.rs +1 -1
  152. package/contracts/workers/executor/src/tests/setup.rs +18 -0
  153. package/contracts/workers/executor-fee-lib/src/lib.rs +4 -1
  154. package/contracts/workers/executor-helper/src/executor_helper.rs +24 -10
  155. package/contracts/workers/executor-helper/src/tests/setup.rs +147 -34
  156. package/contracts/workers/price-feed/src/lib.rs +3 -1
  157. package/contracts/workers/worker/src/lib.rs +2 -1
  158. package/contracts/workers/worker/src/worker.rs +31 -17
  159. package/docs/oapp-guide.md +17 -8
  160. package/docs/oft-guide.md +3 -3
  161. package/package.json +3 -3
  162. package/sdk/.turbo/turbo-test.log +512 -351
  163. package/sdk/dist/generated/bml.d.ts +3 -9
  164. package/sdk/dist/generated/bml.js +6 -7
  165. package/sdk/dist/generated/counter.d.ts +22 -28
  166. package/sdk/dist/generated/counter.js +11 -12
  167. package/sdk/dist/generated/dvn.d.ts +36 -54
  168. package/sdk/dist/generated/dvn.js +10 -15
  169. package/sdk/dist/generated/dvn_fee_lib.d.ts +3 -21
  170. package/sdk/dist/generated/dvn_fee_lib.js +6 -11
  171. package/sdk/dist/generated/endpoint.d.ts +3 -9
  172. package/sdk/dist/generated/endpoint.js +6 -7
  173. package/sdk/dist/generated/executor.d.ts +80 -54
  174. package/sdk/dist/generated/executor.js +16 -16
  175. package/sdk/dist/generated/executor_fee_lib.d.ts +3 -21
  176. package/sdk/dist/generated/executor_fee_lib.js +6 -11
  177. package/sdk/dist/generated/executor_helper.d.ts +36 -42
  178. package/sdk/dist/generated/executor_helper.js +9 -10
  179. package/sdk/dist/generated/layerzero_view.d.ts +20 -32
  180. package/sdk/dist/generated/layerzero_view.js +25 -26
  181. package/sdk/dist/generated/oft.d.ts +147 -79
  182. package/sdk/dist/generated/oft.js +47 -54
  183. package/sdk/dist/generated/price_feed.d.ts +20 -38
  184. package/sdk/dist/generated/price_feed.js +15 -20
  185. package/sdk/dist/generated/sac_manager.d.ts +1309 -0
  186. package/sdk/dist/generated/sac_manager.js +484 -0
  187. package/sdk/dist/generated/sml.d.ts +3 -9
  188. package/sdk/dist/generated/sml.js +6 -7
  189. package/sdk/dist/generated/treasury.d.ts +3 -9
  190. package/sdk/dist/generated/treasury.js +8 -9
  191. package/sdk/dist/generated/uln302.d.ts +20 -20
  192. package/sdk/dist/generated/uln302.js +25 -22
  193. package/sdk/dist/generated/upgrader.d.ts +3 -9
  194. package/sdk/dist/generated/upgrader.js +6 -7
  195. package/sdk/dist/index.d.ts +1 -0
  196. package/sdk/dist/index.js +1 -0
  197. package/sdk/package.json +1 -1
  198. package/sdk/src/index.ts +1 -0
  199. package/sdk/test/oft-sml.test.ts +7 -5
  200. package/sdk/test/sac-manager-redistribution.test.ts +578 -0
  201. package/sdk/test/suites/globalSetup.ts +11 -6
  202. package/sdk/test/test_data/test_upgradeable_dvn.wasm +0 -0
  203. package/sdk/test/upgrader.test.ts +75 -202
  204. package/sdk/test/utils.ts +40 -0
  205. package/tools/ts-bindings-gen/src/main.rs +1 -0
@@ -1,6 +1,6 @@
1
- use soroban_sdk::{address_payload::AddressPayload, assert_with_error, Address, BytesN, Env};
2
-
3
1
  use crate::errors::OFTError;
2
+ use soroban_sdk::{address_payload::AddressPayload, assert_with_error, Address, BytesN, Env};
3
+ use utils::option_ext::OptionExt;
4
4
 
5
5
  // =====================================================
6
6
  // OFT Helper Functions
@@ -38,18 +38,22 @@ pub fn remove_dust(amount_ld: i128, conversion_rate: i128) -> i128 {
38
38
  ///
39
39
  /// # Returns
40
40
  /// A 32-byte payload (contract ID hash or Ed25519 public key)
41
- pub fn address_payload(address: &Address) -> BytesN<32> {
42
- match address.to_payload().unwrap() {
41
+ pub fn address_payload(env: &Env, address: &Address) -> BytesN<32> {
42
+ match address.to_payload().unwrap_or_panic(env, OFTError::InvalidAddress) {
43
43
  AddressPayload::ContractIdHash(payload) => payload,
44
44
  AddressPayload::AccountIdPublicKeyEd25519(payload) => payload,
45
45
  }
46
46
  }
47
47
 
48
- /// Resolves a 32-byte payload to an address.
48
+ /// Resolves a 32-byte payload back to a Stellar address.
49
+ ///
50
+ /// Cross-chain messages only carry 32-byte addresses, but Stellar has two address types
51
+ /// (contract C-addresses and account G-addresses) that share the same 32-byte payload.
52
+ /// This function disambiguates by checking contract existence first, then falling back
53
+ /// to a G-address.
49
54
  ///
50
- /// This function attempts to convert the 32-byte payload to a contract address first.
51
- /// If the contract exists, it returns the contract address.
52
- /// If the contract doesn't exist, it falls back to creating a G-address (account address).
55
+ /// Sending tokens to a non-existent contract address is unlikely in practice — the sender
56
+ /// on the source chain is expected to deploy the destination contract beforehand.
53
57
  ///
54
58
  /// # Arguments
55
59
  /// * `env` - The Soroban environment
@@ -0,0 +1,25 @@
1
+ [package]
2
+ name = "sac-manager"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+
7
+ [lib]
8
+ crate-type = ["cdylib", "rlib"]
9
+ doctest = false
10
+
11
+ [features]
12
+ library = []
13
+ testutils = []
14
+
15
+ [dependencies]
16
+ soroban-sdk = { workspace = true }
17
+ utils = { workspace = true }
18
+ common-macros = { workspace = true }
19
+ cfg-if = { workspace = true }
20
+ oft = { workspace = true, features = ["library"] }
21
+
22
+ [dev-dependencies]
23
+ soroban-sdk = { workspace = true, features = ["testutils"] }
24
+ utils = { workspace = true, features = ["testutils"] }
25
+ oft = { workspace = true, features = ["testutils"] }
@@ -0,0 +1,18 @@
1
+ //! General errors for the SAC Manager contract.
2
+ //!
3
+ //! Note: Extension-specific errors are co-located with their modules:
4
+ //! - Supply control errors: `extensions/supply_control.rs` (SupplyControlError)
5
+ //! - Redistribution errors: `extensions/redistribution.rs` (RedistributionError)
6
+
7
+ use common_macros::contract_error;
8
+
9
+ /// General SacManagerError: 3220-3249
10
+ #[contract_error]
11
+ pub enum SacManagerError {
12
+ /// OFT address has not been set yet
13
+ OftAddressNotSet,
14
+ /// The values are the same
15
+ SameValue,
16
+ /// Caller is not authorized for this operation
17
+ Unauthorized,
18
+ }
@@ -0,0 +1,6 @@
1
+ //! Token Wrapper extensions.
2
+ //!
3
+ //! Each extension owns its own storage and provides a trait with default implementations.
4
+
5
+ pub mod redistribution;
6
+ pub mod supply_control;
@@ -0,0 +1,109 @@
1
+ //! Redistribution extension for handling assets associated with blacklisted addresses.
2
+
3
+ use crate::{interfaces::ISacManager, sac_manager::sac_client};
4
+ use common_macros::{contract_error, contract_trait, only_auth, storage};
5
+ use soroban_sdk::{assert_with_error, contractevent, Address, Env};
6
+ use utils::ownable::Ownable;
7
+
8
+ // =========================================================================
9
+ // Errors
10
+ // =========================================================================
11
+
12
+ #[contract_error]
13
+ pub enum RedistributionError {
14
+ /// Account is not blacklisted (required for redistribution)
15
+ NotBlacklisted = 100,
16
+ /// Redistribution is not enabled
17
+ RedistributionNotEnabled,
18
+ }
19
+
20
+ // =========================================================================
21
+ // Events
22
+ // =========================================================================
23
+
24
+ /// Event emitted when funds are redistributed from a blacklisted account.
25
+ #[contractevent]
26
+ #[derive(Clone, Debug, Eq, PartialEq)]
27
+ pub struct RedistributeFunds {
28
+ pub user: Address,
29
+ pub amount: i128,
30
+ }
31
+
32
+ // =========================================================================
33
+ // Storage
34
+ // =========================================================================
35
+
36
+ #[storage]
37
+ pub enum RedistributionStorage {
38
+ /// Whether redistribution is enabled.
39
+ /// Note: variant name must be globally unique across all storage enums to avoid
40
+ /// Soroban storage key collisions (contracttype serializes by variant name hash).
41
+ #[instance(bool)]
42
+ RedistributionEnabled,
43
+ }
44
+
45
+ // =========================================================================
46
+ // Redistribution Trait
47
+ // =========================================================================
48
+
49
+ /// Redistribution trait for handling transfers to blacklisted addresses.
50
+ /// Uses SAC's authorization system - blacklisted accounts have authorized=false.
51
+ ///
52
+ /// Only the contract owner can redistribute funds.
53
+ /// The redistribution target is always the contract owner.
54
+ #[contract_trait(client_name = "RedistributionClient")]
55
+ pub trait Redistribution: ISacManager + RedistributionInternal {
56
+ // =========================================================================
57
+ // Redistribution Operations
58
+ // =========================================================================
59
+
60
+ /// Redistributes funds from a blacklisted account to the owner.
61
+ #[only_auth]
62
+ fn redistribute_blacklisted_funds(env: &Env, from: &Address, amount: i128) {
63
+ assert_with_error!(env, Self::__redistribution_enabled(env), RedistributionError::RedistributionNotEnabled);
64
+
65
+ let sac_client = sac_client::<Self>(env);
66
+
67
+ // Require `from` to be blacklisted (not authorized on SAC)
68
+ assert_with_error!(env, !sac_client.authorized(from), RedistributionError::NotBlacklisted);
69
+
70
+ // Transfer from blacklisted account to owner using clawback + mint
71
+ let owner = Self::owner(env).unwrap();
72
+ sac_client.clawback(from, &amount);
73
+ sac_client.mint(&owner, &amount);
74
+
75
+ RedistributeFunds { user: from.clone(), amount }.publish(env);
76
+ }
77
+
78
+ // =========================================================================
79
+ // View Functions
80
+ // =========================================================================
81
+
82
+ fn redistribution_enabled(env: &Env) -> bool {
83
+ Self::__redistribution_enabled(env)
84
+ }
85
+ }
86
+
87
+ /// Internal trait for redistribution operations.
88
+ pub trait RedistributionInternal: ISacManager + Ownable {
89
+ /// Redistributes funds from a blacklisted account to the owner.
90
+ fn __redistribute_funds(env: &Env, to: &Address, amount: i128) -> Address {
91
+ // If redistribution is enabled and the address is not authorized, redistribute the funds to the owner.
92
+ if Self::__redistribution_enabled(env) && !sac_client::<Self>(env).authorized(to) {
93
+ RedistributeFunds { user: to.clone(), amount }.publish(env);
94
+ Self::owner(env).unwrap()
95
+ } else {
96
+ to.clone()
97
+ }
98
+ }
99
+
100
+ /// Returns whether redistribution is enabled.
101
+ fn __redistribution_enabled(env: &Env) -> bool {
102
+ RedistributionStorage::has_redistribution_enabled(env)
103
+ }
104
+
105
+ /// Enables redistribution. only call this function at construction time.
106
+ fn __enable_redistribution(env: &Env) {
107
+ RedistributionStorage::set_redistribution_enabled(env, &true);
108
+ }
109
+ }
@@ -0,0 +1,488 @@
1
+ //! Supply control extension for rate-limited minting.
2
+ //!
3
+ //! Provides rate-limited minting with per-controller capacity tracking
4
+ //! and optional mint whitelist enforcement.
5
+ //!
6
+ //! Config and rate limit state are nested in the same struct for atomic storage,
7
+ //! but logically separated so config updates don't reset inflight state.
8
+
9
+ pub(crate) mod rate_limit;
10
+
11
+ use common_macros::{contract_error, contract_trait, only_auth, storage};
12
+ use rate_limit::{get_remaining_amount, try_consume};
13
+ pub use rate_limit::{LimitConfig, RateLimitState};
14
+ use soroban_sdk::{assert_with_error, contractevent, contracttype, panic_with_error, Address, Env, Vec};
15
+ use utils::{option_ext::OptionExt, ownable::Ownable};
16
+
17
+ // =========================================================================
18
+ // Errors
19
+ // =========================================================================
20
+
21
+ /// Supply control errors: 3250-3299
22
+ #[contract_error]
23
+ pub enum SupplyControlError {
24
+ /// Entry already exists (supply controller, manager, or whitelist address)
25
+ AlreadyExists = 200,
26
+ /// Cannot burn from this address
27
+ CannotBurnFromAddress,
28
+ /// Cannot mint to this address (not in whitelist)
29
+ CannotMintToAddress,
30
+ /// Invalid amount
31
+ InvalidAmount,
32
+ /// Invalid configuration
33
+ InvalidConfig,
34
+ /// Entry not found (supply controller, manager, or whitelist address)
35
+ NotFound,
36
+ /// Timestamp is older than the last refill time
37
+ OldTimestamp,
38
+ /// Rate limit exceeded
39
+ RateLimitExceeded,
40
+ /// Value is already set to the same state
41
+ SameValue,
42
+ /// Caller is not authorized (not a supply controller or manager)
43
+ Unauthorized,
44
+ }
45
+
46
+ // =========================================================================
47
+ // Events
48
+ // =========================================================================
49
+
50
+ /// Event emitted when a supply controller is added.
51
+ #[contractevent]
52
+ #[derive(Clone, Debug, Eq, PartialEq)]
53
+ pub struct SupplyControllerAdded {
54
+ pub supply_controller: Address,
55
+ pub config: SupplyControllerConfig,
56
+ }
57
+
58
+ /// Event emitted when a supply controller is removed.
59
+ #[contractevent]
60
+ #[derive(Clone, Debug, Eq, PartialEq)]
61
+ pub struct SupplyControllerRemoved {
62
+ pub supply_controller: Address,
63
+ }
64
+
65
+ /// Event emitted when limit configuration is updated for a supply controller.
66
+ #[contractevent]
67
+ #[derive(Clone, Debug, Eq, PartialEq)]
68
+ pub struct LimitConfigUpdated {
69
+ pub supply_controller: Address,
70
+ pub new_limit_config: LimitConfig,
71
+ pub old_limit_config: LimitConfig,
72
+ }
73
+
74
+ /// Event emitted when `allow_any_mint_burn` is updated for a supply controller.
75
+ #[contractevent]
76
+ #[derive(Clone, Debug, Eq, PartialEq)]
77
+ pub struct AllowAnyMintBurnUpdated {
78
+ pub supply_controller: Address,
79
+ pub new_allow: bool,
80
+ pub old_allow: bool,
81
+ }
82
+
83
+ /// Event emitted when a mint whitelist entry is added or removed.
84
+ #[contractevent]
85
+ #[derive(Clone, Debug, Eq, PartialEq)]
86
+ pub struct MintWhitelistSet {
87
+ pub supply_controller: Address,
88
+ pub mint_address: Address,
89
+ pub active: bool,
90
+ }
91
+
92
+ /// Event emitted when a supply controller manager is added or removed.
93
+ #[contractevent]
94
+ #[derive(Clone, Debug, Eq, PartialEq)]
95
+ pub struct SupplyCtrlManagerSet {
96
+ pub manager: Address,
97
+ pub active: bool,
98
+ }
99
+
100
+ /// Event emitted when tokens are minted.
101
+ #[contractevent]
102
+ #[derive(Clone, Debug, Eq, PartialEq)]
103
+ pub struct SupplyIncreased {
104
+ pub controller: Address,
105
+ pub to: Address,
106
+ pub amount: i128,
107
+ }
108
+
109
+ /// Event emitted when tokens are burned.
110
+ #[contractevent]
111
+ #[derive(Clone, Debug, Eq, PartialEq)]
112
+ pub struct SupplyDecreased {
113
+ pub controller: Address,
114
+ pub from: Address,
115
+ pub amount: i128,
116
+ }
117
+
118
+ // =========================================================================
119
+ // Types
120
+ // =========================================================================
121
+
122
+ /// Supply controller configuration (the configurable parameters).
123
+ #[contracttype]
124
+ #[derive(Clone, Debug, Eq, PartialEq)]
125
+ pub struct SupplyControllerConfig {
126
+ /// Limit configuration
127
+ pub limit_config: LimitConfig,
128
+ /// If true, allows the supply controller to mint to and burn from any address.
129
+ /// When false, the controller can only mint to addresses in its whitelist.
130
+ pub allow_any_mint_burn: bool,
131
+ /// Addresses that this supply controller is allowed to mint to (when `allow_any_mint_burn` is false).
132
+ pub whitelist_addresses: Vec<Address>,
133
+ }
134
+
135
+ /// Supply controller with both config and state nested together.
136
+ /// Stored per-controller for O(1) access on every mint/burn.
137
+ #[contracttype]
138
+ #[derive(Clone, Debug, Eq, PartialEq)]
139
+ pub struct SupplyControllerEntry {
140
+ /// Configuration (can be updated independently)
141
+ pub config: SupplyControllerConfig,
142
+ /// Rate limit state (preserved across config updates)
143
+ pub state: RateLimitState,
144
+ }
145
+
146
+ // =========================================================================
147
+ // Storage
148
+ // =========================================================================
149
+
150
+ #[storage]
151
+ pub enum SupplyControlStorage {
152
+ /// Whether supply control is enabled.
153
+ /// Note: variant name must be globally unique across all storage enums to avoid
154
+ /// Soroban storage key collisions (contracttype serializes by variant name hash).
155
+ #[instance(bool)]
156
+ SupplyControlEnabled,
157
+
158
+ /// Per-controller entry: config + rate limit state.
159
+ /// Keyed by controller address for O(1) access on every mint/burn.
160
+ #[persistent(SupplyControllerEntry)]
161
+ SupplyControllerEntry { controller: Address },
162
+
163
+ /// All supply controller addresses.
164
+ /// Stored as a single Vec since the number of controllers is expected to be small.
165
+ #[persistent(Vec<Address>)]
166
+ #[default(Vec::new(env))]
167
+ SupplyControllers,
168
+
169
+ /// All supply controller manager addresses.
170
+ /// Stored as a single Vec since the number of managers is expected to be small.
171
+ #[persistent(Vec<Address>)]
172
+ #[default(Vec::new(env))]
173
+ SupplyControllerManagers,
174
+ }
175
+
176
+ // =========================================================================
177
+ // Supply Control Trait
178
+ // =========================================================================
179
+
180
+ /// Supply control trait for managing rate-limited minting.
181
+ ///
182
+ /// ## Authorization Model
183
+ /// - **Owner**: Can add/remove supply controller managers.
184
+ /// - **Supply Controller Manager**: Can add/remove/update supply controllers.
185
+ /// - **Supply Controller**: Can mint and burn tokens.
186
+ ///
187
+ /// Note: Global enable/disable is managed by the SacManager contract directly.
188
+ #[contract_trait(client_name = "SupplyControlClient")]
189
+ pub trait SupplyControl: Ownable + SupplyControlInternal {
190
+ // =========================================================================
191
+ // Supply Controller Manager Management (owner-only)
192
+ // =========================================================================
193
+
194
+ /// Sets whether an address is a supply controller manager. Owner-only.
195
+ #[only_auth]
196
+ fn set_supply_controller_manager(env: &Env, manager: &Address, active: bool) {
197
+ let mut managers = Self::get_supply_controller_managers(env);
198
+
199
+ let idx = managers.first_index_of(manager);
200
+ if active {
201
+ assert_with_error!(env, idx.is_none(), SupplyControlError::AlreadyExists);
202
+ managers.push_back(manager.clone());
203
+ } else {
204
+ assert_with_error!(env, idx.is_some(), SupplyControlError::NotFound);
205
+ managers.remove(idx.unwrap());
206
+ }
207
+ SupplyControlStorage::set_supply_controller_managers(env, &managers);
208
+
209
+ SupplyCtrlManagerSet { manager: manager.clone(), active }.publish(env);
210
+ }
211
+
212
+ // =========================================================================
213
+ // Supply Controller Management (supply controller manager-only)
214
+ // =========================================================================
215
+
216
+ /// Sets or removes a supply controller. Manager-only.
217
+ ///
218
+ /// - `Some(config)`: Adds a new supply controller with the given configuration.
219
+ /// - `None`: Removes the supply controller.
220
+ fn set_supply_controller(
221
+ env: &Env,
222
+ sender: &Address,
223
+ supply_controller: &Address,
224
+ config: &Option<SupplyControllerConfig>,
225
+ ) {
226
+ require_manager_auth::<Self>(env, sender);
227
+
228
+ if let Some(config) = config {
229
+ Self::__add_supply_controller(env, supply_controller, config);
230
+ } else {
231
+ Self::__remove_supply_controller(env, supply_controller);
232
+ }
233
+ }
234
+
235
+ /// Updates the rate limit configuration for a supply controller. Manager-only.
236
+ /// Note: This does NOT reset the rate limit state (remaining_amount, last_refill_time).
237
+ fn update_limit_config(env: &Env, sender: &Address, supply_controller: &Address, limit_config: &LimitConfig) {
238
+ require_manager_auth::<Self>(env, sender);
239
+
240
+ validate_limit_config(env, limit_config);
241
+
242
+ let mut sc = Self::__get_entry_or_panic(env, supply_controller);
243
+ let old_limit_config = sc.config.limit_config.clone();
244
+ assert_with_error!(env, old_limit_config != *limit_config, SupplyControlError::SameValue);
245
+ sc.config.limit_config = limit_config.clone();
246
+ SupplyControlStorage::set_supply_controller_entry(env, supply_controller, &sc);
247
+
248
+ LimitConfigUpdated {
249
+ supply_controller: supply_controller.clone(),
250
+ new_limit_config: limit_config.clone(),
251
+ old_limit_config,
252
+ }
253
+ .publish(env);
254
+ }
255
+
256
+ /// Updates whether the supply controller can mint to and burn from any address. Manager-only.
257
+ fn update_allow_any_mint_burn(env: &Env, sender: &Address, supply_controller: &Address, allow_any_mint_burn: bool) {
258
+ require_manager_auth::<Self>(env, sender);
259
+
260
+ let mut sc = Self::__get_entry_or_panic(env, supply_controller);
261
+ let old_allow = sc.config.allow_any_mint_burn;
262
+ assert_with_error!(env, old_allow != allow_any_mint_burn, SupplyControlError::SameValue);
263
+ sc.config.allow_any_mint_burn = allow_any_mint_burn;
264
+ SupplyControlStorage::set_supply_controller_entry(env, supply_controller, &sc);
265
+
266
+ AllowAnyMintBurnUpdated {
267
+ supply_controller: supply_controller.clone(),
268
+ new_allow: allow_any_mint_burn,
269
+ old_allow,
270
+ }
271
+ .publish(env);
272
+ }
273
+
274
+ /// Sets whether an address is in the mint whitelist of a supply controller. Manager-only.
275
+ fn set_mint_whitelist(
276
+ env: &Env,
277
+ sender: &Address,
278
+ supply_controller: &Address,
279
+ mint_address: &Address,
280
+ active: bool,
281
+ ) {
282
+ require_manager_auth::<Self>(env, sender);
283
+
284
+ let mut sc = Self::__get_entry_or_panic(env, supply_controller);
285
+
286
+ let idx = sc.config.whitelist_addresses.first_index_of(mint_address);
287
+ if active {
288
+ assert_with_error!(env, idx.is_none(), SupplyControlError::AlreadyExists);
289
+ sc.config.whitelist_addresses.push_back(mint_address.clone());
290
+ } else {
291
+ assert_with_error!(env, idx.is_some(), SupplyControlError::NotFound);
292
+ sc.config.whitelist_addresses.remove(idx.unwrap());
293
+ }
294
+
295
+ SupplyControlStorage::set_supply_controller_entry(env, supply_controller, &sc);
296
+
297
+ MintWhitelistSet { supply_controller: supply_controller.clone(), mint_address: mint_address.clone(), active }
298
+ .publish(env);
299
+ }
300
+
301
+ // =========================================================================
302
+ // View Functions
303
+ // =========================================================================
304
+
305
+ /// Returns whether the given address is a supply controller manager.
306
+ fn is_supply_controller_manager(env: &Env, address: &Address) -> bool {
307
+ Self::get_supply_controller_managers(env).first_index_of(address).is_some()
308
+ }
309
+
310
+ /// Gets all supply controller manager addresses.
311
+ fn get_supply_controller_managers(env: &Env) -> Vec<Address> {
312
+ SupplyControlStorage::supply_controller_managers(env)
313
+ }
314
+
315
+ /// Gets supply controller configuration.
316
+ fn get_supply_controller_config(env: &Env, supply_controller: &Address) -> Option<SupplyControllerConfig> {
317
+ SupplyControlStorage::supply_controller_entry(env, supply_controller).map(|sc| sc.config)
318
+ }
319
+
320
+ fn allow_any_mint_burn(env: &Env, supply_controller: &Address) -> bool {
321
+ SupplyControlStorage::supply_controller_entry(env, supply_controller)
322
+ .map(|sc| sc.config.allow_any_mint_burn)
323
+ .unwrap_or(false)
324
+ }
325
+
326
+ /// Gets all active supply controller addresses.
327
+ fn get_supply_controllers(env: &Env) -> Vec<Address> {
328
+ SupplyControlStorage::supply_controllers(env)
329
+ }
330
+
331
+ /// Returns whether the given address is in the whitelist of a supply controller.
332
+ fn is_address_whitelisted(env: &Env, supply_controller: &Address, mint_address: &Address) -> bool {
333
+ SupplyControlStorage::supply_controller_entry(env, supply_controller)
334
+ .map(|sc| sc.config.whitelist_addresses.contains(mint_address))
335
+ .unwrap_or(false)
336
+ }
337
+
338
+ /// Gets remaining amount which can be minted at current timestamp.
339
+ fn get_remaining_mint_amount(env: &Env, supply_controller: &Address) -> i128 {
340
+ let Some(sc) = SupplyControlStorage::supply_controller_entry(env, supply_controller) else {
341
+ return 0;
342
+ };
343
+
344
+ get_remaining_amount(&sc.config.limit_config, &sc.state, env.ledger().timestamp())
345
+ .unwrap_or_else(|e| panic_with_error!(env, e))
346
+ }
347
+
348
+ fn supply_control_enabled(env: &Env) -> bool {
349
+ Self::__supply_control_enabled(env)
350
+ }
351
+ }
352
+
353
+ // =========================================================================
354
+ // Internal Trait
355
+ // =========================================================================
356
+
357
+ /// Internal methods for supply control operations.
358
+ /// Separated from the public `SupplyControl` trait for encapsulation.
359
+ pub trait SupplyControlInternal {
360
+ /// Enforces mint supply controls: authorization, whitelist, rate limit, and state update.
361
+ /// Panics on failure.
362
+ fn __enforce_mint(env: &Env, sender: &Address, mint_to: &Address, amount: i128, fallback_auth: impl Fn(&Address)) {
363
+ if !Self::__supply_control_enabled(env) {
364
+ fallback_auth(sender);
365
+ return;
366
+ }
367
+
368
+ let mut sc = Self::__get_entry_or_panic(env, sender);
369
+
370
+ assert_with_error!(
371
+ env,
372
+ sc.config.allow_any_mint_burn || sc.config.whitelist_addresses.contains(mint_to),
373
+ SupplyControlError::CannotMintToAddress
374
+ );
375
+
376
+ try_consume(&sc.config.limit_config, &mut sc.state, env.ledger().timestamp(), amount)
377
+ .unwrap_or_else(|e| panic_with_error!(env, e));
378
+ SupplyControlStorage::set_supply_controller_entry(env, sender, &sc);
379
+
380
+ SupplyIncreased { controller: sender.clone(), to: mint_to.clone(), amount }.publish(env);
381
+ }
382
+
383
+ /// Enforces burn supply controls: authorization and burn permissions.
384
+ /// Panics on failure.
385
+ fn __enforce_burn(
386
+ env: &Env,
387
+ sender: &Address,
388
+ burn_from: &Address,
389
+ amount: i128,
390
+ fallback_auth: impl Fn(&Address),
391
+ ) {
392
+ if !Self::__supply_control_enabled(env) {
393
+ fallback_auth(sender);
394
+ return;
395
+ }
396
+
397
+ assert_with_error!(
398
+ env,
399
+ Self::__get_entry_or_panic(env, sender).config.allow_any_mint_burn,
400
+ SupplyControlError::CannotBurnFromAddress
401
+ );
402
+
403
+ SupplyDecreased { controller: sender.clone(), from: burn_from.clone(), amount }.publish(env);
404
+ }
405
+
406
+ /// Enables supply control. only call this function at construction time.
407
+ fn __enable_supply_control(env: &Env) {
408
+ SupplyControlStorage::set_supply_control_enabled(env, &true);
409
+ }
410
+
411
+ /// Adds a new supply controller with the given configuration.
412
+ fn __add_supply_controller(env: &Env, supply_controller: &Address, config: &SupplyControllerConfig) {
413
+ assert_with_error!(
414
+ env,
415
+ !SupplyControlStorage::has_supply_controller_entry(env, supply_controller),
416
+ SupplyControlError::AlreadyExists
417
+ );
418
+
419
+ validate_limit_config(env, &config.limit_config);
420
+
421
+ // set the initial state of the supply controller
422
+ let init_state = RateLimitState {
423
+ remaining_amount: config.limit_config.limit_capacity,
424
+ last_refill_time: env.ledger().timestamp(),
425
+ };
426
+ let entry = SupplyControllerEntry { config: config.clone(), state: init_state };
427
+ SupplyControlStorage::set_supply_controller_entry(env, supply_controller, &entry);
428
+
429
+ // add the supply controller to the list of supply controllers
430
+ let mut addresses = SupplyControlStorage::supply_controllers(env);
431
+ addresses.push_back(supply_controller.clone());
432
+ SupplyControlStorage::set_supply_controllers(env, &addresses);
433
+
434
+ SupplyControllerAdded { supply_controller: supply_controller.clone(), config: config.clone() }.publish(env);
435
+ }
436
+
437
+ /// Removes an existing supply controller.
438
+ fn __remove_supply_controller(env: &Env, supply_controller: &Address) {
439
+ assert_with_error!(
440
+ env,
441
+ SupplyControlStorage::has_supply_controller_entry(env, supply_controller),
442
+ SupplyControlError::NotFound
443
+ );
444
+
445
+ // remove the supply controller from the list of supply controllers
446
+ SupplyControlStorage::remove_supply_controller_entry(env, supply_controller);
447
+
448
+ // remove the supply controller from the list of supply controllers
449
+ let mut addresses = SupplyControlStorage::supply_controllers(env);
450
+ let idx = addresses.first_index_of(supply_controller).unwrap();
451
+ addresses.remove(idx);
452
+ SupplyControlStorage::set_supply_controllers(env, &addresses);
453
+
454
+ SupplyControllerRemoved { supply_controller: supply_controller.clone() }.publish(env);
455
+ }
456
+
457
+ // =========================================================================
458
+ // View Functions
459
+ // =========================================================================
460
+
461
+ fn __supply_control_enabled(env: &Env) -> bool {
462
+ SupplyControlStorage::has_supply_control_enabled(env)
463
+ }
464
+
465
+ /// Loads a supply controller entry, panicking with `NotFound` if it doesn't exist.
466
+ fn __get_entry_or_panic(env: &Env, controller: &Address) -> SupplyControllerEntry {
467
+ SupplyControlStorage::supply_controller_entry(env, controller)
468
+ .unwrap_or_panic(env, SupplyControlError::NotFound)
469
+ }
470
+ }
471
+
472
+ // =========================================================================
473
+ // Helper Functions
474
+ // =========================================================================
475
+
476
+ fn require_manager_auth<T: SupplyControl>(env: &Env, sender: &Address) {
477
+ sender.require_auth();
478
+ assert_with_error!(env, T::is_supply_controller_manager(env, sender), SupplyControlError::Unauthorized);
479
+ }
480
+
481
+ /// Validates that limit_capacity and refill_per_second are non-negative.
482
+ fn validate_limit_config(env: &Env, limit_config: &LimitConfig) {
483
+ assert_with_error!(
484
+ env,
485
+ limit_config.limit_capacity >= 0 && limit_config.refill_per_second >= 0,
486
+ SupplyControlError::InvalidConfig
487
+ );
488
+ }