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