@layerzerolabs/protocol-stellar-v2 0.2.11 → 0.2.12

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 (74) hide show
  1. package/.turbo/turbo-build.log +202 -194
  2. package/.turbo/turbo-lint.log +38 -38
  3. package/.turbo/turbo-test.log +891 -891
  4. package/Cargo.lock +1 -1
  5. package/contracts/common-macros/src/lib.rs +3 -36
  6. package/contracts/endpoint-v2/src/endpoint_v2.rs +4 -4
  7. package/contracts/endpoint-v2/src/events.rs +40 -22
  8. package/contracts/endpoint-v2/src/interfaces/message_lib.rs +2 -2
  9. package/contracts/endpoint-v2/src/interfaces/message_lib_manager.rs +2 -2
  10. package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +2 -2
  11. package/contracts/endpoint-v2/src/interfaces/messaging_composer.rs +2 -2
  12. package/contracts/endpoint-v2/src/interfaces/send_lib.rs +2 -2
  13. package/contracts/endpoint-v2/src/message_lib_manager.rs +3 -3
  14. package/contracts/endpoint-v2/src/messaging_channel.rs +1 -1
  15. package/contracts/endpoint-v2/src/messaging_composer.rs +1 -1
  16. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +4 -8
  17. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +3 -7
  18. package/contracts/message-libs/{block-message-lib → blocked-message-lib}/Cargo.toml +1 -1
  19. package/contracts/message-libs/treasury/src/events.rs +9 -6
  20. package/contracts/message-libs/uln-302/src/events.rs +19 -11
  21. package/contracts/message-libs/uln-302/src/interfaces/receive_uln.rs +2 -2
  22. package/contracts/message-libs/uln-302/src/interfaces/send_uln.rs +2 -2
  23. package/contracts/message-libs/uln-302/src/receive_uln.rs +2 -2
  24. package/contracts/message-libs/uln-302/src/send_uln.rs +3 -3
  25. package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +5 -5
  26. package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +5 -5
  27. package/contracts/message-libs/uln-302/src/tests/setup.rs +3 -3
  28. package/contracts/message-libs/uln-302/src/types.rs +24 -24
  29. package/contracts/message-libs/uln-302/src/uln302.rs +1 -1
  30. package/contracts/oapps/counter/integration_tests/utils.rs +1 -1
  31. package/contracts/oapps/oapp/src/oapp_core.rs +4 -3
  32. package/contracts/oapps/oapp/src/oapp_options_type3.rs +4 -3
  33. package/contracts/oapps/oft/integration-tests/utils.rs +1 -1
  34. package/contracts/oapps/oft/src/events.rs +5 -4
  35. package/contracts/oapps/oft/src/extensions/oft_fee.rs +10 -6
  36. package/contracts/oapps/oft/src/extensions/pausable.rs +4 -4
  37. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +8 -6
  38. package/contracts/utils/src/ownable.rs +6 -4
  39. package/contracts/utils/src/tests/testing_utils.rs +7 -5
  40. package/contracts/utils/src/ttl.rs +5 -4
  41. package/contracts/workers/dvn/src/auth.rs +59 -45
  42. package/contracts/workers/dvn/src/dvn.rs +84 -16
  43. package/contracts/workers/dvn/src/errors.rs +10 -13
  44. package/contracts/workers/dvn/src/events.rs +7 -5
  45. package/contracts/workers/dvn/src/interfaces/dvn.rs +29 -1
  46. package/contracts/workers/dvn/src/multisig.rs +94 -71
  47. package/contracts/workers/dvn/src/storage.rs +9 -12
  48. package/contracts/workers/dvn/src/tests/auth.rs +56 -26
  49. package/contracts/workers/dvn/src/tests/dvn.rs +37 -37
  50. package/contracts/workers/dvn/src/tests/multisig/set_signer.rs +8 -8
  51. package/contracts/workers/dvn/src/tests/multisig/set_threshold.rs +9 -9
  52. package/contracts/workers/dvn/src/tests/multisig/verify_signatures.rs +6 -6
  53. package/contracts/workers/dvn/src/tests/setup.rs +5 -5
  54. package/contracts/workers/executor/src/auth.rs +93 -0
  55. package/contracts/workers/executor/src/events.rs +5 -4
  56. package/contracts/workers/executor/src/{lz_executor.rs → executor.rs} +25 -98
  57. package/contracts/workers/executor/src/interfaces/mod.rs +1 -1
  58. package/contracts/workers/executor/src/lib.rs +6 -5
  59. package/contracts/workers/worker/src/events.rs +23 -13
  60. package/contracts/workers/worker/src/worker.rs +32 -21
  61. package/package.json +3 -3
  62. package/sdk/dist/generated/bml.js +23 -23
  63. package/sdk/dist/generated/counter.js +25 -25
  64. package/sdk/dist/generated/endpoint.js +23 -23
  65. package/sdk/dist/generated/sml.js +23 -23
  66. package/sdk/dist/generated/uln302.d.ts +1 -1
  67. package/sdk/dist/generated/uln302.js +33 -33
  68. package/sdk/package.json +1 -1
  69. package/sdk/test/index.test.ts +1 -1
  70. package/sdk/test/oft.test.ts +847 -0
  71. package/sdk/test/suites/scan.ts +20 -4
  72. package/tools/ts-bindings-gen/src/main.rs +2 -1
  73. package/contracts/common-macros/src/event.rs +0 -16
  74. /package/contracts/message-libs/{block-message-lib → blocked-message-lib}/src/lib.rs +0 -0
@@ -1,23 +1,50 @@
1
+ //! LayerZero Decentralized Verifier Network (DVN) contract.
2
+ //!
3
+ //! The DVN is responsible for verifying cross-chain messages by providing
4
+ //! cryptographic attestations. It uses a multisig scheme with secp256k1
5
+ //! signatures for authorization and implements Soroban's custom account
6
+ //! interface for transaction signing.
7
+
1
8
  use crate::{
2
- dvn::multisig::init_multisig, errors::DvnError, events::SetDstConfig, storage::DvnStorage, DstConfig,
3
- DstConfigParam, IMultisig, IDVN,
9
+ errors::DvnError, events::SetDstConfig, storage::DvnStorage, Call, DstConfig, DstConfigParam, IMultisig, IDVN,
4
10
  };
5
11
  use common_macros::{contract_impl, ttl_configurable};
6
12
  use endpoint_v2::FeeRecipient;
7
13
  use message_lib_common::interfaces::ILayerZeroDVN;
8
- use soroban_sdk::{contract, Address, Bytes, BytesN, Env, Vec};
14
+ use soroban_sdk::{contract, xdr::ToXdr, Address, Bytes, BytesN, Env, Vec};
15
+ use utils::buffer_writer::BufferWriter;
9
16
  use utils::option_ext::OptionExt;
10
17
  use worker::{
11
18
  assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, set_admin_by_admin,
12
- set_admin_by_owner, storage::WorkerStorage, DvnFeeLibClient, DvnFeeParams, Worker,
19
+ set_admin_by_owner, DvnFeeLibClient, DvnFeeParams, Worker,
13
20
  };
14
21
 
22
+ /// LayerZero DVN contract.
23
+ ///
24
+ /// Implements multisig-based verification with custom account authorization.
25
+ /// The contract owns itself, allowing the multisig quorum to authorize operations.
15
26
  #[contract]
16
27
  #[ttl_configurable]
17
- pub struct Dvn;
28
+ pub struct LzDVN;
29
+
30
+ // ============================================================================
31
+ // Core Implementation
32
+ // ============================================================================
18
33
 
19
34
  #[contract_impl]
20
- impl Dvn {
35
+ impl LzDVN {
36
+ /// Initializes the DVN contract.
37
+ ///
38
+ /// # Arguments
39
+ /// * `vid` - Verifier ID, unique identifier for this DVN
40
+ /// * `signers` - Initial multisig signers (20-byte Ethereum addresses)
41
+ /// * `threshold` - Minimum signatures required for multisig operations
42
+ /// * `admins` - Initial admin addresses for operational functions
43
+ /// * `supported_msglibs` - Message libraries this DVN supports (e.g., ULN302)
44
+ /// * `price_feed` - Price feed contract for fee calculations
45
+ /// * `default_multiplier_bps` - Default fee multiplier (10000 = 1x)
46
+ /// * `worker_fee_lib` - Fee library contract for computing DVN fees
47
+ /// * `deposit_address` - Address to receive fee payments
21
48
  pub fn __constructor(
22
49
  env: &Env,
23
50
  vid: u32,
@@ -26,15 +53,22 @@ impl Dvn {
26
53
  admins: &Vec<Address>,
27
54
  supported_msglibs: &Vec<Address>,
28
55
  price_feed: &Address,
29
- worker_fee_lib: &Address,
30
56
  default_multiplier_bps: u32,
57
+ worker_fee_lib: &Address,
31
58
  deposit_address: &Address,
32
59
  ) {
33
- init_multisig(env, signers, threshold);
60
+ Self::init_multisig(env, signers, threshold);
34
61
  Self::init_owner(env, &env.current_contract_address());
35
- init_worker::<Self>(env, admins, supported_msglibs, price_feed, default_multiplier_bps);
36
- WorkerStorage::set_worker_fee_lib(env, worker_fee_lib);
37
- WorkerStorage::set_deposit_address(env, deposit_address);
62
+ init_worker::<Self>(
63
+ env,
64
+ admins,
65
+ supported_msglibs,
66
+ price_feed,
67
+ default_multiplier_bps,
68
+ worker_fee_lib,
69
+ deposit_address,
70
+ );
71
+
38
72
  DvnStorage::set_vid(env, &vid);
39
73
  }
40
74
 
@@ -51,10 +85,13 @@ impl Dvn {
51
85
  set_admin_by_admin::<Self>(env, caller, admin, active);
52
86
  }
53
87
 
54
- /// Function for quorum to change the admin without going through the execute function
88
+ /// Allows the quorum to add/remove admins without requiring an admin signature.
55
89
  ///
56
- /// # Arguments
90
+ /// This function bypasses the normal admin verification in `__check_auth`,
91
+ /// requiring only multisig quorum signatures. Must be called as a single
92
+ /// operation (cannot be bundled with other calls).
57
93
  ///
94
+ /// # Arguments
58
95
  /// * `admin` - The address to set admin status for
59
96
  /// * `active` - `true` to add admin, `false` to remove
60
97
  pub fn quorum_change_admin(env: &Env, admin: &Address, active: bool) {
@@ -62,8 +99,13 @@ impl Dvn {
62
99
  }
63
100
  }
64
101
 
102
+ // ============================================================================
103
+ // IDVN Implementation
104
+ // ============================================================================
105
+
65
106
  #[contract_impl]
66
- impl IDVN for Dvn {
107
+ impl IDVN for LzDVN {
108
+ /// Sets destination chain configurations. Requires admin authorization.
67
109
  fn set_dst_config(env: &Env, admin: &Address, params: &Vec<DstConfigParam>) {
68
110
  require_admin_auth::<Self>(env, admin);
69
111
  for param in params {
@@ -73,17 +115,35 @@ impl IDVN for Dvn {
73
115
  SetDstConfig { params: params.clone() }.publish(env);
74
116
  }
75
117
 
118
+ /// Returns the destination configuration for a specific endpoint ID.
76
119
  fn dst_config(env: &Env, dst_eid: u32) -> Option<DstConfig> {
77
120
  DvnStorage::dst_config(env, dst_eid)
78
121
  }
79
122
 
123
+ /// Returns the Verifier ID for this DVN.
80
124
  fn vid(env: &Env) -> u32 {
81
125
  DvnStorage::vid(env).unwrap()
82
126
  }
127
+
128
+ /// Computes the hash of call data for multisig signing.
129
+ ///
130
+ /// Off-chain signers use this to compute the hash they need to sign.
131
+ fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32> {
132
+ let mut writer = BufferWriter::new(env);
133
+ let data = writer.write_u32(vid).write_u64(expiration).write_bytes(&calls.to_xdr(env)).to_bytes();
134
+ env.crypto().keccak256(&data).into()
135
+ }
83
136
  }
84
137
 
138
+ // ============================================================================
139
+ // ILayerZeroDVN Implementation (Send Flow)
140
+ // ============================================================================
141
+
85
142
  #[contract_impl]
86
- impl ILayerZeroDVN for Dvn {
143
+ impl ILayerZeroDVN for LzDVN {
144
+ /// Calculates the verification fee for a cross-chain message.
145
+ ///
146
+ /// Called by the send library to quote DVN fees before sending.
87
147
  fn get_fee(
88
148
  env: &Env,
89
149
  _send_lib: &Address,
@@ -114,6 +174,10 @@ impl ILayerZeroDVN for Dvn {
114
174
  DvnFeeLibClient::new(env, &Self::worker_fee_lib(env)).get_fee(&env.current_contract_address(), &params)
115
175
  }
116
176
 
177
+ /// Assigns a verification job to this DVN and returns fee payment info.
178
+ ///
179
+ /// Called by the send library when a message is sent. The DVN will later
180
+ /// verify the message on the destination chain.
117
181
  fn assign_job(
118
182
  env: &Env,
119
183
  send_lib: &Address,
@@ -132,8 +196,12 @@ impl ILayerZeroDVN for Dvn {
132
196
  }
133
197
  }
134
198
 
199
+ // ============================================================================
200
+ // Worker Implementation
201
+ // ============================================================================
202
+
135
203
  #[contract_impl(contracttrait)]
136
- impl Worker for Dvn {}
204
+ impl Worker for LzDVN {}
137
205
 
138
206
  // ============================================================================
139
207
  // Include SubModules
@@ -1,21 +1,18 @@
1
1
  use common_macros::contract_error;
2
2
 
3
- #[contract_error]
4
- pub enum MultisigError {
5
- ZeroThreshold,
6
- TotalSignersLessThanThreshold,
7
- InvalidSigner,
8
- SignerAlreadyExists,
9
- SignerNotFound,
10
- UnsortedSigners,
11
- SignatureError,
12
- }
13
-
14
3
  #[contract_error]
15
4
  pub enum DvnError {
16
- InvalidVid,
17
5
  AuthDataExpired,
6
+ EidNotSupported,
18
7
  HashAlreadyUsed,
8
+ InvalidSigner,
9
+ InvalidVid,
10
+ NonContractInvoke,
19
11
  OnlyAdmin,
20
- EidNotSupported,
12
+ SignatureError,
13
+ SignerAlreadyExists,
14
+ SignerNotFound,
15
+ TotalSignersLessThanThreshold,
16
+ UnsortedSigners,
17
+ ZeroThreshold,
21
18
  }
@@ -1,19 +1,21 @@
1
1
  use crate::DstConfigParam;
2
- use common_macros::event;
3
- use soroban_sdk::{BytesN, Vec};
2
+ use soroban_sdk::{contractevent, BytesN, Vec};
4
3
 
5
- #[event]
4
+ #[contractevent]
5
+ #[derive(Clone, Debug, Eq, PartialEq)]
6
6
  pub struct SignerSet {
7
7
  pub signer: BytesN<20>,
8
8
  pub active: bool,
9
9
  }
10
10
 
11
- #[event]
11
+ #[contractevent]
12
+ #[derive(Clone, Debug, Eq, PartialEq)]
12
13
  pub struct ThresholdSet {
13
14
  pub threshold: u32,
14
15
  }
15
16
 
16
- #[event]
17
+ #[contractevent]
18
+ #[derive(Clone, Debug, Eq, PartialEq)]
17
19
  pub struct SetDstConfig {
18
20
  pub params: Vec<DstConfigParam>,
19
21
  }
@@ -1,6 +1,6 @@
1
1
  use crate::IMultisig;
2
2
  use message_lib_common::interfaces::ILayerZeroDVN;
3
- use soroban_sdk::{contractclient, contracttype, Address, Env, Vec};
3
+ use soroban_sdk::{contractclient, contracttype, Address, BytesN, Env, Symbol, Val, Vec};
4
4
  use worker::Worker;
5
5
 
6
6
  /// Configuration for a destination chain.
@@ -28,6 +28,20 @@ pub struct DstConfigParam {
28
28
  pub config: DstConfig,
29
29
  }
30
30
 
31
+ /// Represents a single contract invocation for multisig authorization.
32
+ ///
33
+ /// Used in `hash_call_data` to compute the hash that signers sign over.
34
+ #[contracttype]
35
+ #[derive(Clone, Debug, Eq, PartialEq)]
36
+ pub struct Call {
37
+ /// Target contract address.
38
+ pub to: Address,
39
+ /// Function name to invoke.
40
+ pub func: Symbol,
41
+ /// Function arguments.
42
+ pub args: Vec<Val>,
43
+ }
44
+
31
45
  /// DVN (Decentralized Verifier Network) contract interface.
32
46
  ///
33
47
  /// Extends the LayerZero DVN interface with destination configuration management
@@ -54,4 +68,18 @@ pub trait IDVN: ILayerZeroDVN + Worker + IMultisig {
54
68
  ///
55
69
  /// The VID is a unique identifier used in multisig authentication.
56
70
  fn vid(env: &Env) -> u32;
71
+
72
+ /// Computes the hash of call data for multisig signing.
73
+ ///
74
+ /// Off-chain signers use this to compute the hash they need to sign.
75
+ /// The hash includes the VID, expiration, and the calls being authorized.
76
+ ///
77
+ /// # Arguments
78
+ /// * `vid` - Verifier ID (must match contract's VID)
79
+ /// * `expiration` - Expiration timestamp for the authorization
80
+ /// * `calls` - The contract calls being authorized
81
+ ///
82
+ /// # Returns
83
+ /// A 32-byte keccak256 hash to be signed by the multisig quorum
84
+ fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32>;
57
85
  }
@@ -1,65 +1,86 @@
1
+ //! Multisig functionality for the DVN contract.
2
+ //!
3
+ //! Provides secp256k1-based multisig signature verification using Ethereum-style
4
+ //! 20-byte addresses derived from recovered public keys.
5
+
1
6
  use super::*;
2
7
 
3
8
  use crate::{
4
- errors::MultisigError,
9
+ errors::DvnError,
5
10
  events::{SignerSet, ThresholdSet},
6
- storage::MultisigStorage,
11
+ storage::DvnStorage,
7
12
  };
8
13
  use soroban_sdk::assert_with_error;
9
14
 
15
+ // ============================================================================
16
+ // IMultisig Implementation
17
+ // ============================================================================
18
+
10
19
  #[contract_impl]
11
- impl IMultisig for Dvn {
20
+ impl IMultisig for LzDVN {
21
+ /// Adds or removes a signer. Requires contract self-authorization (quorum).
12
22
  fn set_signer(env: &Env, signer: &BytesN<20>, active: bool) {
13
23
  env.current_contract_address().require_auth();
14
-
15
24
  if active {
16
- add_signer(env, signer);
25
+ Self::add_signer(env, signer);
17
26
  } else {
18
- remove_signer(env, signer);
27
+ Self::remove_signer(env, signer);
19
28
  }
20
29
  }
21
30
 
31
+ /// Updates the signature threshold. Requires contract self-authorization (quorum).
22
32
  fn set_threshold(env: &Env, threshold: u32) {
23
33
  env.current_contract_address().require_auth();
24
34
 
25
- set_threshold(env, threshold);
35
+ assert_with_error!(env, threshold > 0, DvnError::ZeroThreshold);
36
+ assert_with_error!(env, Self::total_signers(env) >= threshold, DvnError::TotalSignersLessThanThreshold);
37
+
38
+ DvnStorage::set_threshold(env, &threshold);
39
+
40
+ ThresholdSet { threshold }.publish(env);
26
41
  }
27
42
 
43
+ /// Returns the list of all registered signers.
28
44
  fn get_signers(env: &Env) -> Vec<BytesN<20>> {
29
- MultisigStorage::signers(env)
45
+ DvnStorage::signers(env)
30
46
  }
31
47
 
48
+ /// Returns the total number of registered signers.
32
49
  fn total_signers(env: &Env) -> u32 {
33
- MultisigStorage::signers(env).len()
50
+ DvnStorage::signers(env).len()
34
51
  }
35
52
 
53
+ /// Checks if an address is a registered signer.
36
54
  fn is_signer(env: &Env, signer: &BytesN<20>) -> bool {
37
- MultisigStorage::signers(env).iter().any(|s| &s == signer)
55
+ DvnStorage::signers(env).iter().any(|s| &s == signer)
38
56
  }
39
57
 
58
+ /// Returns the current signature threshold.
40
59
  fn threshold(env: &Env) -> u32 {
41
- MultisigStorage::threshold(env)
60
+ DvnStorage::threshold(env)
42
61
  }
43
62
 
63
+ /// Verifies signatures against the current threshold.
44
64
  fn verify_signatures(env: &Env, hash: &BytesN<32>, signatures: &Vec<BytesN<65>>) {
45
65
  Self::verify_n_signatures(env, hash, signatures, Self::threshold(env));
46
66
  }
47
67
 
68
+ /// Verifies that at least `threshold` valid signatures exist for the given hash.
69
+ ///
70
+ /// Signatures must be:
71
+ /// - From registered signers
72
+ /// - Sorted by signer address (ascending, no duplicates)
48
73
  fn verify_n_signatures(env: &Env, hash: &BytesN<32>, signatures: &Vec<BytesN<65>>, threshold: u32) {
49
- assert_with_error!(env, threshold > 0, MultisigError::ZeroThreshold);
50
- assert_with_error!(env, signatures.len() >= threshold, MultisigError::SignatureError);
74
+ assert_with_error!(env, threshold > 0, DvnError::ZeroThreshold);
75
+ assert_with_error!(env, signatures.len() >= threshold, DvnError::SignatureError);
51
76
 
52
77
  let mut last_signer: Option<BytesN<20>> = None;
53
78
  signatures.iter().for_each(|signature| {
54
- let signer = recover_signer(env, hash, &signature);
79
+ let signer = Self::recover_signer(env, hash, &signature);
55
80
 
56
- // Signers must be strictly increasing
57
- assert_with_error!(
58
- env,
59
- last_signer.as_ref().is_none_or(|last| &signer > last),
60
- MultisigError::UnsortedSigners
61
- );
62
- assert_with_error!(env, Self::is_signer(env, &signer), MultisigError::SignerNotFound);
81
+ // Signers must be strictly increasing (ensures no duplicates)
82
+ assert_with_error!(env, last_signer.as_ref().is_none_or(|last| &signer > last), DvnError::UnsortedSigners);
83
+ assert_with_error!(env, Self::is_signer(env, &signer), DvnError::SignerNotFound);
63
84
 
64
85
  last_signer = Some(signer);
65
86
  });
@@ -67,64 +88,66 @@ impl IMultisig for Dvn {
67
88
  }
68
89
 
69
90
  // ============================================================================
70
- // Internal Helper Functions
91
+ // Internal Functions
71
92
  // ============================================================================
72
93
 
73
- fn add_signer(env: &Env, signer: &BytesN<20>) {
74
- let zero_signer = BytesN::from_array(env, &[0u8; 20]);
75
- assert_with_error!(env, signer != &zero_signer, MultisigError::InvalidSigner);
76
-
77
- let mut signers = MultisigStorage::signers(env);
78
- assert_with_error!(env, !signers.iter().any(|s| &s == signer), MultisigError::SignerAlreadyExists);
79
- signers.push_back(signer.clone());
80
- MultisigStorage::set_signers(env, &signers);
81
-
82
- SignerSet { signer: signer.clone(), active: true }.publish(env);
83
- }
94
+ impl LzDVN {
95
+ /// Initializes the multisig with a set of signers and threshold.
96
+ /// Called during contract construction.
97
+ pub fn init_multisig(env: &Env, signers: &Vec<BytesN<20>>, threshold: u32) {
98
+ signers.iter().for_each(|signer| Self::add_signer(env, &signer));
99
+ Self::set_threshold(env, threshold);
100
+ }
84
101
 
85
- fn remove_signer(env: &Env, signer: &BytesN<20>) {
86
- let mut signers = MultisigStorage::signers(env);
87
- let index = signers.first_index_of(signer);
88
- assert_with_error!(env, index.is_some(), MultisigError::SignerNotFound);
89
-
90
- signers.remove(index.unwrap());
91
- // Check that removing didn't violate threshold constraint
92
- assert_with_error!(
93
- env,
94
- signers.len() >= MultisigStorage::threshold(env),
95
- MultisigError::TotalSignersLessThanThreshold
96
- );
97
- MultisigStorage::set_signers(env, &signers);
98
-
99
- SignerSet { signer: signer.clone(), active: false }.publish(env);
100
- }
102
+ /// Adds a new signer to the multisig.
103
+ ///
104
+ /// # Errors
105
+ /// - `InvalidSigner` if the signer is the zero address
106
+ /// - `SignerAlreadyExists` if the signer is already registered
107
+ fn add_signer(env: &Env, signer: &BytesN<20>) {
108
+ let zero_signer = BytesN::from_array(env, &[0u8; 20]);
109
+ assert_with_error!(env, signer != &zero_signer, DvnError::InvalidSigner);
110
+
111
+ let mut signers = Self::get_signers(env);
112
+ assert_with_error!(env, !signers.iter().any(|s| &s == signer), DvnError::SignerAlreadyExists);
113
+ signers.push_back(signer.clone());
114
+ DvnStorage::set_signers(env, &signers);
115
+
116
+ SignerSet { signer: signer.clone(), active: true }.publish(env);
117
+ }
101
118
 
102
- fn set_threshold(env: &Env, threshold: u32) {
103
- assert_with_error!(env, threshold > 0, MultisigError::ZeroThreshold);
104
- assert_with_error!(
105
- env,
106
- MultisigStorage::signers(env).len() >= threshold,
107
- MultisigError::TotalSignersLessThanThreshold
108
- );
119
+ /// Removes a signer from the multisig.
120
+ ///
121
+ /// # Errors
122
+ /// - `SignerNotFound` if the signer is not registered
123
+ /// - `TotalSignersLessThanThreshold` if removal would violate threshold constraint
124
+ fn remove_signer(env: &Env, signer: &BytesN<20>) {
125
+ let mut signers = Self::get_signers(env);
126
+ let index = signers.first_index_of(signer);
127
+ assert_with_error!(env, index.is_some(), DvnError::SignerNotFound);
109
128
 
110
- MultisigStorage::set_threshold(env, &threshold);
129
+ signers.remove(index.unwrap());
111
130
 
112
- ThresholdSet { threshold }.publish(env);
113
- }
131
+ // Ensure removal doesn't violate threshold constraint
132
+ assert_with_error!(env, signers.len() >= Self::threshold(env), DvnError::TotalSignersLessThanThreshold);
133
+ DvnStorage::set_signers(env, &signers);
114
134
 
115
- pub fn init_multisig(env: &Env, signers: &Vec<BytesN<20>>, threshold: u32) {
116
- signers.iter().for_each(|signer| add_signer(env, &signer));
117
-
118
- set_threshold(env, threshold);
119
- }
135
+ SignerSet { signer: signer.clone(), active: false }.publish(env);
136
+ }
120
137
 
121
- fn recover_signer(env: &Env, digest: &BytesN<32>, signature: &BytesN<65>) -> BytesN<20> {
122
- let sig_bytes: Bytes = signature.into();
123
- let v = sig_bytes.get(64).unwrap();
124
- let recovery_id = if (27..=30).contains(&v) { v - 27 } else { v };
125
- let sig_rs: BytesN<64> = sig_bytes.slice(0..64).try_into().unwrap();
138
+ /// Recovers the Ethereum-style signer address from a secp256k1 signature.
139
+ ///
140
+ /// The signature format is 65 bytes: r (32) + s (32) + v (1).
141
+ /// Returns the last 20 bytes of keccak256(uncompressed_pubkey[1..65]).
142
+ fn recover_signer(env: &Env, digest: &BytesN<32>, signature: &BytesN<65>) -> BytesN<20> {
143
+ let sig_bytes: Bytes = signature.into();
144
+ let v = sig_bytes.get(64).unwrap();
145
+ let recovery_id = if (27..=30).contains(&v) { v - 27 } else { v };
146
+ let sig_rs: BytesN<64> = sig_bytes.slice(0..64).try_into().unwrap();
126
147
 
127
- let public_key = env.crypto_hazmat().secp256k1_recover(digest, &sig_rs, recovery_id as u32);
148
+ let public_key = env.crypto_hazmat().secp256k1_recover(digest, &sig_rs, recovery_id as u32);
128
149
 
129
- Bytes::from(env.crypto().keccak256(&Bytes::from(public_key).slice(1..65))).slice(12..32).try_into().unwrap()
150
+ // Derive Ethereum address: keccak256(pubkey[1..65])[12..32]
151
+ Bytes::from(env.crypto().keccak256(&Bytes::from(public_key).slice(1..65))).slice(12..32).try_into().unwrap()
152
+ }
130
153
  }
@@ -2,33 +2,30 @@ use crate::DstConfig;
2
2
  use common_macros::storage;
3
3
  use soroban_sdk::{BytesN, Vec};
4
4
 
5
- // ============================================================================
6
- // Multisig Storage
7
- // ============================================================================
8
-
5
+ /// DVN contract storage keys.
9
6
  #[storage]
10
- pub enum MultisigStorage {
7
+ pub enum DvnStorage {
8
+ // ======================== Multisig ========================
9
+ /// List of authorized signer addresses (20-byte Ethereum addresses).
11
10
  #[persistent(Vec<BytesN<20>>)]
12
11
  #[default(Vec::new(env))]
13
12
  Signers,
14
13
 
14
+ /// Minimum number of signatures required for multisig operations.
15
15
  #[instance(u32)]
16
16
  #[default(0)]
17
17
  Threshold,
18
- }
19
18
 
20
- // ============================================================================
21
- // DVN Storage
22
- // ============================================================================
23
-
24
- #[storage]
25
- pub enum DvnStorage {
19
+ // ======================== DVN ============================
20
+ /// Verifier ID - unique identifier for this DVN instance.
26
21
  #[instance(u32)]
27
22
  Vid,
28
23
 
24
+ /// Destination chain configuration, keyed by endpoint ID.
29
25
  #[persistent(DstConfig)]
30
26
  DstConfig { dst_eid: u32 },
31
27
 
28
+ /// Tracks used hashes for replay protection.
32
29
  #[persistent(bool)]
33
30
  #[default(false)]
34
31
  UsedHash { hash: BytesN<32> },