@layerzerolabs/protocol-stellar-v2 0.2.53 → 0.2.55

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.
@@ -494,24 +494,19 @@ fn setup_chain_workers<'a>(
494
494
  let executor_helper = ExecutorHelperClient::new(env, &executor_helper_address);
495
495
 
496
496
  // Register the executor helper with the executor (address + allowed function names)
497
- let allowed_functions: Vec<Symbol> = vec![
498
- env,
499
- Symbol::new(env, "execute"),
500
- Symbol::new(env, "compose"),
501
- Symbol::new(env, "native_drop_and_execute"),
502
- ];
503
- let admin = &infra.admin;
497
+ let allowed_functions: Vec<Symbol> =
498
+ vec![env, Symbol::new(env, "execute"), Symbol::new(env, "compose")];
504
499
  let lz_executor = LzExecutorClient::new(env, &executor_address);
505
500
  env.mock_auths(&[MockAuth {
506
- address: admin,
501
+ address: owner,
507
502
  invoke: &MockAuthInvoke {
508
503
  contract: &executor_address,
509
504
  fn_name: "set_executor_helper",
510
- args: (admin, &executor_helper_address, &allowed_functions).into_val(env),
505
+ args: (&executor_helper_address, &allowed_functions).into_val(env),
511
506
  sub_invokes: &[],
512
507
  },
513
508
  }]);
514
- lz_executor.set_executor_helper(admin, &executor_helper_address, &allowed_functions);
509
+ lz_executor.set_executor_helper(&executor_helper_address, &allowed_functions);
515
510
 
516
511
  (dvn, dvn2, executor, executor_helper)
517
512
  }
@@ -2,9 +2,10 @@ use super::*;
2
2
  use crate::{Sender, TransactionAuthData};
3
3
  use soroban_sdk::{
4
4
  address_payload::AddressPayload,
5
+ assert_with_error,
5
6
  auth::{Context, CustomAccountInterface},
6
7
  crypto::Hash,
7
- vec, Symbol,
8
+ Bytes, Symbol,
8
9
  };
9
10
 
10
11
  // ============================================================================
@@ -23,9 +24,12 @@ impl CustomAccountInterface for LzDVN {
23
24
  auth_data: Self::Signature,
24
25
  auth_contexts: Vec<Context>,
25
26
  ) -> Result<(), Self::Error> {
26
- let TransactionAuthData { vid, expiration, signatures, sender } = auth_data;
27
+ let TransactionAuthData { vid, expiration, signatures, sender, preimage } = auth_data;
27
28
 
28
- // 1. Check VID and expiration
29
+ // 1. Verify preimage matches signature_payload
30
+ let invocation = Self::verify_preimage(&env, &signature_payload, &preimage);
31
+
32
+ // 2. Check VID and expiration
29
33
  if vid != Self::vid(&env) {
30
34
  return Err(DvnError::InvalidVid);
31
35
  }
@@ -33,21 +37,20 @@ impl CustomAccountInterface for LzDVN {
33
37
  return Err(DvnError::AuthDataExpired);
34
38
  }
35
39
 
36
- // 2. Admin verification (`set_admin` bypasses this - allows quorum-only admin changes)
40
+ // 3. Admin verification (`set_admin` bypasses this - allows quorum-only admin changes)
37
41
  let requires_admin = !Self::is_invoking_worker_set_admin(&env, &auth_contexts);
38
42
  if requires_admin {
39
43
  Self::verify_admin_signature(&env, &sender, &signature_payload)?;
40
44
  }
41
45
 
42
- // 3. Replay protection
43
- let calls = Self::extract_contract_calls(&env, &auth_contexts)?;
44
- let hash = Self::hash_call_data(&env, vid, expiration, &calls);
46
+ // 4. Replay protection
47
+ let hash = Self::hash_call_data(&env, vid, expiration, &invocation);
45
48
  if DvnStorage::used_hash(&env, &hash) {
46
49
  return Err(DvnError::HashAlreadyUsed);
47
50
  }
48
51
  DvnStorage::set_used_hash(&env, &hash, &true);
49
52
 
50
- // 4. MultiSig verification (most expensive - do last)
53
+ // 5. MultiSig verification (most expensive - do last)
51
54
  Self::verify_signatures(&env, &hash, &signatures);
52
55
 
53
56
  Ok(())
@@ -59,6 +62,17 @@ impl CustomAccountInterface for LzDVN {
59
62
  // ============================================================================
60
63
 
61
64
  impl LzDVN {
65
+ /// Verifies the preimage matches the host-provided `signature_payload`
66
+ /// and returns the invocation bytes extracted from it.
67
+ fn verify_preimage(env: &Env, signature_payload: &Hash<32>, preimage: &Bytes) -> Bytes {
68
+ let computed = env.crypto().sha256(preimage);
69
+ assert_with_error!(env, computed.to_bytes() == signature_payload.to_bytes(), DvnError::InvalidPreimage);
70
+
71
+ // Skip the 48-byte fixed prefix:
72
+ // envelope_type (4) + network_id (32) + nonce (8) + expiry_ledger (4) = 48
73
+ preimage.slice(48..)
74
+ }
75
+
62
76
  /// Verifies that the sender is an admin with a valid signature.
63
77
  ///
64
78
  /// # Errors
@@ -78,18 +92,6 @@ impl LzDVN {
78
92
  Ok(())
79
93
  }
80
94
 
81
- /// Collects contract calls from the auth contexts.
82
- fn extract_contract_calls(env: &Env, auth_contexts: &Vec<Context>) -> Result<Vec<Call>, DvnError> {
83
- let mut calls = vec![env];
84
- for context in auth_contexts.iter() {
85
- let Context::Contract(ctx) = context else {
86
- return Err(DvnError::NonContractInvoke);
87
- };
88
- calls.push_back(Call { to: ctx.contract, func: ctx.fn_name, args: ctx.args });
89
- }
90
- Ok(calls)
91
- }
92
-
93
95
  /// Checks if the first auth context is a call to `set_admin` on this contract.
94
96
  ///
95
97
  /// Used to allow quorum-only admin changes without requiring existing admin authorization.
@@ -5,12 +5,12 @@
5
5
  //! signatures for authorization and implements Soroban's custom account
6
6
  //! interface for transaction signing.
7
7
 
8
- use crate::{errors::DvnError, events::SetDstConfig, storage::DvnStorage, Call, DstConfig, DstConfigParam, IDVN};
8
+ use crate::{errors::DvnError, events::SetDstConfig, storage::DvnStorage, DstConfig, DstConfigParam, IDVN};
9
9
  use common_macros::{contract_impl, lz_contract};
10
10
  use endpoint_v2::FeeRecipient;
11
11
  use fee_lib_interfaces::{DvnFeeLibClient, DvnFeeParams};
12
12
  use message_lib_common::interfaces::ILayerZeroDVN;
13
- use soroban_sdk::{xdr::ToXdr, Address, Bytes, BytesN, Env, Vec};
13
+ use soroban_sdk::{Address, Bytes, BytesN, Env, Vec};
14
14
  use utils::{buffer_writer::BufferWriter, multisig, option_ext::OptionExt};
15
15
  use worker::{
16
16
  assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, set_admin_by_admin,
@@ -107,12 +107,12 @@ impl IDVN for LzDVN {
107
107
  DvnStorage::vid(env).unwrap()
108
108
  }
109
109
 
110
- /// Computes the hash of call data for multisig signing.
110
+ /// Computes the hash of invocation data for multisig signing.
111
111
  ///
112
112
  /// Off-chain signers use this to compute the hash they need to sign.
113
- fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32> {
113
+ fn hash_call_data(env: &Env, vid: u32, expiration: u64, invocation: &Bytes) -> BytesN<32> {
114
114
  let mut writer = BufferWriter::new(env);
115
- let data = writer.write_u32(vid).write_u64(expiration).write_bytes(&calls.to_xdr(env)).to_bytes();
115
+ let data = writer.write_u32(vid).write_u64(expiration).write_bytes(invocation).to_bytes();
116
116
  env.crypto().keccak256(&data).into()
117
117
  }
118
118
  }
@@ -5,7 +5,7 @@ pub enum DvnError {
5
5
  AuthDataExpired,
6
6
  EidNotSupported,
7
7
  HashAlreadyUsed,
8
+ InvalidPreimage,
8
9
  InvalidVid,
9
- NonContractInvoke,
10
10
  OnlyAdmin,
11
11
  }
@@ -1,5 +1,5 @@
1
1
  use message_lib_common::interfaces::ILayerZeroDVN;
2
- use soroban_sdk::{auth::CustomAccountInterface, contractclient, contracttype, Address, BytesN, Env, Symbol, Val, Vec};
2
+ use soroban_sdk::{auth::CustomAccountInterface, contractclient, contracttype, Address, Bytes, BytesN, Env, Vec};
3
3
  use utils::multisig::MultiSig;
4
4
  use worker::Worker;
5
5
 
@@ -32,6 +32,9 @@ pub struct TransactionAuthData {
32
32
  pub signatures: Vec<BytesN<65>>,
33
33
  /// Entity submitting the transaction (admin, or permissionless).
34
34
  pub sender: Sender,
35
+ /// XDR-encoded `HashIdPreimage::SorobanAuthorization` from the auth entry.
36
+ /// Layout: envelope_type (4) || network_id (32) || nonce (8) || expiry_ledger (4) || invocation (variable).
37
+ pub preimage: Bytes,
35
38
  }
36
39
 
37
40
  // ============================================================================
@@ -63,20 +66,6 @@ pub struct DstConfigParam {
63
66
  pub config: DstConfig,
64
67
  }
65
68
 
66
- /// Represents a single contract invocation for multisig authorization.
67
- ///
68
- /// Used in `hash_call_data` to compute the hash that signers sign over.
69
- #[contracttype]
70
- #[derive(Clone, Debug, Eq, PartialEq)]
71
- pub struct Call {
72
- /// Target contract address.
73
- pub to: Address,
74
- /// Function name to invoke.
75
- pub func: Symbol,
76
- /// Function arguments.
77
- pub args: Vec<Val>,
78
- }
79
-
80
69
  /// DVN (Decentralized Verifier Network) contract interface.
81
70
  ///
82
71
  /// Extends the LayerZero DVN interface with destination configuration management
@@ -104,17 +93,17 @@ pub trait IDVN: ILayerZeroDVN + Worker + MultiSig + CustomAccountInterface {
104
93
  /// The VID is a unique identifier used in multisig authentication.
105
94
  fn vid(env: &Env) -> u32;
106
95
 
107
- /// Computes the hash of call data for multisig signing.
96
+ /// Computes the hash of invocation data for multisig signing.
108
97
  ///
109
98
  /// Off-chain signers use this to compute the hash they need to sign.
110
- /// The hash includes the VID, expiration, and the calls being authorized.
99
+ /// The hash includes the VID, expiration, and the raw invocation XDR.
111
100
  ///
112
101
  /// # Arguments
113
102
  /// * `vid` - Verifier ID (must match contract's VID)
114
103
  /// * `expiration` - Expiration timestamp for the authorization
115
- /// * `calls` - The contract calls being authorized
104
+ /// * `invocation` - XDR-encoded `SorobanAuthorizedInvocation` from the auth entry
116
105
  ///
117
106
  /// # Returns
118
107
  /// A 32-byte keccak256 hash to be signed by the multisig quorum
119
- fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32>;
108
+ fn hash_call_data(env: &Env, vid: u32, expiration: u64, invocation: &Bytes) -> BytesN<32>;
120
109
  }
@@ -1,11 +1,27 @@
1
1
  use crate::{
2
2
  errors::DvnError,
3
3
  tests::setup::{TestSetup, VID},
4
- Sender, TransactionAuthData,
4
+ LzDVNClient, Sender, TransactionAuthData,
5
5
  };
6
6
  use ed25519_dalek::{Signer, SigningKey};
7
7
  use rand::thread_rng;
8
- use soroban_sdk::{auth::Context, vec, Bytes, BytesN, Env, IntoVal, Vec};
8
+ use soroban_sdk::{auth::Context, vec, xdr::ToXdr, Bytes, BytesN, Env, IntoVal, Vec};
9
+ use utils::buffer_writer::BufferWriter;
10
+
11
+ const AUTH_NONCE: i64 = 0;
12
+ const AUTH_EXPIRY_LEDGER: u32 = 10000;
13
+ const ENVELOPE_TYPE_SOROBAN_AUTHORIZATION: u32 = 9;
14
+
15
+ /// Build the full preimage bytes from auth fields.
16
+ fn build_preimage(env: &Env, auth_nonce: i64, auth_expiry_ledger: u32, invocation: &Bytes) -> Bytes {
17
+ BufferWriter::new(env)
18
+ .write_u32(ENVELOPE_TYPE_SOROBAN_AUTHORIZATION)
19
+ .write_bytes_n(&env.ledger().network_id())
20
+ .write_i64(auth_nonce)
21
+ .write_u32(auth_expiry_ledger)
22
+ .write_bytes(invocation)
23
+ .to_bytes()
24
+ }
9
25
 
10
26
  struct Ed25519KeyPair {
11
27
  signing_key: SigningKey,
@@ -30,14 +46,16 @@ impl Ed25519KeyPair {
30
46
  }
31
47
  }
32
48
 
33
- fn hash_auth_data(env: &Env, vid: u32, expiration: u64, auth_contexts: &Vec<Context>) -> BytesN<32> {
34
- use soroban_sdk::xdr::ToXdr;
35
- let mut data = Bytes::new(env);
36
- data.extend_from_array(&vid.to_be_bytes());
37
- data.extend_from_array(&expiration.to_be_bytes());
38
- data.append(&auth_contexts.to_xdr(env));
39
- let hash = env.crypto().keccak256(&data);
40
- BytesN::from_array(env, &hash.to_array())
49
+ /// Compute signature_payload = SHA256(preimage) for the given preimage bytes.
50
+ fn compute_signature_payload(env: &Env, preimage: &Bytes) -> BytesN<32> {
51
+ env.crypto().sha256(preimage).into()
52
+ }
53
+
54
+ /// Compute call hash for multisig: keccak256(vid || expiration || invocation).
55
+ fn compute_call_hash(env: &Env, vid: u32, expiration: u64, invocation: &Bytes) -> BytesN<32> {
56
+ let mut writer = BufferWriter::new(env);
57
+ let data = writer.write_u32(vid).write_u64(expiration).write_bytes(invocation).to_bytes();
58
+ env.crypto().keccak256(&data).into()
41
59
  }
42
60
 
43
61
  #[test]
@@ -51,11 +69,13 @@ fn test_check_auth_success() {
51
69
  let expiration = env.ledger().timestamp() + 1000;
52
70
  let auth_contexts: Vec<Context> = Vec::new(&env);
53
71
 
54
- let payload = BytesN::from_array(&env, &[0u8; 32]);
72
+ let invocation = auth_contexts.clone().to_xdr(&env);
73
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
74
+ let payload = compute_signature_payload(&env, &preimage);
55
75
  let public_key = admin_kp.public_key(&env);
56
76
  let signature = admin_kp.sign(&env, &payload.to_array());
57
77
 
58
- let hash = hash_auth_data(&env, VID, expiration, &auth_contexts);
78
+ let hash = compute_call_hash(&env, VID, expiration, &invocation);
59
79
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
60
80
 
61
81
  let tx_auth = TransactionAuthData {
@@ -63,6 +83,7 @@ fn test_check_auth_success() {
63
83
  expiration,
64
84
  signatures: vec![&env, sig],
65
85
  sender: Sender::Admin(public_key, signature),
86
+ preimage,
66
87
  };
67
88
 
68
89
  let res = env.try_invoke_contract_check_auth::<DvnError>(
@@ -85,11 +106,13 @@ fn test_check_auth_not_admin() {
85
106
  let expiration = env.ledger().timestamp() + 1000;
86
107
  let auth_contexts: Vec<Context> = Vec::new(&env);
87
108
 
88
- let payload = BytesN::from_array(&env, &[0u8; 32]);
109
+ let invocation = auth_contexts.clone().to_xdr(&env);
110
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
111
+ let payload = compute_signature_payload(&env, &preimage);
89
112
  let public_key = non_admin_kp.public_key(&env);
90
113
  let signature = non_admin_kp.sign(&env, &payload.to_array());
91
114
 
92
- let hash = hash_auth_data(&env, VID, expiration, &auth_contexts);
115
+ let hash = compute_call_hash(&env, VID, expiration, &invocation);
93
116
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
94
117
 
95
118
  let tx_auth = TransactionAuthData {
@@ -97,6 +120,7 @@ fn test_check_auth_not_admin() {
97
120
  expiration,
98
121
  signatures: vec![&env, sig],
99
122
  sender: Sender::Admin(public_key, signature),
123
+ preimage,
100
124
  };
101
125
 
102
126
  let res = env.try_invoke_contract_check_auth::<DvnError>(
@@ -120,11 +144,13 @@ fn test_check_auth_wrong_signer_fails() {
120
144
  let expiration = env.ledger().timestamp() + 1000;
121
145
  let auth_contexts: Vec<Context> = Vec::new(&env);
122
146
 
123
- let payload = BytesN::from_array(&env, &[0u8; 32]);
147
+ let invocation = auth_contexts.clone().to_xdr(&env);
148
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
149
+ let payload = compute_signature_payload(&env, &preimage);
124
150
  let public_key = admin_kp.public_key(&env);
125
151
  let signature = admin_kp.sign(&env, &payload.to_array());
126
152
 
127
- let hash = hash_auth_data(&env, VID, expiration, &auth_contexts);
153
+ let hash = compute_call_hash(&env, VID, expiration, &invocation);
128
154
  let wrong_sig = crate::tests::key_pair::KeyPair::generate().sign_bytes(&env, &hash);
129
155
 
130
156
  let tx_auth = TransactionAuthData {
@@ -132,9 +158,9 @@ fn test_check_auth_wrong_signer_fails() {
132
158
  expiration,
133
159
  signatures: vec![&env, wrong_sig],
134
160
  sender: Sender::Admin(public_key, signature),
161
+ preimage,
135
162
  };
136
163
 
137
- // verify_signatures panics with MultiSigError::SignerNotFound when signer is not found
138
164
  let res = env.try_invoke_contract_check_auth::<utils::errors::MultiSigError>(
139
165
  &setup.contract_id,
140
166
  &payload,
@@ -157,11 +183,13 @@ fn test_check_auth_invalid_vid_fails() {
157
183
  let wrong_vid = VID + 1;
158
184
  let auth_contexts: Vec<Context> = Vec::new(&env);
159
185
 
160
- let payload = BytesN::from_array(&env, &[0u8; 32]);
186
+ let invocation = auth_contexts.clone().to_xdr(&env);
187
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
188
+ let payload = compute_signature_payload(&env, &preimage);
161
189
  let public_key = admin_kp.public_key(&env);
162
190
  let signature = admin_kp.sign(&env, &payload.to_array());
163
191
 
164
- let hash = hash_auth_data(&env, wrong_vid, expiration, &auth_contexts);
192
+ let hash = compute_call_hash(&env, wrong_vid, expiration, &invocation);
165
193
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
166
194
 
167
195
  let tx_auth = TransactionAuthData {
@@ -169,6 +197,7 @@ fn test_check_auth_invalid_vid_fails() {
169
197
  expiration,
170
198
  signatures: vec![&env, sig],
171
199
  sender: Sender::Admin(public_key, signature),
200
+ preimage,
172
201
  };
173
202
 
174
203
  let res = env.try_invoke_contract_check_auth::<DvnError>(
@@ -192,11 +221,13 @@ fn test_check_auth_expired_fails() {
192
221
  let expiration = env.ledger().timestamp();
193
222
  let auth_contexts: Vec<Context> = Vec::new(&env);
194
223
 
195
- let payload = BytesN::from_array(&env, &[0u8; 32]);
224
+ let invocation = auth_contexts.clone().to_xdr(&env);
225
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
226
+ let payload = compute_signature_payload(&env, &preimage);
196
227
  let public_key = admin_kp.public_key(&env);
197
228
  let signature = admin_kp.sign(&env, &payload.to_array());
198
229
 
199
- let hash = hash_auth_data(&env, VID, expiration, &auth_contexts);
230
+ let hash = compute_call_hash(&env, VID, expiration, &invocation);
200
231
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
201
232
 
202
233
  let tx_auth = TransactionAuthData {
@@ -204,6 +235,7 @@ fn test_check_auth_expired_fails() {
204
235
  expiration,
205
236
  signatures: vec![&env, sig],
206
237
  sender: Sender::Admin(public_key, signature),
238
+ preimage,
207
239
  };
208
240
 
209
241
  let res = env.try_invoke_contract_check_auth::<DvnError>(
@@ -227,11 +259,13 @@ fn test_check_auth_hash_already_used_fails() {
227
259
  let expiration = env.ledger().timestamp() + 1000;
228
260
  let auth_contexts: Vec<Context> = Vec::new(&env);
229
261
 
230
- let payload = BytesN::from_array(&env, &[0u8; 32]);
262
+ let invocation = auth_contexts.clone().to_xdr(&env);
263
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
264
+ let payload = compute_signature_payload(&env, &preimage);
231
265
  let public_key = admin_kp.public_key(&env);
232
266
  let signature = admin_kp.sign(&env, &payload.to_array());
233
267
 
234
- let hash = hash_auth_data(&env, VID, expiration, &auth_contexts);
268
+ let hash = compute_call_hash(&env, VID, expiration, &invocation);
235
269
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
236
270
 
237
271
  let tx_auth = TransactionAuthData {
@@ -239,6 +273,7 @@ fn test_check_auth_hash_already_used_fails() {
239
273
  expiration,
240
274
  signatures: vec![&env, sig.clone()],
241
275
  sender: Sender::Admin(public_key.clone(), signature.clone()),
276
+ preimage: preimage.clone(),
242
277
  };
243
278
 
244
279
  let res = env.try_invoke_contract_check_auth::<DvnError>(
@@ -254,6 +289,7 @@ fn test_check_auth_hash_already_used_fails() {
254
289
  expiration,
255
290
  signatures: vec![&env, sig],
256
291
  sender: Sender::Admin(public_key, signature),
292
+ preimage,
257
293
  };
258
294
 
259
295
  let res2 = env.try_invoke_contract_check_auth::<DvnError>(
@@ -269,25 +305,21 @@ fn test_check_auth_hash_already_used_fails() {
269
305
  #[test]
270
306
  fn test_check_auth_sender_none_fails_when_admin_required() {
271
307
  extern crate std;
272
- use crate::Sender;
273
308
 
274
309
  let setup = TestSetup::new(1);
275
310
  let env = setup.env.clone();
276
311
  let expiration = env.ledger().timestamp() + 1000;
277
312
  let auth_contexts: Vec<Context> = Vec::new(&env);
278
313
 
279
- let payload = BytesN::from_array(&env, &[0u8; 32]);
314
+ let invocation = auth_contexts.clone().to_xdr(&env);
315
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
316
+ let payload = compute_signature_payload(&env, &preimage);
280
317
 
281
- let hash = hash_auth_data(&env, VID, expiration, &auth_contexts);
318
+ let hash = compute_call_hash(&env, VID, expiration, &invocation);
282
319
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
283
320
 
284
- // Use Sender::None which should fail when admin is required
285
- let tx_auth = TransactionAuthData {
286
- vid: VID,
287
- expiration,
288
- signatures: vec![&env, sig],
289
- sender: Sender::None,
290
- };
321
+ let tx_auth =
322
+ TransactionAuthData { vid: VID, expiration, signatures: vec![&env, sig], sender: Sender::None, preimage };
291
323
 
292
324
  let res = env.try_invoke_contract_check_auth::<DvnError>(
293
325
  &setup.contract_id,
@@ -302,14 +334,12 @@ fn test_check_auth_sender_none_fails_when_admin_required() {
302
334
  #[test]
303
335
  fn test_check_auth_set_admin_bypasses_admin_verification() {
304
336
  extern crate std;
305
- use crate::{Call, LzDVNClient, Sender};
306
337
  use soroban_sdk::{auth::ContractContext, Symbol};
307
338
 
308
339
  let setup = TestSetup::new(1);
309
340
  let env = setup.env.clone();
310
341
  let expiration = env.ledger().timestamp() + 1000;
311
342
 
312
- // Create a context for set_admin call on the DVN contract
313
343
  let set_admin_context = Context::Contract(ContractContext {
314
344
  contract: setup.contract_id.clone(),
315
345
  fn_name: Symbol::new(&env, "set_admin"),
@@ -317,30 +347,16 @@ fn test_check_auth_set_admin_bypasses_admin_verification() {
317
347
  });
318
348
  let auth_contexts: Vec<Context> = vec![&env, set_admin_context.clone()];
319
349
 
320
- let payload = BytesN::from_array(&env, &[0u8; 32]);
350
+ let invocation = auth_contexts.clone().to_xdr(&env);
351
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
352
+ let payload = compute_signature_payload(&env, &preimage);
321
353
 
322
- // Create Call structs matching what extract_contract_calls would produce
323
- let calls: Vec<Call> = vec![
324
- &env,
325
- Call {
326
- to: setup.contract_id.clone(),
327
- func: Symbol::new(&env, "set_admin"),
328
- args: Vec::new(&env),
329
- }
330
- ];
331
-
332
- // Use the contract's hash_call_data function directly
333
354
  let dvn_client = LzDVNClient::new(&env, &setup.contract_id);
334
- let hash = dvn_client.hash_call_data(&VID, &expiration, &calls);
355
+ let hash = dvn_client.hash_call_data(&VID, &expiration, &invocation);
335
356
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
336
357
 
337
- // Use Sender::None - should succeed for set_admin since it bypasses admin verification
338
- let tx_auth = TransactionAuthData {
339
- vid: VID,
340
- expiration,
341
- signatures: vec![&env, sig],
342
- sender: Sender::None,
343
- };
358
+ let tx_auth =
359
+ TransactionAuthData { vid: VID, expiration, signatures: vec![&env, sig], sender: Sender::None, preimage };
344
360
 
345
361
  let res = env.try_invoke_contract_check_auth::<DvnError>(
346
362
  &setup.contract_id,
@@ -349,43 +365,41 @@ fn test_check_auth_set_admin_bypasses_admin_verification() {
349
365
  &auth_contexts,
350
366
  );
351
367
 
352
- // Should succeed because set_admin bypasses admin verification
353
368
  assert!(res.is_ok(), "Expected success for set_admin call, got {:?}", res);
354
369
  }
355
370
 
356
371
  #[test]
357
- fn test_check_auth_non_contract_context_fails() {
372
+ fn test_check_auth_invalid_signature_payload_fails() {
358
373
  extern crate std;
359
- use crate::Sender;
360
- use soroban_sdk::auth::{ContractExecutable, CreateContractHostFnContext};
361
-
362
374
  let admin_kp = Ed25519KeyPair::generate();
363
375
  let admin_bytes = admin_kp.public_key_bytes();
364
376
 
365
377
  let setup = TestSetup::with_admin_bytes(1, std::vec![admin_bytes]);
366
378
  let env = setup.env.clone();
367
379
  let expiration = env.ledger().timestamp() + 1000;
380
+ let auth_contexts: Vec<Context> = Vec::new(&env);
368
381
 
369
- // Create a non-Contract context (CreateContractHostFn)
370
- let non_contract_context = Context::CreateContractHostFn(CreateContractHostFnContext {
371
- executable: ContractExecutable::Wasm(BytesN::from_array(&env, &[0; 32])),
372
- salt: BytesN::from_array(&env, &[0; 32]),
373
- });
374
- let auth_contexts: Vec<Context> = vec![&env, non_contract_context];
375
-
376
- let payload = BytesN::from_array(&env, &[0u8; 32]);
382
+ let invocation = auth_contexts.clone().to_xdr(&env);
383
+ // Compute the correct payload so admin signature is valid
384
+ let preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &invocation);
385
+ let payload = compute_signature_payload(&env, &preimage);
377
386
  let public_key = admin_kp.public_key(&env);
378
387
  let signature = admin_kp.sign(&env, &payload.to_array());
379
388
 
380
- // Use empty hash for signatures since we expect early error
381
- let hash = hash_auth_data(&env, VID, expiration, &auth_contexts);
389
+ let hash = compute_call_hash(&env, VID, expiration, &invocation);
382
390
  let sig = setup.key_pairs[0].sign_bytes(&env, &hash);
383
391
 
392
+ // Tamper with preimage so it no longer matches the signature_payload
393
+ let mut tampered_invocation = Bytes::new(&env);
394
+ tampered_invocation.extend_from_array(&[0u8; 32]);
395
+ let tampered_preimage = build_preimage(&env, AUTH_NONCE, AUTH_EXPIRY_LEDGER, &tampered_invocation);
396
+
384
397
  let tx_auth = TransactionAuthData {
385
398
  vid: VID,
386
399
  expiration,
387
400
  signatures: vec![&env, sig],
388
401
  sender: Sender::Admin(public_key, signature),
402
+ preimage: tampered_preimage,
389
403
  };
390
404
 
391
405
  let res = env.try_invoke_contract_check_auth::<DvnError>(
@@ -395,6 +409,5 @@ fn test_check_auth_non_contract_context_fails() {
395
409
  &auth_contexts,
396
410
  );
397
411
 
398
- // Should fail with NonContractInvoke error
399
- assert_eq!(res, Err(Ok(DvnError::NonContractInvoke)));
412
+ assert_eq!(res, Err(Ok(DvnError::InvalidPreimage)));
400
413
  }
@@ -5,7 +5,7 @@ use crate::{
5
5
  storage::ExecutorStorage,
6
6
  NativeDropParams,
7
7
  };
8
- use common_macros::{contract_impl, lz_contract};
8
+ use common_macros::{contract_impl, lz_contract, only_auth};
9
9
  use endpoint_v2::{FeeRecipient, LayerZeroEndpointV2Client, Origin};
10
10
  use fee_lib_interfaces::{ExecutorFeeLibClient, FeeParams};
11
11
  use message_lib_common::interfaces::ILayerZeroExecutor;
@@ -76,11 +76,10 @@ impl LzExecutor {
76
76
  /// used by `validate_auth_contexts` to verify authorization contexts.
77
77
  ///
78
78
  /// # Arguments
79
- /// * `admin` - Admin address (must provide authorization)
80
79
  /// * `helper` - The executor helper contract address
81
80
  /// * `allowed_functions` - Function names the helper is allowed to invoke (e.g., "execute", "compose")
82
- pub fn set_executor_helper(env: &Env, admin: &Address, helper: &Address, allowed_functions: &Vec<Symbol>) {
83
- require_admin_auth::<Self>(env, admin);
81
+ #[only_auth]
82
+ pub fn set_executor_helper(env: &Env, helper: &Address, allowed_functions: &Vec<Symbol>) {
84
83
  ExecutorStorage::set_executor_helper(
85
84
  env,
86
85
  &crate::storage::ExecutorHelperConfig {
@@ -12,7 +12,7 @@ use crate::DstConfig;
12
12
  pub struct ExecutorHelperConfig {
13
13
  /// The executor helper contract address.
14
14
  pub address: Address,
15
- /// Allowed function names on the helper (e.g., "execute", "compose", "native_drop_and_execute").
15
+ /// Allowed function names on the helper (e.g., "execute", "compose").
16
16
  pub allowed_functions: Vec<Symbol>,
17
17
  }
18
18
 
@@ -167,12 +167,8 @@ impl<'a> TestSetup<'a> {
167
167
 
168
168
  // Register executor helper config directly in storage
169
169
  let executor_helper = Address::generate(&env);
170
- let allowed_functions: Vec<Symbol> = vec![
171
- &env,
172
- Symbol::new(&env, "execute"),
173
- Symbol::new(&env, "compose"),
174
- Symbol::new(&env, "native_drop_and_execute"),
175
- ];
170
+ let allowed_functions: Vec<Symbol> =
171
+ vec![&env, Symbol::new(&env, "execute"), Symbol::new(&env, "compose")];
176
172
  env.as_contract(&contract_id, || {
177
173
  crate::storage::ExecutorStorage::set_executor_helper(
178
174
  &env,