@layerzerolabs/protocol-stellar-v2 0.2.34 → 0.2.35
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 +250 -251
- package/.turbo/turbo-lint.log +226 -231
- package/.turbo/turbo-test.log +1994 -1731
- package/Cargo.lock +10 -10
- package/Cargo.toml +1 -1
- package/contracts/common-macros/src/storage.rs +7 -5
- package/contracts/common-macros/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +3 -3
- package/contracts/endpoint-v2/src/endpoint_v2.rs +5 -4
- package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +7 -8
- package/contracts/endpoint-v2/src/messaging_channel.rs +78 -45
- package/contracts/endpoint-v2/src/storage.rs +8 -3
- package/contracts/endpoint-v2/src/tests/endpoint_setup.rs +2 -2
- package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +12 -15
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verifiable.rs +46 -9
- package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +7 -23
- package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +23 -20
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +94 -1
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_nonce.rs +17 -15
- package/contracts/endpoint-v2/src/tests/messaging_channel/mod.rs +1 -1
- package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +48 -13
- package/contracts/endpoint-v2/src/tests/messaging_channel/pending_inbound_nonces.rs +111 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +15 -25
- package/contracts/layerzero-views/src/layerzero_view.rs +2 -2
- package/contracts/layerzero-views/src/tests/layerzero_view_tests.rs +3 -4
- package/contracts/layerzero-views/src/tests/setup.rs +0 -21
- package/contracts/message-libs/blocked-message-lib/src/lib.rs +4 -4
- package/contracts/message-libs/uln-302/src/send_uln.rs +5 -5
- package/contracts/oapps/counter/src/counter.rs +6 -0
- package/contracts/oapps/oapp/src/oapp_sender.rs +3 -2
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +5 -0
- package/contracts/oapps/oft/src/oft.rs +5 -4
- package/docs/layerzero-v2-on-stellar.md +46 -2
- package/package.json +3 -3
- package/sdk/.turbo/turbo-test.log +312 -316
- package/sdk/dist/generated/bml.d.ts +3 -3
- package/sdk/dist/generated/bml.js +3 -3
- package/sdk/dist/generated/counter.d.ts +32 -3
- package/sdk/dist/generated/counter.js +6 -3
- package/sdk/dist/generated/dvn.d.ts +3 -3
- package/sdk/dist/generated/dvn.js +3 -3
- package/sdk/dist/generated/dvn_fee_lib.d.ts +2 -2
- package/sdk/dist/generated/dvn_fee_lib.js +2 -2
- package/sdk/dist/generated/endpoint.d.ts +12 -13
- package/sdk/dist/generated/endpoint.js +7 -7
- package/sdk/dist/generated/executor.d.ts +3 -3
- package/sdk/dist/generated/executor.js +3 -3
- package/sdk/dist/generated/executor_fee_lib.d.ts +2 -2
- package/sdk/dist/generated/executor_fee_lib.js +2 -2
- package/sdk/dist/generated/executor_helper.d.ts +2 -2
- package/sdk/dist/generated/executor_helper.js +2 -2
- package/sdk/dist/generated/layerzero_view.d.ts +3 -3
- package/sdk/dist/generated/layerzero_view.js +3 -3
- package/sdk/dist/generated/oft.d.ts +32 -3
- package/sdk/dist/generated/oft.js +6 -3
- package/sdk/dist/generated/price_feed.d.ts +3 -3
- package/sdk/dist/generated/price_feed.js +3 -3
- package/sdk/dist/generated/sac_manager.d.ts +24 -3
- package/sdk/dist/generated/sac_manager.js +4 -3
- package/sdk/dist/generated/sml.d.ts +2 -2
- package/sdk/dist/generated/sml.js +2 -2
- package/sdk/dist/generated/treasury.d.ts +2 -2
- package/sdk/dist/generated/treasury.js +2 -2
- package/sdk/dist/generated/uln302.d.ts +3 -3
- package/sdk/dist/generated/uln302.js +3 -3
- package/sdk/dist/generated/upgrader.d.ts +2 -2
- package/sdk/dist/generated/upgrader.js +2 -2
- package/sdk/package.json +1 -1
- package/contracts/endpoint-v2/src/tests/messaging_channel/lazy_inbound_nonce.rs +0 -39
|
@@ -53,7 +53,7 @@ fn test_skip_requires_auth_even_when_caller_is_receiver() {
|
|
|
53
53
|
endpoint_client.skip(&receiver, &receiver, &src_eid, &sender, &nonce);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// Successful skip updates inbound
|
|
56
|
+
// Successful skip updates inbound nonce and emits InboundNonceSkipped
|
|
57
57
|
#[test]
|
|
58
58
|
fn test_skip_success() {
|
|
59
59
|
let context = setup();
|
|
@@ -65,13 +65,10 @@ fn test_skip_success() {
|
|
|
65
65
|
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
66
66
|
let nonce = 1;
|
|
67
67
|
|
|
68
|
-
// Verify initial state: lazy inbound nonce should be 0.
|
|
69
|
-
let initial_lazy_nonce = endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender);
|
|
70
|
-
assert_eq!(initial_lazy_nonce, 0, "Initial lazy inbound nonce should be 0");
|
|
71
|
-
|
|
72
68
|
// Initially, inbound nonce should be 0.
|
|
73
69
|
let initial_nonce = endpoint_client.inbound_nonce(&receiver, &src_eid, &sender);
|
|
74
70
|
assert_eq!(initial_nonce, 0);
|
|
71
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
75
72
|
|
|
76
73
|
// Skip nonce 1 (expected nonce is initial_nonce + 1 = 1).
|
|
77
74
|
skip_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce);
|
|
@@ -86,10 +83,7 @@ fn test_skip_success() {
|
|
|
86
83
|
// Verify inbound nonce reflects the skip via public interface.
|
|
87
84
|
let updated_nonce = endpoint_client.inbound_nonce(&receiver, &src_eid, &sender);
|
|
88
85
|
assert_eq!(updated_nonce, nonce);
|
|
89
|
-
|
|
90
|
-
// Verify lazy inbound nonce was updated.
|
|
91
|
-
let lazy_nonce = endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender);
|
|
92
|
-
assert_eq!(lazy_nonce, nonce);
|
|
86
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
93
87
|
}
|
|
94
88
|
|
|
95
89
|
// Multiple sequential skips update to the latest nonce
|
|
@@ -111,13 +105,10 @@ fn test_skip_multiple_nonces() {
|
|
|
111
105
|
let nonce2 = 2;
|
|
112
106
|
skip_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce2);
|
|
113
107
|
|
|
114
|
-
// Verify lazy inbound nonce was updated to nonce2.
|
|
115
|
-
let lazy_nonce = endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender);
|
|
116
|
-
assert_eq!(lazy_nonce, nonce2);
|
|
117
|
-
|
|
118
108
|
// Verify inbound nonce reflects the latest skip.
|
|
119
109
|
let updated_nonce = endpoint_client.inbound_nonce(&receiver, &src_eid, &sender);
|
|
120
110
|
assert_eq!(updated_nonce, nonce2);
|
|
111
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
// Delegate authorization (delegate(receiver) is allowed)
|
|
@@ -146,9 +137,8 @@ fn test_skip_with_delegate() {
|
|
|
146
137
|
InboundNonceSkipped { src_eid, sender: sender.clone(), receiver: receiver.clone(), nonce },
|
|
147
138
|
);
|
|
148
139
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
assert_eq!(lazy_nonce, nonce);
|
|
140
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), nonce);
|
|
141
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
152
142
|
}
|
|
153
143
|
|
|
154
144
|
// Path isolation (receiver/src_eid/sender are isolated)
|
|
@@ -176,11 +166,11 @@ fn test_skip_different_paths() {
|
|
|
176
166
|
// Skip for different senders.
|
|
177
167
|
skip_with_auth(&context, &receiver1, &receiver1, src_eid1, &sender2, nonce);
|
|
178
168
|
|
|
179
|
-
// Verify all paths have independent
|
|
180
|
-
assert_eq!(endpoint_client.
|
|
181
|
-
assert_eq!(endpoint_client.
|
|
182
|
-
assert_eq!(endpoint_client.
|
|
183
|
-
assert_eq!(endpoint_client.
|
|
169
|
+
// Verify all paths have independent inbound nonces.
|
|
170
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver1, &src_eid1, &sender1), nonce);
|
|
171
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver2, &src_eid1, &sender1), nonce);
|
|
172
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver1, &src_eid2, &sender1), nonce);
|
|
173
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver1, &src_eid1, &sender2), nonce);
|
|
184
174
|
}
|
|
185
175
|
|
|
186
176
|
// Invalid nonce rejection (must match expected nonce)
|
|
@@ -224,10 +214,10 @@ fn test_skip_next_nonce_accounts_for_verified_payload_hashes() {
|
|
|
224
214
|
let result = endpoint_client.try_skip(&receiver, &receiver, &src_eid, &sender, &1);
|
|
225
215
|
assert_eq!(result.err().unwrap().ok().unwrap(), EndpointError::InvalidNonce.into());
|
|
226
216
|
|
|
227
|
-
// Skipping 2 should succeed and advance
|
|
217
|
+
// Skipping 2 should succeed and advance inbound nonce to 2.
|
|
228
218
|
skip_with_auth(&context, &receiver, &receiver, src_eid, &sender, 2);
|
|
229
|
-
assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
230
219
|
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
220
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
231
221
|
|
|
232
222
|
// skip() does not clear any existing payload hashes.
|
|
233
223
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &1), Some(payload_hash_1));
|
|
@@ -251,8 +241,8 @@ fn test_skip_closes_gap_and_advances_inbound_nonce() {
|
|
|
251
241
|
|
|
252
242
|
// Skip nonce 1 to close the gap. This should allow inbound_nonce to advance to 2.
|
|
253
243
|
skip_with_auth(&context, &receiver, &receiver, src_eid, &sender, 1);
|
|
254
|
-
assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
255
244
|
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
245
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
256
246
|
|
|
257
247
|
// Payload hash at nonce 2 remains intact.
|
|
258
248
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2), Some(payload_hash_2));
|
|
@@ -271,7 +261,7 @@ fn test_skip_rejects_repeated_same_nonce() {
|
|
|
271
261
|
|
|
272
262
|
// Skip nonce 1 successfully.
|
|
273
263
|
skip_with_auth(&context, &receiver, &receiver, src_eid, &sender, 1);
|
|
274
|
-
assert_eq!(endpoint_client.
|
|
264
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
275
265
|
|
|
276
266
|
// Skipping nonce 1 again should fail since the next expected nonce is now 2.
|
|
277
267
|
context.mock_auth(&receiver, "skip", (&receiver, &receiver, &src_eid, &sender, &1u64));
|
|
@@ -104,9 +104,9 @@ impl LayerZeroView {
|
|
|
104
104
|
let empty_hash = empty_payload_hash(env);
|
|
105
105
|
let nil_hash = nil_payload_hash(env);
|
|
106
106
|
|
|
107
|
-
// Executed: payload hash has been cleared (None) and nonce <=
|
|
107
|
+
// Executed: payload hash has been cleared (None) and nonce <= inbound_nonce
|
|
108
108
|
if payload_hash.is_none()
|
|
109
|
-
&& origin.nonce <= messaging_channel.
|
|
109
|
+
&& origin.nonce <= messaging_channel.inbound_nonce(receiver, &origin.src_eid, &origin.sender)
|
|
110
110
|
{
|
|
111
111
|
return ExecutionState::Executed;
|
|
112
112
|
}
|
|
@@ -206,9 +206,9 @@ fn test_executable_state_executed() {
|
|
|
206
206
|
let receiver = test_setup.register_oapp();
|
|
207
207
|
let sender = soroban_sdk::Address::generate(&test_setup.env);
|
|
208
208
|
|
|
209
|
-
// Clear payload hash (None) and set
|
|
209
|
+
// Clear payload hash (None) and set inbound_nonce >= nonce = Executed
|
|
210
210
|
test_setup.set_payload_hash(&receiver, REMOTE_EID, &sender, 1, &None);
|
|
211
|
-
test_setup.
|
|
211
|
+
test_setup.set_inbound_nonce(&receiver, REMOTE_EID, &sender, 1);
|
|
212
212
|
|
|
213
213
|
let origin = Origin { src_eid: REMOTE_EID, sender: address_to_bytes32(&sender), nonce: 1 };
|
|
214
214
|
|
|
@@ -262,9 +262,8 @@ fn test_executable_multiple_nonces_in_sequence() {
|
|
|
262
262
|
assert_eq!(test_setup.view_client.executable(&origin_2, &receiver), ExecutionState::VerifiedButNotExecutable);
|
|
263
263
|
assert_eq!(test_setup.view_client.executable(&origin_3, &receiver), ExecutionState::VerifiedButNotExecutable);
|
|
264
264
|
|
|
265
|
-
// Now execute nonce 1 (
|
|
265
|
+
// Now execute nonce 1 (clear payload hash, advance inbound_nonce)
|
|
266
266
|
test_setup.set_payload_hash(&receiver, REMOTE_EID, &sender, 1, &None);
|
|
267
|
-
test_setup.set_lazy_inbound_nonce(&receiver, REMOTE_EID, &sender, 1);
|
|
268
267
|
test_setup.set_inbound_nonce(&receiver, REMOTE_EID, &sender, 2);
|
|
269
268
|
|
|
270
269
|
assert_eq!(test_setup.view_client.executable(&origin_1, &receiver), ExecutionState::Executed);
|
|
@@ -30,7 +30,6 @@ mod endpoint_storage {
|
|
|
30
30
|
Initializable(Address, u32, BytesN<32>),
|
|
31
31
|
Verifiable(Address, u32, BytesN<32>),
|
|
32
32
|
// State for executable tests
|
|
33
|
-
LazyInboundNonce(Address, u32, BytesN<32>),
|
|
34
33
|
InboundNonce(Address, u32, BytesN<32>),
|
|
35
34
|
InboundPayloadHash(Address, u32, BytesN<32>, u64),
|
|
36
35
|
ReceiveLibrary(Address, u32),
|
|
@@ -91,13 +90,6 @@ impl MockEndpoint {
|
|
|
91
90
|
.set(&endpoint_storage::MockEndpointStorage::Verifiable(receiver.clone(), *src_eid, sender.clone()), value);
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
pub fn set_lazy_inbound_nonce(env: &Env, receiver: &Address, src_eid: &u32, sender: &BytesN<32>, nonce: &u64) {
|
|
95
|
-
env.storage().persistent().set(
|
|
96
|
-
&endpoint_storage::MockEndpointStorage::LazyInboundNonce(receiver.clone(), *src_eid, sender.clone()),
|
|
97
|
-
nonce,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
93
|
pub fn set_inbound_nonce(env: &Env, receiver: &Address, src_eid: &u32, sender: &BytesN<32>, nonce: &u64) {
|
|
102
94
|
env.storage().persistent().set(
|
|
103
95
|
&endpoint_storage::MockEndpointStorage::InboundNonce(receiver.clone(), *src_eid, sender.clone()),
|
|
@@ -135,13 +127,6 @@ impl MockEndpoint {
|
|
|
135
127
|
// Getters required by LayerZeroView
|
|
136
128
|
// =========================================================================
|
|
137
129
|
|
|
138
|
-
pub fn lazy_inbound_nonce(env: &Env, receiver: &Address, src_eid: &u32, sender: &BytesN<32>) -> u64 {
|
|
139
|
-
env.storage()
|
|
140
|
-
.persistent()
|
|
141
|
-
.get(&endpoint_storage::MockEndpointStorage::LazyInboundNonce(receiver.clone(), *src_eid, sender.clone()))
|
|
142
|
-
.unwrap_or(0)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
130
|
pub fn inbound_nonce(env: &Env, receiver: &Address, src_eid: &u32, sender: &BytesN<32>) -> u64 {
|
|
146
131
|
env.storage()
|
|
147
132
|
.persistent()
|
|
@@ -282,12 +267,6 @@ impl<'a> TestSetup<'a> {
|
|
|
282
267
|
self.endpoint_client.set_verifiable(receiver, &src_eid, &sender_bytes32, &value);
|
|
283
268
|
}
|
|
284
269
|
|
|
285
|
-
/// Set lazy inbound nonce (marks messages up to this nonce as processed).
|
|
286
|
-
pub fn set_lazy_inbound_nonce(&self, receiver: &Address, src_eid: u32, sender: &Address, nonce: u64) {
|
|
287
|
-
let sender_bytes32 = address_to_bytes32(sender);
|
|
288
|
-
self.endpoint_client.set_lazy_inbound_nonce(receiver, &src_eid, &sender_bytes32, &nonce);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
270
|
/// Set inbound nonce (marks messages up to this nonce as verified and executable).
|
|
292
271
|
pub fn set_inbound_nonce(&self, receiver: &Address, src_eid: u32, sender: &Address, nonce: u64) {
|
|
293
272
|
let sender_bytes32 = address_to_bytes32(sender);
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
#[cfg(test)]
|
|
16
16
|
mod tests;
|
|
17
17
|
|
|
18
|
-
use common_macros::
|
|
18
|
+
use common_macros::contract_error;
|
|
19
19
|
use endpoint_v2::{
|
|
20
20
|
FeesAndPacket, IMessageLib, ISendLib, MessageLibType, MessageLibVersion, MessagingFee, OutboundPacket,
|
|
21
21
|
SetConfigParam,
|
|
22
22
|
};
|
|
23
|
-
use soroban_sdk::{contract, panic_with_error, Address, Bytes, Env, Vec};
|
|
23
|
+
use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Bytes, Env, Vec};
|
|
24
24
|
|
|
25
25
|
#[contract_error]
|
|
26
26
|
pub enum BlockedMessageLibError {
|
|
@@ -31,7 +31,7 @@ pub enum BlockedMessageLibError {
|
|
|
31
31
|
#[contract]
|
|
32
32
|
pub struct BlockedMessageLib;
|
|
33
33
|
|
|
34
|
-
#[
|
|
34
|
+
#[contractimpl]
|
|
35
35
|
impl IMessageLib for BlockedMessageLib {
|
|
36
36
|
/// Always panics - config modification is not supported.
|
|
37
37
|
fn set_config(env: &Env, _oapp: &Address, _param: &Vec<SetConfigParam>) {
|
|
@@ -59,7 +59,7 @@ impl IMessageLib for BlockedMessageLib {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
#[
|
|
62
|
+
#[contractimpl]
|
|
63
63
|
impl ISendLib for BlockedMessageLib {
|
|
64
64
|
/// Always panics - quoting is blocked.
|
|
65
65
|
fn quote(env: &Env, _packet: &OutboundPacket, _options: &Bytes, _pay_in_zro: bool) -> MessagingFee {
|
|
@@ -45,7 +45,7 @@ impl ISendLib for Uln302 {
|
|
|
45
45
|
|
|
46
46
|
// Treasury fee
|
|
47
47
|
let workers_fee = executor_fee + dvns_fee;
|
|
48
|
-
let (_, treasury_fee) = Self::quote_treasury(env, &packet.sender, packet.dst_eid,
|
|
48
|
+
let (_, treasury_fee) = Self::quote_treasury(env, &packet.sender, packet.dst_eid, workers_fee, pay_in_zro);
|
|
49
49
|
|
|
50
50
|
if pay_in_zro {
|
|
51
51
|
MessagingFee { native_fee: workers_fee, zro_fee: treasury_fee }
|
|
@@ -91,7 +91,7 @@ impl ISendLib for Uln302 {
|
|
|
91
91
|
// Treasury fee
|
|
92
92
|
let total_worker_fee = native_fee_recipients.iter().map(|fee| fee.amount).sum();
|
|
93
93
|
let (treasury_addr, treasury_fee) =
|
|
94
|
-
Self::quote_treasury(env, &packet.sender, packet.dst_eid,
|
|
94
|
+
Self::quote_treasury(env, &packet.sender, packet.dst_eid, total_worker_fee, pay_in_zro);
|
|
95
95
|
|
|
96
96
|
// Handle ZRO fee recipients
|
|
97
97
|
let mut zro_fee_recipients = vec![env];
|
|
@@ -280,12 +280,12 @@ impl Uln302 {
|
|
|
280
280
|
env: &Env,
|
|
281
281
|
sender: &Address,
|
|
282
282
|
dst_eid: u32,
|
|
283
|
-
workers_fee:
|
|
284
|
-
pay_in_zro:
|
|
283
|
+
workers_fee: i128,
|
|
284
|
+
pay_in_zro: bool,
|
|
285
285
|
) -> (Address, i128) {
|
|
286
286
|
let treasury_addr = Self::treasury(env);
|
|
287
287
|
let treasury_fee =
|
|
288
|
-
LayerZeroTreasuryClient::new(env, &treasury_addr).get_fee(sender, &dst_eid, workers_fee, pay_in_zro);
|
|
288
|
+
LayerZeroTreasuryClient::new(env, &treasury_addr).get_fee(sender, &dst_eid, &workers_fee, &pay_in_zro);
|
|
289
289
|
assert_with_error!(env, treasury_fee >= 0, Uln302Error::InvalidFee);
|
|
290
290
|
(treasury_addr, treasury_fee)
|
|
291
291
|
}
|
|
@@ -67,6 +67,12 @@ impl Counter {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
#[only_auth]
|
|
71
|
+
pub fn withdraw(env: &Env, to: &Address, amount: i128) {
|
|
72
|
+
let native_token = LayerZeroEndpointV2Client::new(env, &Self::endpoint(env)).native_token();
|
|
73
|
+
TokenClient::new(env, &native_token).transfer(&env.current_contract_address(), to, &amount);
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
// ============================================================================================
|
|
71
77
|
// View functions
|
|
72
78
|
// ============================================================================================
|
|
@@ -3,7 +3,7 @@ use crate::{
|
|
|
3
3
|
oapp_core::{endpoint_client, get_peer_or_panic, OAppCore},
|
|
4
4
|
};
|
|
5
5
|
use endpoint_v2::{MessagingFee, MessagingParams, MessagingReceipt};
|
|
6
|
-
use soroban_sdk::{token::TokenClient, Address, Bytes, Env};
|
|
6
|
+
use soroban_sdk::{contracttype, token::TokenClient, Address, Bytes, Env};
|
|
7
7
|
use utils::option_ext::OptionExt;
|
|
8
8
|
|
|
9
9
|
/// The version of the OAppSender implementation.
|
|
@@ -22,7 +22,8 @@ pub const SENDER_VERSION: u64 = 1;
|
|
|
22
22
|
/// - `Verified` — Caller asserts that `require_auth()` has already been called.
|
|
23
23
|
/// Use this to avoid a duplicate `require_auth()` node in the Soroban auth tree
|
|
24
24
|
/// (e.g., when the same address was already authorized as the message sender).
|
|
25
|
-
#[
|
|
25
|
+
#[contracttype]
|
|
26
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
26
27
|
pub enum FeePayer {
|
|
27
28
|
/// The fee payer has **not** been authorized yet.
|
|
28
29
|
/// `__lz_send` will call `fee_payer.require_auth()` before transferring fees.
|
|
@@ -122,6 +122,11 @@ pub trait OFTFee: OFTFeeInternal + Auth {
|
|
|
122
122
|
Self::__effective_fee_bps(env, dst_eid)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/// Returns true if the OFT has a fee rate greater than 0 for the specified destination
|
|
126
|
+
fn has_oft_fee(env: &soroban_sdk::Env, dst_eid: u32) -> bool {
|
|
127
|
+
Self::__effective_fee_bps(env, dst_eid) > 0
|
|
128
|
+
}
|
|
129
|
+
|
|
125
130
|
/// Returns the fee deposit address.
|
|
126
131
|
fn fee_deposit_address(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
127
132
|
Self::__fee_deposit_address(env)
|
|
@@ -80,21 +80,22 @@ impl OFTInternal for OFT {
|
|
|
80
80
|
/// Overrides default to add pausable check and fee calculation.
|
|
81
81
|
///
|
|
82
82
|
/// Dust handling (consistent with EVM):
|
|
83
|
-
/// - fee
|
|
84
|
-
/// - fee
|
|
83
|
+
/// - no fee: dust stays with sender (amount_sent_ld has dust removed)
|
|
84
|
+
/// - has fee: dust is absorbed into the charged fee (amount_sent_ld is the full amount)
|
|
85
85
|
fn __debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> (i128, i128) {
|
|
86
86
|
Self::__assert_not_paused(env);
|
|
87
87
|
|
|
88
88
|
let conversion_rate = Self::__decimal_conversion_rate(env);
|
|
89
|
-
let
|
|
89
|
+
let has_fee = Self::has_oft_fee(env, dst_eid);
|
|
90
90
|
|
|
91
|
-
let (amount_sent_ld, amount_received_ld) = if
|
|
91
|
+
let (amount_sent_ld, amount_received_ld) = if !has_fee {
|
|
92
92
|
// No fee: dust stays with sender (default OFT behavior)
|
|
93
93
|
let amount_sent_ld = oft_utils::remove_dust(amount_ld, conversion_rate);
|
|
94
94
|
(amount_sent_ld, amount_sent_ld)
|
|
95
95
|
} else {
|
|
96
96
|
// With fee: match EVM OFTFee behavior
|
|
97
97
|
// - sender pays full amount_ld (no dust removed), dust is absorbed into the charged fee
|
|
98
|
+
let fee = Self::__fee_view(env, dst_eid, amount_ld);
|
|
98
99
|
let amount_received_ld = oft_utils::remove_dust(amount_ld - fee, conversion_rate);
|
|
99
100
|
(amount_ld, amount_received_ld)
|
|
100
101
|
};
|
|
@@ -221,12 +221,16 @@ sequenceDiagram
|
|
|
221
221
|
|
|
222
222
|
## Stellar-specific considerations
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
Several differences between Soroban and EVM require protocol-level adaptations:
|
|
225
225
|
|
|
226
226
|
1. Stellar's variable-length address model differs from LayerZero's fixed
|
|
227
227
|
bytes32 address abstraction
|
|
228
228
|
2. Soroban's Time-To-Live (TTL)-based storage model requires active state
|
|
229
229
|
maintenance, unlike EVM's persistent storage
|
|
230
|
+
3. Soroban's 200-read-per-transaction storage limit makes the lazy inbound
|
|
231
|
+
nonce model susceptible to denial-of-service for certain OApps
|
|
232
|
+
4. Soroban prohibits reentrancy, requiring alternative patterns for cross-contract
|
|
233
|
+
call flows
|
|
230
234
|
|
|
231
235
|
### Constraint 1: bytes32 address format mismatch
|
|
232
236
|
|
|
@@ -296,7 +300,47 @@ constraints may evolve.
|
|
|
296
300
|
- Hard upper cap on extension targets (e.g., 1 year) to prevent excessive fees
|
|
297
301
|
- TTL parameters can be permanently frozen once the ecosystem stabilizes
|
|
298
302
|
|
|
299
|
-
### Constraint 3:
|
|
303
|
+
### Constraint 3: Storage read limits
|
|
304
|
+
|
|
305
|
+
Soroban enforces a hard limit of 200 persistent/temporary storage reads per
|
|
306
|
+
transaction. Under the lazy inbound nonce model used in EVM, the `inbound_nonce`
|
|
307
|
+
is not stored directly — it is computed on the fly by iterating forward from the
|
|
308
|
+
last checkpoint (`lazy_inbound_nonce`) and probing storage for each consecutive
|
|
309
|
+
payload hash. The same iterative check occurs during `clear`, which must verify
|
|
310
|
+
that all nonces between the checkpoint and the target nonce have been verified.
|
|
311
|
+
|
|
312
|
+
For certain OApps, failed `lz_receive` executions can create nonce gaps that
|
|
313
|
+
grow over time. Under the lazy model, clearing subsequent messages requires
|
|
314
|
+
iterating across these gaps, and the accumulated storage reads can exceed the
|
|
315
|
+
200-read limit, making the messaging path susceptible to denial-of-service.
|
|
316
|
+
|
|
317
|
+
**Solution: Eager inbound nonce with pending nonce list (Solana model)**
|
|
318
|
+
|
|
319
|
+
Stellar adopts the same inbound nonce model used by LayerZero V2 on Solana.
|
|
320
|
+
Instead of lazily computing the inbound nonce via storage probing, the
|
|
321
|
+
`inbound_nonce` is stored directly and updated eagerly during verification:
|
|
322
|
+
|
|
323
|
+
1. **`PendingInboundNonces`**: A sorted list of out-of-order verified nonces is
|
|
324
|
+
maintained in a single storage entry per path. When a message is verified
|
|
325
|
+
(or skipped/nilified), its nonce is inserted into this list.
|
|
326
|
+
|
|
327
|
+
2. **Drain on insert**: After each insertion, consecutive nonces at the front of
|
|
328
|
+
the list are drained to advance the `inbound_nonce`. For example, if
|
|
329
|
+
`inbound_nonce = 3` and the pending list becomes `[4, 5, 7]`, nonces 4 and 5
|
|
330
|
+
are drained, advancing `inbound_nonce` to 5.
|
|
331
|
+
|
|
332
|
+
3. **O(1) clear**: The `clear_payload` operation becomes a simple comparison
|
|
333
|
+
(`nonce <= inbound_nonce`) with no iteration or storage probing.
|
|
334
|
+
|
|
335
|
+
4. **Bounded list size**: The pending list is capped at 256 entries
|
|
336
|
+
(`PENDING_INBOUND_NONCE_MAX_LEN`). Nonces beyond `inbound_nonce + 256`
|
|
337
|
+
cannot be verified, preventing unbounded memory growth and limiting the
|
|
338
|
+
maximum storage reads per verify operation.
|
|
339
|
+
|
|
340
|
+
This eliminates the iterative storage reads that could cause DoS under the lazy
|
|
341
|
+
model, keeping all operations within Soroban's transaction resource limits.
|
|
342
|
+
|
|
343
|
+
### Constraint 4: Reentrancy prohibition
|
|
300
344
|
|
|
301
345
|
Soroban prohibits reentrancy—a contract cannot call itself, directly or
|
|
302
346
|
indirectly, within the same transaction. This fundamental difference from EVM
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layerzerolabs/protocol-stellar-v2",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.35",
|
|
4
4
|
"private": false,
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@types/node": "^22.18.6",
|
|
7
7
|
"tsx": "^4.19.3",
|
|
8
8
|
"typescript": "^5.8.2",
|
|
9
|
-
"@layerzerolabs/common-node-utils": "0.2.
|
|
10
|
-
"@layerzerolabs/vm-tooling-stellar": "0.2.
|
|
9
|
+
"@layerzerolabs/common-node-utils": "0.2.35",
|
|
10
|
+
"@layerzerolabs/vm-tooling-stellar": "0.2.35"
|
|
11
11
|
},
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "restricted",
|