@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.
- package/.turbo/turbo-build.log +73 -232
- package/.turbo/turbo-lint.log +162 -98
- package/.turbo/turbo-test.log +1874 -2078
- package/contracts/oapps/counter/integration_tests/setup_uln.rs +5 -10
- package/contracts/workers/dvn/src/auth.rs +22 -20
- package/contracts/workers/dvn/src/dvn.rs +5 -5
- package/contracts/workers/dvn/src/errors.rs +1 -1
- package/contracts/workers/dvn/src/interfaces/dvn.rs +8 -19
- package/contracts/workers/dvn/src/tests/auth.rs +85 -72
- package/contracts/workers/executor/src/executor.rs +3 -4
- package/contracts/workers/executor/src/storage.rs +1 -1
- package/contracts/workers/executor/src/tests/setup.rs +2 -6
- package/contracts/workers/executor-helper/src/executor_helper.rs +9 -33
- package/contracts/workers/executor-helper/src/tests/executor_helper.rs +0 -122
- package/docs/oft-guide.md +15 -0
- package/package.json +4 -4
- package/sdk/.turbo/turbo-test.log +333 -283
- package/sdk/dist/generated/dvn.d.ts +11 -25
- package/sdk/dist/generated/dvn.js +8 -9
- package/sdk/dist/generated/executor.d.ts +5 -7
- package/sdk/dist/generated/executor.js +5 -5
- package/sdk/dist/generated/executor_helper.d.ts +3 -17
- package/sdk/dist/generated/executor_helper.js +4 -6
- package/sdk/package.json +1 -1
- package/sdk/test/counter-sml.test.ts +31 -5
- package/sdk/test/counter-uln.test.ts +40 -14
- package/sdk/test/suites/globalSetup.ts +1 -2
- package/sdk/test/utils.ts +14 -81
|
@@ -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> =
|
|
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:
|
|
501
|
+
address: owner,
|
|
507
502
|
invoke: &MockAuthInvoke {
|
|
508
503
|
contract: &executor_address,
|
|
509
504
|
fn_name: "set_executor_helper",
|
|
510
|
-
args: (
|
|
505
|
+
args: (&executor_helper_address, &allowed_functions).into_val(env),
|
|
511
506
|
sub_invokes: &[],
|
|
512
507
|
},
|
|
513
508
|
}]);
|
|
514
|
-
lz_executor.set_executor_helper(
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
43
|
-
let
|
|
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
|
-
//
|
|
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,
|
|
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::{
|
|
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
|
|
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,
|
|
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(
|
|
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
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
use message_lib_common::interfaces::ILayerZeroDVN;
|
|
2
|
-
use soroban_sdk::{auth::CustomAccountInterface, contractclient, contracttype, Address, BytesN, Env,
|
|
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
|
|
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
|
|
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
|
-
/// * `
|
|
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,
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
285
|
-
|
|
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
|
|
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, &
|
|
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
|
-
|
|
338
|
-
|
|
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
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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"
|
|
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> =
|
|
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,
|