@layerzerolabs/protocol-stellar-v2 0.2.60 → 0.2.61
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 +64 -65
- package/.turbo/turbo-lint.log +68 -73
- package/.turbo/turbo-test.log +1953 -1882
- package/contracts/workers/dvn/src/auth.rs +67 -30
- package/contracts/workers/dvn/src/dvn.rs +31 -6
- package/contracts/workers/dvn/src/errors.rs +4 -1
- package/contracts/workers/dvn/src/events.rs +7 -1
- package/contracts/workers/dvn/src/interfaces/dvn.rs +34 -8
- package/contracts/workers/dvn/src/storage.rs +5 -1
- package/contracts/workers/dvn/src/tests/auth.rs +539 -90
- package/package.json +4 -4
- package/sdk/.turbo/turbo-test.log +303 -269
- package/sdk/dist/generated/dvn.d.ts +59 -11
- package/sdk/dist/generated/dvn.js +21 -10
- package/sdk/package.json +1 -1
- package/sdk/test/counter-uln.test.ts +34 -15
- package/sdk/test/upgrader.test.ts +21 -0
- package/sdk/test/utils.ts +101 -14
|
@@ -2,10 +2,9 @@ use super::*;
|
|
|
2
2
|
use crate::{Sender, TransactionAuthData};
|
|
3
3
|
use soroban_sdk::{
|
|
4
4
|
address_payload::AddressPayload,
|
|
5
|
-
assert_with_error,
|
|
6
5
|
auth::{Context, CustomAccountInterface},
|
|
7
6
|
crypto::Hash,
|
|
8
|
-
|
|
7
|
+
vec, Symbol,
|
|
9
8
|
};
|
|
10
9
|
|
|
11
10
|
// ============================================================================
|
|
@@ -24,12 +23,9 @@ impl CustomAccountInterface for LzDVN {
|
|
|
24
23
|
auth_data: Self::Signature,
|
|
25
24
|
auth_contexts: Vec<Context>,
|
|
26
25
|
) -> Result<(), Self::Error> {
|
|
27
|
-
let TransactionAuthData { vid, expiration, signatures, sender
|
|
26
|
+
let TransactionAuthData { vid, expiration, signatures, sender } = auth_data;
|
|
28
27
|
|
|
29
|
-
// 1.
|
|
30
|
-
let invocation = Self::verify_preimage(&env, &signature_payload, &preimage);
|
|
31
|
-
|
|
32
|
-
// 2. Check VID and expiration
|
|
28
|
+
// 1. Check VID and expiration
|
|
33
29
|
if vid != Self::vid(&env) {
|
|
34
30
|
return Err(DvnError::InvalidVid);
|
|
35
31
|
}
|
|
@@ -37,14 +33,24 @@ impl CustomAccountInterface for LzDVN {
|
|
|
37
33
|
return Err(DvnError::AuthDataExpired);
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
//
|
|
41
|
-
let
|
|
42
|
-
|
|
36
|
+
// 2. Extract and validate calls from auth contexts
|
|
37
|
+
let (calls, is_set_admin) = match auth_contexts.len() {
|
|
38
|
+
1 => {
|
|
39
|
+
let call = Self::extract_single_self_call(&env, &auth_contexts)?;
|
|
40
|
+
let is_set_admin = call.func == Symbol::new(&env, "set_admin");
|
|
41
|
+
(vec![&env, call], is_set_admin)
|
|
42
|
+
}
|
|
43
|
+
3 => (Self::extract_upgrade_calls(&env, &auth_contexts)?, false),
|
|
44
|
+
_ => return Err(DvnError::InvalidAuthContext),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// 3. Admin verification (set_admin bypasses)
|
|
48
|
+
if !is_set_admin {
|
|
43
49
|
Self::verify_admin_signature(&env, &sender, &signature_payload)?;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
// 4. Replay protection
|
|
47
|
-
let hash = Self::hash_call_data(&env, vid, expiration, &
|
|
53
|
+
let hash = Self::hash_call_data(&env, vid, expiration, &calls);
|
|
48
54
|
if DvnStorage::used_hash(&env, &hash) {
|
|
49
55
|
return Err(DvnError::HashAlreadyUsed);
|
|
50
56
|
}
|
|
@@ -62,17 +68,6 @@ impl CustomAccountInterface for LzDVN {
|
|
|
62
68
|
// ============================================================================
|
|
63
69
|
|
|
64
70
|
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
|
-
|
|
76
71
|
/// Verifies that the sender is an admin with a valid signature.
|
|
77
72
|
///
|
|
78
73
|
/// # Errors
|
|
@@ -92,14 +87,56 @@ impl LzDVN {
|
|
|
92
87
|
Ok(())
|
|
93
88
|
}
|
|
94
89
|
|
|
95
|
-
///
|
|
90
|
+
/// Extracts a single self-targeting contract call from auth_contexts.
|
|
91
|
+
fn extract_single_self_call(env: &Env, auth_contexts: &Vec<Context>) -> Result<Call, DvnError> {
|
|
92
|
+
let Context::Contract(ctx) = auth_contexts.get(0).unwrap() else {
|
|
93
|
+
return Err(DvnError::NonContractInvoke);
|
|
94
|
+
};
|
|
95
|
+
if ctx.contract != env.current_contract_address() {
|
|
96
|
+
return Err(DvnError::InvalidAuthContext);
|
|
97
|
+
}
|
|
98
|
+
Ok(Call { to: ctx.contract, func: ctx.fn_name, args: ctx.args })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Extracts and validates upgrade auth contexts (3 entries, positional).
|
|
96
102
|
///
|
|
97
|
-
///
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
/// Expected order:
|
|
104
|
+
/// - `[0]`: Upgrader contract call (must target the registered upgrader)
|
|
105
|
+
/// - `[1]`: `upgrade` self-call
|
|
106
|
+
/// - `[2]`: `migrate` self-call
|
|
107
|
+
fn extract_upgrade_calls(env: &Env, auth_contexts: &Vec<Context>) -> Result<Vec<Call>, DvnError> {
|
|
108
|
+
let self_addr = env.current_contract_address();
|
|
109
|
+
let upgrader_addr = Self::upgrader(env).ok_or(DvnError::UpgraderNotSet)?;
|
|
110
|
+
|
|
111
|
+
// [0]: Upgrader contract call
|
|
112
|
+
let Context::Contract(ctx0) = auth_contexts.get(0).unwrap() else {
|
|
113
|
+
return Err(DvnError::NonContractInvoke);
|
|
114
|
+
};
|
|
115
|
+
if ctx0.contract != upgrader_addr {
|
|
116
|
+
return Err(DvnError::InvalidUpgradeContext);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// [1]: upgrade self-call
|
|
120
|
+
let Context::Contract(ctx1) = auth_contexts.get(1).unwrap() else {
|
|
121
|
+
return Err(DvnError::NonContractInvoke);
|
|
122
|
+
};
|
|
123
|
+
if ctx1.contract != self_addr || ctx1.fn_name != Symbol::new(env, "upgrade") {
|
|
124
|
+
return Err(DvnError::InvalidUpgradeContext);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// [2]: migrate self-call
|
|
128
|
+
let Context::Contract(ctx2) = auth_contexts.get(2).unwrap() else {
|
|
129
|
+
return Err(DvnError::NonContractInvoke);
|
|
130
|
+
};
|
|
131
|
+
if ctx2.contract != self_addr || ctx2.fn_name != Symbol::new(env, "migrate") {
|
|
132
|
+
return Err(DvnError::InvalidUpgradeContext);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Ok(vec![
|
|
136
|
+
env,
|
|
137
|
+
Call { to: ctx0.contract, func: ctx0.fn_name, args: ctx0.args },
|
|
138
|
+
Call { to: ctx1.contract, func: ctx1.fn_name, args: ctx1.args },
|
|
139
|
+
Call { to: ctx2.contract, func: ctx2.fn_name, args: ctx2.args },
|
|
140
|
+
])
|
|
104
141
|
}
|
|
105
142
|
}
|
|
@@ -5,12 +5,17 @@
|
|
|
5
5
|
//! signatures for authorization and implements Soroban's custom account
|
|
6
6
|
//! interface for transaction signing.
|
|
7
7
|
|
|
8
|
-
use crate::{
|
|
9
|
-
|
|
8
|
+
use crate::{
|
|
9
|
+
errors::DvnError,
|
|
10
|
+
events::{SetDstConfig, SetUpgrader},
|
|
11
|
+
storage::DvnStorage,
|
|
12
|
+
Call, DstConfig, DstConfigParam, IDVN,
|
|
13
|
+
};
|
|
14
|
+
use common_macros::{contract_impl, lz_contract, only_auth};
|
|
10
15
|
use endpoint_v2::FeeRecipient;
|
|
11
16
|
use fee_lib_interfaces::{DvnFeeLibClient, DvnFeeParams};
|
|
12
17
|
use message_lib_common::interfaces::ILayerZeroDVN;
|
|
13
|
-
use soroban_sdk::{Address, Bytes, BytesN, Env, Vec};
|
|
18
|
+
use soroban_sdk::{xdr::ToXdr, Address, Bytes, BytesN, Env, Val, Vec};
|
|
14
19
|
use utils::{buffer_writer::BufferWriter, multisig, option_ext::OptionExt};
|
|
15
20
|
use worker::{
|
|
16
21
|
assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, set_admin_by_admin,
|
|
@@ -88,6 +93,26 @@ impl LzDVN {
|
|
|
88
93
|
|
|
89
94
|
#[contract_impl]
|
|
90
95
|
impl IDVN for LzDVN {
|
|
96
|
+
/// Dispatches external contract calls.
|
|
97
|
+
#[only_auth]
|
|
98
|
+
fn execute_transaction(env: &Env, calls: &Vec<Call>) {
|
|
99
|
+
for call in calls.iter() {
|
|
100
|
+
env.invoke_contract::<Val>(&call.to, &call.func, call.args);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Sets the upgrader contract address. Requires self authorization.
|
|
105
|
+
#[only_auth]
|
|
106
|
+
fn set_upgrader(env: &Env, upgrader: &Option<Address>) {
|
|
107
|
+
DvnStorage::set_or_remove_upgrader(env, upgrader);
|
|
108
|
+
SetUpgrader { upgrader: upgrader.clone() }.publish(env);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Returns the current upgrader contract address, if set.
|
|
112
|
+
fn upgrader(env: &Env) -> Option<Address> {
|
|
113
|
+
DvnStorage::upgrader(env)
|
|
114
|
+
}
|
|
115
|
+
|
|
91
116
|
/// Sets destination chain configurations. Requires admin authorization.
|
|
92
117
|
fn set_dst_config(env: &Env, admin: &Address, params: &Vec<DstConfigParam>) {
|
|
93
118
|
require_admin_auth::<Self>(env, admin);
|
|
@@ -107,12 +132,12 @@ impl IDVN for LzDVN {
|
|
|
107
132
|
DvnStorage::vid(env).unwrap()
|
|
108
133
|
}
|
|
109
134
|
|
|
110
|
-
/// Computes the hash of
|
|
135
|
+
/// Computes the hash of call data for multisig signing.
|
|
111
136
|
///
|
|
112
137
|
/// Off-chain signers use this to compute the hash they need to sign.
|
|
113
|
-
fn hash_call_data(env: &Env, vid: u32, expiration: u64,
|
|
138
|
+
fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32> {
|
|
114
139
|
let mut writer = BufferWriter::new(env);
|
|
115
|
-
let data = writer.write_u32(vid).write_u64(expiration).write_bytes(
|
|
140
|
+
let data = writer.write_u32(vid).write_u64(expiration).write_bytes(&calls.to_xdr(env)).to_bytes();
|
|
116
141
|
env.crypto().keccak256(&data).into()
|
|
117
142
|
}
|
|
118
143
|
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
use crate::DstConfigParam;
|
|
2
|
-
use soroban_sdk::{contractevent, Vec};
|
|
2
|
+
use soroban_sdk::{contractevent, Address, Vec};
|
|
3
3
|
|
|
4
4
|
#[contractevent]
|
|
5
5
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
6
6
|
pub struct SetDstConfig {
|
|
7
7
|
pub params: Vec<DstConfigParam>,
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
#[contractevent]
|
|
11
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
12
|
+
pub struct SetUpgrader {
|
|
13
|
+
pub upgrader: Option<Address>,
|
|
14
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
use message_lib_common::interfaces::ILayerZeroDVN;
|
|
2
|
-
use soroban_sdk::{auth::CustomAccountInterface, contractclient, contracttype, Address,
|
|
2
|
+
use soroban_sdk::{auth::CustomAccountInterface, contractclient, contracttype, Address, BytesN, Env, Symbol, Val, Vec};
|
|
3
3
|
use utils::multisig::MultiSig;
|
|
4
4
|
use worker::Worker;
|
|
5
5
|
|
|
@@ -32,9 +32,6 @@ 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,
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
// ============================================================================
|
|
@@ -66,12 +63,32 @@ pub struct DstConfigParam {
|
|
|
66
63
|
pub config: DstConfig,
|
|
67
64
|
}
|
|
68
65
|
|
|
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
|
+
|
|
69
80
|
/// DVN (Decentralized Verifier Network) contract interface.
|
|
70
81
|
///
|
|
71
82
|
/// Extends the LayerZero DVN interface with destination configuration management
|
|
72
83
|
/// and multisig capabilities for secure cross-chain message verification.
|
|
73
84
|
#[contractclient(name = "DVNClient")]
|
|
74
85
|
pub trait IDVN: ILayerZeroDVN + Worker + MultiSig + CustomAccountInterface {
|
|
86
|
+
/// Dispatches a list of external contract calls.
|
|
87
|
+
///
|
|
88
|
+
/// # Arguments
|
|
89
|
+
/// * `calls` - List of calls to execute atomically
|
|
90
|
+
fn execute_transaction(env: &Env, calls: &Vec<Call>);
|
|
91
|
+
|
|
75
92
|
/// Sets the configuration for one or more destination chains.
|
|
76
93
|
///
|
|
77
94
|
/// # Arguments
|
|
@@ -93,17 +110,26 @@ pub trait IDVN: ILayerZeroDVN + Worker + MultiSig + CustomAccountInterface {
|
|
|
93
110
|
/// The VID is a unique identifier used in multisig authentication.
|
|
94
111
|
fn vid(env: &Env) -> u32;
|
|
95
112
|
|
|
96
|
-
///
|
|
113
|
+
/// Sets or clears the registered upgrader contract address.
|
|
114
|
+
///
|
|
115
|
+
/// Protected by `#[only_auth]` (multisig quorum required).
|
|
116
|
+
/// Pass `None` to remove the upgrader.
|
|
117
|
+
fn set_upgrader(env: &Env, upgrader: &Option<Address>);
|
|
118
|
+
|
|
119
|
+
/// Returns the registered upgrader contract address, if any.
|
|
120
|
+
fn upgrader(env: &Env) -> Option<Address>;
|
|
121
|
+
|
|
122
|
+
/// Computes the hash of call data for multisig signing.
|
|
97
123
|
///
|
|
98
124
|
/// Off-chain signers use this to compute the hash they need to sign.
|
|
99
|
-
/// The hash includes the VID, expiration, and the
|
|
125
|
+
/// The hash includes the VID, expiration, and the calls being authorized.
|
|
100
126
|
///
|
|
101
127
|
/// # Arguments
|
|
102
128
|
/// * `vid` - Verifier ID (must match contract's VID)
|
|
103
129
|
/// * `expiration` - Expiration timestamp for the authorization
|
|
104
|
-
/// * `
|
|
130
|
+
/// * `calls` - The contract calls being authorized
|
|
105
131
|
///
|
|
106
132
|
/// # Returns
|
|
107
133
|
/// A 32-byte keccak256 hash to be signed by the multisig quorum
|
|
108
|
-
fn hash_call_data(env: &Env, vid: u32, expiration: u64,
|
|
134
|
+
fn hash_call_data(env: &Env, vid: u32, expiration: u64, calls: &Vec<Call>) -> BytesN<32>;
|
|
109
135
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use crate::DstConfig;
|
|
2
2
|
use common_macros::storage;
|
|
3
|
-
use soroban_sdk::BytesN;
|
|
3
|
+
use soroban_sdk::{Address, BytesN};
|
|
4
4
|
|
|
5
5
|
/// DVN contract storage keys.
|
|
6
6
|
///
|
|
@@ -11,6 +11,10 @@ pub enum DvnStorage {
|
|
|
11
11
|
#[instance(u32)]
|
|
12
12
|
Vid,
|
|
13
13
|
|
|
14
|
+
/// Registered upgrader contract address for upgrade operations.
|
|
15
|
+
#[instance(Address)]
|
|
16
|
+
Upgrader,
|
|
17
|
+
|
|
14
18
|
/// Destination chain configuration, keyed by endpoint ID.
|
|
15
19
|
#[persistent(DstConfig)]
|
|
16
20
|
DstConfig { dst_eid: u32 },
|