@layerzerolabs/protocol-stellar-v2 0.2.10 → 0.2.11

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 (83) hide show
  1. package/.turbo/turbo-build.log +245 -199
  2. package/.turbo/turbo-lint.log +79 -107
  3. package/.turbo/turbo-test.log +1017 -841
  4. package/Cargo.lock +13 -5
  5. package/contracts/common-macros/src/contract_impl.rs +6 -3
  6. package/contracts/common-macros/src/error.rs +9 -17
  7. package/contracts/common-macros/src/event.rs +4 -4
  8. package/contracts/common-macros/src/lib.rs +2 -2
  9. package/contracts/common-macros/src/ownable.rs +9 -5
  10. package/contracts/common-macros/src/tests/contract_impl.rs +178 -86
  11. package/contracts/common-macros/src/tests/error.rs +168 -0
  12. package/contracts/common-macros/src/tests/mod.rs +2 -4
  13. package/contracts/common-macros/src/tests/ownable.rs +37 -60
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_impl__snapshot_generated_contract_impl_code.snap +16 -6
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
  16. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_generated_ownable_code.snap +3 -1
  17. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_only_owner_preserves_function_signature.snap +12 -2
  18. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +5 -1
  19. package/contracts/common-macros/src/tests/utils.rs +267 -0
  20. package/contracts/common-macros/src/ttl_configurable.rs +15 -12
  21. package/contracts/common-macros/src/utils.rs +35 -6
  22. package/contracts/message-libs/uln-302/src/receive_uln.rs +1 -1
  23. package/contracts/message-libs/uln-302/src/send_uln.rs +2 -2
  24. package/contracts/message-libs/uln-302/src/uln302.rs +2 -2
  25. package/contracts/oapp-macros/src/oapp_core.rs +1 -1
  26. package/contracts/oapps/oft/integration-tests/setup.rs +4 -3
  27. package/contracts/oapps/oft/src/default_oft_impl.rs +146 -0
  28. package/contracts/oapps/oft/src/extensions/mod.rs +3 -0
  29. package/contracts/oapps/oft/src/extensions/oft_fee.rs +164 -0
  30. package/contracts/oapps/oft/src/extensions/pausable.rs +50 -0
  31. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +198 -0
  32. package/contracts/oapps/oft/src/lib.rs +2 -3
  33. package/contracts/oapps/oft/src/oft.rs +16 -85
  34. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
  35. package/contracts/oapps/oft/src/tests/extensions/mod.rs +11 -0
  36. package/contracts/oapps/oft/src/tests/extensions/setup.rs +888 -0
  37. package/contracts/oapps/oft/src/tests/extensions/test_oft_fee.rs +749 -0
  38. package/contracts/oapps/oft/src/tests/extensions/test_pausable.rs +432 -0
  39. package/contracts/oapps/oft/src/tests/extensions/test_rate_limiter.rs +1078 -0
  40. package/contracts/oapps/oft/src/tests/mod.rs +2 -0
  41. package/contracts/oapps/oft/src/tests/test_utils.rs +24 -6
  42. package/contracts/oapps/{oft-mint-burn → oft-std}/Cargo.toml +1 -8
  43. package/contracts/oapps/oft-std/src/lib.rs +5 -0
  44. package/contracts/oapps/oft-std/src/oft.rs +59 -0
  45. package/contracts/utils/src/ownable.rs +2 -2
  46. package/contracts/utils/src/tests/ownable.rs +0 -63
  47. package/contracts/utils/src/ttl.rs +19 -1
  48. package/contracts/workers/dvn/src/auth.rs +91 -27
  49. package/contracts/workers/dvn/src/dvn.rs +22 -20
  50. package/contracts/workers/dvn/src/interfaces/dvn.rs +48 -3
  51. package/contracts/workers/dvn/src/interfaces/multisig.rs +41 -0
  52. package/contracts/workers/dvn/src/lib.rs +6 -8
  53. package/contracts/workers/dvn/src/multisig.rs +6 -3
  54. package/contracts/workers/dvn/src/tests/auth.rs +1 -1
  55. package/contracts/workers/dvn/src/tests/dvn.rs +3 -4
  56. package/contracts/workers/dvn-fee-lib/Cargo.toml +2 -1
  57. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +4 -3
  58. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +8 -6
  59. package/contracts/workers/executor/src/interfaces/executor.rs +5 -2
  60. package/contracts/workers/executor/src/lz_executor.rs +6 -6
  61. package/contracts/workers/price-feed/Cargo.toml +21 -0
  62. package/contracts/workers/price-feed/src/errors.rs +9 -0
  63. package/contracts/workers/price-feed/src/events.rs +30 -0
  64. package/contracts/workers/price-feed/src/lib.rs +11 -0
  65. package/contracts/workers/price-feed/src/price_feed.rs +265 -0
  66. package/contracts/workers/price-feed/src/storage.rs +42 -0
  67. package/contracts/workers/price-feed/src/types.rs +59 -0
  68. package/contracts/workers/worker/src/interfaces/dvn_fee_lib.rs +2 -1
  69. package/package.json +3 -3
  70. package/sdk/dist/generated/bml.js +3 -1
  71. package/sdk/dist/generated/counter.d.ts +102 -0
  72. package/sdk/dist/generated/counter.js +13 -1
  73. package/sdk/dist/generated/endpoint.js +3 -1
  74. package/sdk/dist/generated/sml.js +3 -1
  75. package/sdk/dist/generated/uln302.js +3 -1
  76. package/sdk/package.json +1 -1
  77. package/contracts/oapps/oft/src/macro_tests/mod.rs +0 -2
  78. package/contracts/oapps/oft/src/macro_tests/test_all_default.rs +0 -41
  79. package/contracts/oapps/oft/src/macro_tests/test_override.rs +0 -83
  80. package/contracts/oapps/oft-mint-burn/src/lib.rs +0 -3
  81. package/contracts/oapps/oft-mint-burn/src/oft.rs +0 -28
  82. package/contracts/oapps/oft-mint-burn/src/tests/mod.rs +0 -1
  83. package/contracts/workers/dvn/src/types.rs +0 -26
@@ -11,3 +11,5 @@ pub mod test_quote_send;
11
11
  pub mod test_resolve_address;
12
12
  pub mod test_send;
13
13
  pub mod test_token;
14
+
15
+ pub mod extensions;
@@ -110,8 +110,15 @@ pub struct TestMintBurnOFT;
110
110
 
111
111
  #[contractimpl]
112
112
  impl TestMintBurnOFT {
113
- pub fn __constructor(env: &Env, token: &Address, owner: &Address, endpoint: &Address, delegate: &Option<Address>) {
114
- oft_initialize::<Self>(env, owner, token, endpoint, delegate)
113
+ pub fn __constructor(
114
+ env: &Env,
115
+ token: &Address,
116
+ owner: &Address,
117
+ endpoint: &Address,
118
+ delegate: &Option<Address>,
119
+ shared_decimals: u32,
120
+ ) {
121
+ oft_initialize::<Self>(env, owner, token, endpoint, delegate, shared_decimals)
115
122
  }
116
123
  }
117
124
 
@@ -134,8 +141,15 @@ pub struct TestLockUnlockOFT;
134
141
 
135
142
  #[contractimpl]
136
143
  impl TestLockUnlockOFT {
137
- pub fn __constructor(env: &Env, token: &Address, owner: &Address, endpoint: &Address, delegate: &Option<Address>) {
138
- oft_initialize::<Self>(env, owner, token, endpoint, delegate)
144
+ pub fn __constructor(
145
+ env: &Env,
146
+ token: &Address,
147
+ owner: &Address,
148
+ endpoint: &Address,
149
+ delegate: &Option<Address>,
150
+ shared_decimals: u32,
151
+ ) {
152
+ oft_initialize::<Self>(env, owner, token, endpoint, delegate, shared_decimals)
139
153
  }
140
154
  }
141
155
 
@@ -477,8 +491,12 @@ impl<'a> OFTTestSetupBuilder<'a> {
477
491
  // Register OFT based on type
478
492
  let delegate: Option<Address> = Some(owner.clone());
479
493
  let oft_address = match oft_type {
480
- OFTType::MintBurn => env.register(TestMintBurnOFT, (&token, &owner, &endpoint_address, &delegate)),
481
- OFTType::LockUnlock => env.register(TestLockUnlockOFT, (&token, &owner, &endpoint_address, &delegate)),
494
+ OFTType::MintBurn => {
495
+ env.register(TestMintBurnOFT, (&token, &owner, &endpoint_address, &delegate, &self.shared_decimals))
496
+ }
497
+ OFTType::LockUnlock => {
498
+ env.register(TestLockUnlockOFT, (&token, &owner, &endpoint_address, &delegate, &self.shared_decimals))
499
+ }
482
500
  };
483
501
  let oft = OFTClient::new(env, &oft_address);
484
502
 
@@ -1,5 +1,5 @@
1
1
  [package]
2
- name = "oft-mint-burn"
2
+ name = "oft-std"
3
3
  version.workspace = true
4
4
  edition.workspace = true
5
5
  license.workspace = true
@@ -17,10 +17,3 @@ common-macros = { workspace = true }
17
17
  oapp-macros = { workspace = true }
18
18
  oft = { workspace = true }
19
19
 
20
- [dev-dependencies]
21
- soroban-sdk = { workspace = true, features = ["testutils"] }
22
- assert_unordered = "0.3.5"
23
- simple-message-lib = { workspace = true }
24
- message-lib-common = { workspace = true, features = ["testutils"] }
25
- endpoint-v2 = { workspace = true, features = ["testutils"] }
26
- executor = { workspace = true, features = ["testutils"] }
@@ -0,0 +1,5 @@
1
+ #![no_std]
2
+
3
+ mod oft;
4
+
5
+ pub use oft::*;
@@ -0,0 +1,59 @@
1
+ use common_macros::{contract_impl, storage};
2
+ use oapp_macros::oapp;
3
+ use oft::oft::{oft_initialize, OFTInner, OFT};
4
+ use oft::oft_types::{lock_unlock, mint_burn};
5
+ use oft::types::OFTReceipt;
6
+ use soroban_sdk::{Address, Env};
7
+
8
+ #[storage]
9
+ enum OFTStdStorage {
10
+ #[instance(bool)]
11
+ IsLockUnlock,
12
+ }
13
+
14
+ #[oapp]
15
+ pub struct OFTStd;
16
+
17
+ #[contract_impl]
18
+ impl OFTStd {
19
+ pub fn __constructor(
20
+ env: &Env,
21
+ token: &Address,
22
+ owner: &Address,
23
+ endpoint: &Address,
24
+ delegate: &Option<Address>,
25
+ shared_decimals: u32,
26
+ is_lock_unlock: bool,
27
+ ) {
28
+ oft_initialize::<Self>(env, owner, token, endpoint, delegate, shared_decimals);
29
+ OFTStdStorage::set_is_lock_unlock(env, &is_lock_unlock);
30
+ }
31
+
32
+ /// Whether this OFT uses lock/unlock mode (true) or mint/burn mode (false)
33
+ pub fn is_lock_unlock(env: &Env) -> bool {
34
+ OFTStdStorage::is_lock_unlock(env).unwrap()
35
+ }
36
+ }
37
+
38
+ /// OFT trait implementation for standard OFT
39
+ #[contract_impl(contracttrait)]
40
+ impl OFT for OFTStd {}
41
+
42
+ /// OFT behavior for standard OFT
43
+ impl OFTInner for OFTStd {
44
+ fn __debit(env: &Env, sender: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> OFTReceipt {
45
+ if Self::is_lock_unlock(env) {
46
+ lock_unlock::debit::<Self>(env, sender, amount_ld, min_amount_ld, dst_eid)
47
+ } else {
48
+ mint_burn::debit::<Self>(env, sender, amount_ld, min_amount_ld, dst_eid)
49
+ }
50
+ }
51
+
52
+ fn __credit(env: &Env, to: &Address, amount_ld: i128, src_eid: u32) -> i128 {
53
+ if Self::is_lock_unlock(env) {
54
+ lock_unlock::credit::<Self>(env, to, amount_ld, src_eid)
55
+ } else {
56
+ mint_burn::credit::<Self>(env, to, amount_ld, src_eid)
57
+ }
58
+ }
59
+ }
@@ -76,13 +76,13 @@ impl Ownable for DefaultOwnable {
76
76
  }
77
77
 
78
78
  fn transfer_ownership(env: &Env, new_owner: &Address) {
79
- let old_owner = enforce_owner_auth::<Self>(env);
79
+ let old_owner = Self::owner(env).unwrap_or_panic(env, OwnableError::OwnerNotSet);
80
80
  DefaultOwnableStorage::set_owner(env, new_owner);
81
81
  OwnershipTransferred { old_owner, new_owner: new_owner.clone() }.publish(env);
82
82
  }
83
83
 
84
84
  fn renounce_ownership(env: &Env) {
85
- let old_owner = enforce_owner_auth::<Self>(env);
85
+ let old_owner = Self::owner(env).unwrap_or_panic(env, OwnableError::OwnerNotSet);
86
86
  DefaultOwnableStorage::remove_owner(env);
87
87
  OwnershipRenounced { old_owner }.publish(env);
88
88
  }
@@ -186,20 +186,6 @@ fn transfer_ownership_to_same_owner() {
186
186
  assert_eq!(client.owner(), Some(owner));
187
187
  }
188
188
 
189
- #[test]
190
- #[should_panic(expected = "Error(Auth, InvalidAction)")]
191
- fn transfer_ownership_requires_auth() {
192
- let env = Env::default();
193
- let owner = Address::generate(&env);
194
- let new_owner = Address::generate(&env);
195
-
196
- let contract = env.register(Contract, (&owner,));
197
- let client = ContractClient::new(&env, &contract);
198
-
199
- // Try to transfer without auth should fail
200
- client.transfer_ownership(&new_owner);
201
- }
202
-
203
189
  #[test]
204
190
  #[should_panic(expected = "Error(Contract, #1301)")]
205
191
  fn transfer_after_renounce_fails() {
@@ -264,19 +250,6 @@ fn renounce_ownership_with_event() {
264
250
  assert_eq!(client.owner(), None);
265
251
  }
266
252
 
267
- #[test]
268
- #[should_panic(expected = "Error(Auth, InvalidAction)")]
269
- fn renounce_ownership_requires_auth() {
270
- let env = Env::default();
271
- let owner = Address::generate(&env);
272
-
273
- let contract = env.register(Contract, (&owner,));
274
- let client = ContractClient::new(&env, &contract);
275
-
276
- // Try to renounce without auth should fail
277
- client.renounce_ownership();
278
- }
279
-
280
253
  #[test]
281
254
  #[should_panic(expected = "Error(Contract, #1301)")]
282
255
  fn renounce_after_renounce_fails() {
@@ -352,42 +325,6 @@ fn chain_new_owner_can_transfer() {
352
325
  assert_eq!(client.owner(), Some(third_owner));
353
326
  }
354
327
 
355
- #[test]
356
- #[should_panic(expected = "Error(Auth, InvalidAction)")]
357
- fn chain_old_owner_cannot_transfer_after_transfer() {
358
- let env = Env::default();
359
- let owner = Address::generate(&env);
360
- let new_owner = Address::generate(&env);
361
- let third_owner = Address::generate(&env);
362
-
363
- let contract = env.register(Contract, (&owner,));
364
- let client = ContractClient::new(&env, &contract);
365
-
366
- // Transfer to new_owner
367
- env.mock_auths(&[MockAuth {
368
- address: &owner,
369
- invoke: &MockAuthInvoke {
370
- contract: &contract,
371
- args: (&new_owner,).into_val(&env),
372
- fn_name: "transfer_ownership",
373
- sub_invokes: &[],
374
- },
375
- }]);
376
- client.transfer_ownership(&new_owner);
377
-
378
- // Old owner tries to transfer again - should fail
379
- env.mock_auths(&[MockAuth {
380
- address: &owner,
381
- invoke: &MockAuthInvoke {
382
- contract: &contract,
383
- args: (&third_owner,).into_val(&env),
384
- fn_name: "transfer_ownership",
385
- sub_invokes: &[],
386
- },
387
- }]);
388
- client.transfer_ownership(&third_owner);
389
- }
390
-
391
328
  // ============================================
392
329
  // init: DefaultOwnable::init_owner tests
393
330
  // ============================================
@@ -1,4 +1,5 @@
1
1
  use crate::errors::TtlError;
2
+ use common_macros::event;
2
3
  use soroban_sdk::{assert_with_error, contractclient, contracttype, Env};
3
4
 
4
5
  /// Number of ledgers per day (~5 second ledger close time).
@@ -85,6 +86,8 @@ impl TtlConfigurable for DefaultTtlConfigurable {
85
86
 
86
87
  TtlConfigStorage::set_or_remove_instance(env, instance);
87
88
  TtlConfigStorage::set_or_remove_persistent(env, persistent);
89
+
90
+ TtlConfigsSet { instance: *instance, persistent: *persistent }.publish(env);
88
91
  }
89
92
 
90
93
  fn ttl_configs(env: &Env) -> (Option<TtlConfig>, Option<TtlConfig>) {
@@ -94,6 +97,8 @@ impl TtlConfigurable for DefaultTtlConfigurable {
94
97
  fn freeze_ttl_configs(env: &Env) {
95
98
  assert_with_error!(env, !Self::is_ttl_configs_frozen(env), TtlError::TtlConfigAlreadyFrozen);
96
99
  TtlConfigStorage::set_frozen(env, &true);
100
+
101
+ TtlConfigsFrozen {}.publish(env);
97
102
  }
98
103
 
99
104
  fn is_ttl_configs_frozen(env: &Env) -> bool {
@@ -101,4 +106,17 @@ impl TtlConfigurable for DefaultTtlConfigurable {
101
106
  }
102
107
  }
103
108
 
104
- // TODO: add events
109
+ // ============================================
110
+ // Events
111
+ // ============================================
112
+
113
+ /// Event emitted when TTL configs are set.
114
+ #[event]
115
+ pub struct TtlConfigsSet {
116
+ pub instance: Option<TtlConfig>,
117
+ pub persistent: Option<TtlConfig>,
118
+ }
119
+
120
+ /// Event emitted when TTL configs are frozen.
121
+ #[event]
122
+ pub struct TtlConfigsFrozen {}
@@ -1,15 +1,57 @@
1
- use crate::TransactionAuthData;
1
+ use super::*;
2
+
2
3
  use soroban_sdk::{
3
4
  address_payload::AddressPayload,
4
5
  auth::{Context, CustomAccountInterface},
6
+ contractimpl, contracttype,
7
+ crypto::Hash,
8
+ xdr::ToXdr,
5
9
  };
10
+ use utils::buffer_writer::BufferWriter;
11
+
12
+ // ============================================================================
13
+ // Authentication Data Types
14
+ // ============================================================================
15
+
16
+ /// Authentication data for DVN contract transactions.
17
+ ///
18
+ /// This struct is used with Soroban's custom account interface to authorize
19
+ /// transactions through a combination of admin signature and multisig quorum.
20
+ #[contracttype]
21
+ #[derive(Clone, Debug, Eq, PartialEq)]
22
+ pub struct TransactionAuthData {
23
+ /// Verifier ID - must match the DVN's configured VID.
24
+ pub vid: u32,
25
+ /// Expiration timestamp (ledger time) after which this auth is invalid.
26
+ pub expiration: u64,
27
+ /// Signatures from multisig signers (secp256k1, 65 bytes each).
28
+ pub signatures: Vec<BytesN<65>>,
29
+ /// Admin's Ed25519 public key (32 bytes).
30
+ pub admin: BytesN<32>,
31
+ /// Admin's Ed25519 signature over the signature payload (64 bytes).
32
+ pub admin_signature: BytesN<64>,
33
+ }
34
+
35
+ // ============================================================================
36
+ // Custom Account Interface Implementation
37
+ // ============================================================================
6
38
 
7
39
  #[contractimpl]
8
40
  impl CustomAccountInterface for Dvn {
9
41
  type Signature = TransactionAuthData;
10
42
  type Error = DvnError;
11
43
 
12
- #[allow(non_snake_case)]
44
+ /// Validates authorization for DVN contract operations.
45
+ ///
46
+ /// This implements Soroban's custom account interface, allowing the DVN
47
+ /// contract to act as its own account with multisig authorization.
48
+ ///
49
+ /// # Validation Steps
50
+ /// 1. Verify the admin signature is from a registered admin
51
+ /// 2. Check the VID matches the contract's configured VID
52
+ /// 3. Ensure the auth data hasn't expired
53
+ /// 4. Verify the auth contexts hash hasn't been used (replay protection)
54
+ /// 5. Verify multisig signatures meet the threshold
13
55
  fn __check_auth(
14
56
  env: Env,
15
57
  signature_payload: Hash<32>,
@@ -18,49 +60,71 @@ impl CustomAccountInterface for Dvn {
18
60
  ) -> Result<(), Self::Error> {
19
61
  let TransactionAuthData { vid, expiration, signatures, admin, admin_signature } = auth_data;
20
62
 
21
- verify_admin(&env, &admin, &admin_signature, &signature_payload)?;
63
+ // Verify the admin signature is valid and from a registered admin.
64
+ Self::verify_admin(&env, &admin, &admin_signature, &signature_payload)?;
22
65
 
23
- let stored_vid = DvnStorage::vid(&env).unwrap();
66
+ // Verify the VID matches the contract's configured VID.
67
+ let stored_vid = Self::vid(&env);
24
68
  if vid != stored_vid {
25
69
  return Err(DvnError::InvalidVid);
26
70
  }
27
-
71
+ // Ensure the auth data hasn't expired.
28
72
  if expiration <= env.ledger().timestamp() {
29
73
  return Err(DvnError::AuthDataExpired);
30
74
  }
31
75
 
32
- let hash = hash_auth_data(&env, vid, expiration, &auth_contexts);
33
-
76
+ // Compute the hash of the auth data for replay protection.
77
+ let hash = Self::hash_auth_data(&env, vid, expiration, &auth_contexts);
78
+ // Verify the hash hasn't been used (replay protection) and set it as used.
34
79
  if DvnStorage::used_hash(&env, &hash) {
35
80
  return Err(DvnError::HashAlreadyUsed);
36
81
  }
37
-
38
- Dvn::verify_signatures(&env, &hash, &signatures);
39
-
40
82
  DvnStorage::set_used_hash(&env, &hash, &true);
41
83
 
84
+ // Verify the multisig signatures meet the threshold.
85
+ Self::verify_signatures(&env, &hash, &signatures);
86
+
42
87
  Ok(())
43
88
  }
44
89
  }
45
90
 
46
- fn verify_admin(
47
- env: &Env,
48
- admin: &BytesN<32>,
49
- admin_signature: &BytesN<64>,
50
- signature_payload: &Hash<32>,
51
- ) -> Result<(), DvnError> {
52
- let admin_address = Address::from_payload(env, AddressPayload::AccountIdPublicKeyEd25519(admin.clone()));
53
- if !Dvn::is_admin(env, &admin_address) {
54
- return Err(DvnError::OnlyAdmin);
55
- }
91
+ // ============================================================================
92
+ // Internal Helper Functions
93
+ // ============================================================================
56
94
 
57
- env.crypto().ed25519_verify(admin, &signature_payload.clone().into(), admin_signature);
95
+ impl Dvn {
96
+ /// Verifies that the admin signature is valid and from a registered admin.
97
+ ///
98
+ /// # Arguments
99
+ /// * `admin` - The admin's Ed25519 public key
100
+ /// * `admin_signature` - The admin's signature over the signature payload
101
+ /// * `signature_payload` - The payload that was signed
102
+ ///
103
+ /// # Errors
104
+ /// Returns `DvnError::OnlyAdmin` if the signer is not a registered admin.
105
+ fn verify_admin(
106
+ env: &Env,
107
+ admin: &BytesN<32>,
108
+ admin_signature: &BytesN<64>,
109
+ signature_payload: &Hash<32>,
110
+ ) -> Result<(), DvnError> {
111
+ let admin_address = Address::from_payload(env, AddressPayload::AccountIdPublicKeyEd25519(admin.clone()));
112
+ if !Self::is_admin(env, &admin_address) {
113
+ return Err(DvnError::OnlyAdmin);
114
+ }
58
115
 
59
- Ok(())
60
- }
116
+ env.crypto().ed25519_verify(admin, &signature_payload.clone().into(), admin_signature);
61
117
 
62
- fn hash_auth_data(env: &Env, vid: u32, expiration: u64, auth_contexts: &Vec<Context>) -> BytesN<32> {
63
- let mut writer = BufferWriter::new(env);
64
- let data = writer.write_u32(vid).write_u64(expiration).write_bytes(&auth_contexts.to_xdr(env)).to_bytes();
65
- env.crypto().keccak256(&data).into()
118
+ Ok(())
119
+ }
120
+
121
+ /// Computes the hash of auth data for replay protection.
122
+ ///
123
+ /// Creates a unique hash from the VID, expiration, and auth contexts
124
+ /// to prevent the same authorization from being used multiple times.
125
+ fn hash_auth_data(env: &Env, vid: u32, expiration: u64, auth_contexts: &Vec<Context>) -> BytesN<32> {
126
+ let mut writer = BufferWriter::new(env);
127
+ let data = writer.write_u32(vid).write_u64(expiration).write_bytes(&auth_contexts.to_xdr(env)).to_bytes();
128
+ env.crypto().keccak256(&data).into()
129
+ }
66
130
  }
@@ -1,26 +1,22 @@
1
1
  use crate::{
2
- errors::{DvnError, MultisigError},
3
- events::SetDstConfig,
4
- storage::DvnStorage,
5
- types::{DstConfig, DstConfigParam},
6
- IDVN,
2
+ dvn::multisig::init_multisig, errors::DvnError, events::SetDstConfig, storage::DvnStorage, DstConfig,
3
+ DstConfigParam, IMultisig, IDVN,
7
4
  };
8
- use common_macros::ttl_configurable;
5
+ use common_macros::{contract_impl, ttl_configurable};
9
6
  use endpoint_v2::FeeRecipient;
10
7
  use message_lib_common::interfaces::ILayerZeroDVN;
11
- use soroban_sdk::{
12
- assert_with_error, contract, contractimpl, crypto::Hash, xdr::ToXdr, Address, Bytes, BytesN, Env, Vec,
13
- };
14
- use utils::{buffer_writer::BufferWriter, option_ext::OptionExt};
8
+ use soroban_sdk::{contract, Address, Bytes, BytesN, Env, Vec};
9
+ use utils::option_ext::OptionExt;
15
10
  use worker::{
16
11
  assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, set_admin_by_admin,
17
12
  set_admin_by_owner, storage::WorkerStorage, DvnFeeLibClient, DvnFeeParams, Worker,
18
13
  };
14
+
19
15
  #[contract]
20
16
  #[ttl_configurable]
21
17
  pub struct Dvn;
22
18
 
23
- #[contractimpl]
19
+ #[contract_impl]
24
20
  impl Dvn {
25
21
  pub fn __constructor(
26
22
  env: &Env,
@@ -66,7 +62,7 @@ impl Dvn {
66
62
  }
67
63
  }
68
64
 
69
- #[contractimpl]
65
+ #[contract_impl]
70
66
  impl IDVN for Dvn {
71
67
  fn set_dst_config(env: &Env, admin: &Address, params: &Vec<DstConfigParam>) {
72
68
  require_admin_auth::<Self>(env, admin);
@@ -77,8 +73,8 @@ impl IDVN for Dvn {
77
73
  SetDstConfig { params: params.clone() }.publish(env);
78
74
  }
79
75
 
80
- fn dst_config(env: &Env, dst_eid: u32) -> DstConfig {
81
- DvnStorage::dst_config(env, dst_eid).unwrap_or_panic(env, DvnError::EidNotSupported)
76
+ fn dst_config(env: &Env, dst_eid: u32) -> Option<DstConfig> {
77
+ DvnStorage::dst_config(env, dst_eid)
82
78
  }
83
79
 
84
80
  fn vid(env: &Env) -> u32 {
@@ -86,7 +82,7 @@ impl IDVN for Dvn {
86
82
  }
87
83
  }
88
84
 
89
- #[contractimpl]
85
+ #[contract_impl]
90
86
  impl ILayerZeroDVN for Dvn {
91
87
  fn get_fee(
92
88
  env: &Env,
@@ -101,7 +97,7 @@ impl ILayerZeroDVN for Dvn {
101
97
  assert_not_paused::<Self>(env);
102
98
  assert_acl::<Self>(env, sender);
103
99
 
104
- let dst_config = Self::dst_config(env, dst_eid);
100
+ let dst_config = Self::dst_config(env, dst_eid).unwrap_or_panic(env, DvnError::EidNotSupported);
105
101
  let params = DvnFeeParams {
106
102
  sender: sender.clone(),
107
103
  dst_eid,
@@ -115,7 +111,7 @@ impl ILayerZeroDVN for Dvn {
115
111
  floor_margin_usd: dst_config.floor_margin_usd,
116
112
  };
117
113
 
118
- DvnFeeLibClient::new(env, &Self::worker_fee_lib(env)).get_fee(&params)
114
+ DvnFeeLibClient::new(env, &Self::worker_fee_lib(env)).get_fee(&env.current_contract_address(), &params)
119
115
  }
120
116
 
121
117
  fn assign_job(
@@ -136,8 +132,14 @@ impl ILayerZeroDVN for Dvn {
136
132
  }
137
133
  }
138
134
 
139
- #[contractimpl(contracttrait)]
135
+ #[contract_impl(contracttrait)]
140
136
  impl Worker for Dvn {}
141
137
 
142
- include!("multisig.rs");
143
- include!("auth.rs");
138
+ // ============================================================================
139
+ // Include SubModules
140
+ // ============================================================================
141
+
142
+ #[path = "auth.rs"]
143
+ pub mod auth;
144
+ #[path = "multisig.rs"]
145
+ pub mod multisig;
@@ -1,12 +1,57 @@
1
- use crate::types::{DstConfig, DstConfigParam};
2
1
  use crate::IMultisig;
3
2
  use message_lib_common::interfaces::ILayerZeroDVN;
4
- use soroban_sdk::{contractclient, Address, Env, Vec};
3
+ use soroban_sdk::{contractclient, contracttype, Address, Env, Vec};
5
4
  use worker::Worker;
6
5
 
6
+ /// Configuration for a destination chain.
7
+ ///
8
+ /// Contains fee calculation parameters specific to each destination endpoint.
9
+ #[contracttype]
10
+ #[derive(Clone, Debug, Eq, PartialEq)]
11
+ pub struct DstConfig {
12
+ /// Gas for verification on the destination chain.
13
+ pub gas: u128,
14
+ /// Fee multiplier in basis points (10000 = 100%).
15
+ /// If 0, the default multiplier from worker config is used.
16
+ pub multiplier_bps: u32,
17
+ /// Minimum fee margin in USD (scaled by native decimals rate).
18
+ pub floor_margin_usd: u128,
19
+ }
20
+
21
+ /// Parameter for setting destination chain configuration.
22
+ #[contracttype]
23
+ #[derive(Clone, Debug, Eq, PartialEq)]
24
+ pub struct DstConfigParam {
25
+ /// The destination endpoint ID.
26
+ pub dst_eid: u32,
27
+ /// The configuration for this destination.
28
+ pub config: DstConfig,
29
+ }
30
+
31
+ /// DVN (Decentralized Verifier Network) contract interface.
32
+ ///
33
+ /// Extends the LayerZero DVN interface with destination configuration management
34
+ /// and multisig capabilities for secure cross-chain message verification.
7
35
  #[contractclient(name = "DVNClient")]
8
36
  pub trait IDVN: ILayerZeroDVN + Worker + IMultisig {
37
+ /// Sets the configuration for one or more destination chains.
38
+ ///
39
+ /// # Arguments
40
+ /// * `admin` - The admin address (must provide authorization)
41
+ /// * `params` - List of destination configurations to set
9
42
  fn set_dst_config(env: &Env, admin: &Address, params: &Vec<DstConfigParam>);
10
- fn dst_config(env: &Env, dst_eid: u32) -> DstConfig;
43
+
44
+ /// Gets the configuration for a destination chain.
45
+ ///
46
+ /// # Arguments
47
+ /// * `dst_eid` - The destination endpoint ID
48
+ ///
49
+ /// # Returns
50
+ /// The destination configuration, or `None` if not configured
51
+ fn dst_config(env: &Env, dst_eid: u32) -> Option<DstConfig>;
52
+
53
+ /// Returns the verifier ID (VID) of this DVN.
54
+ ///
55
+ /// The VID is a unique identifier used in multisig authentication.
11
56
  fn vid(env: &Env) -> u32;
12
57
  }
@@ -1,15 +1,56 @@
1
1
  use soroban_sdk::{contractclient, BytesN, Env, Vec};
2
2
 
3
+ /// Length of an ECDSA signature (64 bytes) plus recovery ID (1 byte).
3
4
  pub const SIGNATURE_LENGTH: usize = 65;
4
5
 
6
+ /// Multisig interface for threshold-based signature verification.
7
+ ///
8
+ /// Provides functionality to manage signers and verify that a sufficient
9
+ /// number of valid signatures (meeting the threshold) have signed a message.
5
10
  #[contractclient(name = "MultiSigClient")]
6
11
  pub trait IMultisig {
12
+ /// Adds or removes a signer from the multisig.
13
+ ///
14
+ /// # Arguments
15
+ /// * `signer` - The 20-byte signer address (derived from secp256k1 public key)
16
+ /// * `active` - `true` to add, `false` to remove
7
17
  fn set_signer(env: &Env, signer: &BytesN<20>, active: bool);
18
+
19
+ /// Returns all registered signers.
8
20
  fn get_signers(env: &Env) -> Vec<BytesN<20>>;
21
+
22
+ /// Returns the total number of registered signers.
9
23
  fn total_signers(env: &Env) -> u32;
24
+
25
+ /// Checks if an address is a registered signer.
10
26
  fn is_signer(env: &Env, signer: &BytesN<20>) -> bool;
27
+
28
+ /// Returns the current signature threshold (quorum).
11
29
  fn threshold(env: &Env) -> u32;
30
+
31
+ /// Sets the signature threshold (quorum).
32
+ ///
33
+ /// The threshold must be greater than 0 and not exceed total signers.
12
34
  fn set_threshold(env: &Env, threshold: u32);
35
+
36
+ /// Verifies signatures against the configured threshold.
37
+ ///
38
+ /// # Arguments
39
+ /// * `hash` - The 32-byte message hash that was signed
40
+ /// * `signatures` - List of signatures to verify
41
+ ///
42
+ /// # Panics
43
+ /// If fewer than `threshold` valid signatures from registered signers are provided.
13
44
  fn verify_signatures(env: &Env, hash: &BytesN<32>, signatures: &Vec<BytesN<SIGNATURE_LENGTH>>);
45
+
46
+ /// Verifies signatures against a custom threshold.
47
+ ///
48
+ /// # Arguments
49
+ /// * `hash` - The 32-byte message hash that was signed
50
+ /// * `signatures` - List of signatures to verify
51
+ /// * `threshold` - Custom threshold to use instead of the configured one
52
+ ///
53
+ /// # Panics
54
+ /// If fewer than `threshold` valid signatures from registered signers are provided.
14
55
  fn verify_n_signatures(env: &Env, hash: &BytesN<32>, signatures: &Vec<BytesN<SIGNATURE_LENGTH>>, threshold: u32);
15
56
  }