From c8f078ddbc6636eaefc5aa3e35bebc236f3c487d Mon Sep 17 00:00:00 2001 From: Goutham Date: Thu, 9 Jul 2026 16:15:42 +0530 Subject: [PATCH 1/3] fix(executor): pass native token address via constructor --- .../executor-soroban-client/src/constants.rs | 5 --- .../executor-soroban-client/src/lib.rs | 2 -- .../contracts/wormhole-executors/src/lib.rs | 31 ++++++++++-------- .../contracts/wormhole-executors/src/tests.rs | 32 +++++++++++-------- 4 files changed, 35 insertions(+), 35 deletions(-) delete mode 100644 stellar/contracts/executor-soroban-client/src/constants.rs diff --git a/stellar/contracts/executor-soroban-client/src/constants.rs b/stellar/contracts/executor-soroban-client/src/constants.rs deleted file mode 100644 index 7d83643..0000000 --- a/stellar/contracts/executor-soroban-client/src/constants.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Public constants for the Wormhole Executor contract. - -/// Native XLM Stellar Asset Contract address (deterministic, same on all -/// networks). -pub const NATIVE_TOKEN_ADDRESS: &str = "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"; diff --git a/stellar/contracts/executor-soroban-client/src/lib.rs b/stellar/contracts/executor-soroban-client/src/lib.rs index 366c656..81899be 100644 --- a/stellar/contracts/executor-soroban-client/src/lib.rs +++ b/stellar/contracts/executor-soroban-client/src/lib.rs @@ -5,11 +5,9 @@ #![no_std] -pub mod constants; pub mod error; pub mod types; -pub use constants::*; pub use error::ExecutorError; pub use types::*; diff --git a/stellar/contracts/wormhole-executors/src/lib.rs b/stellar/contracts/wormhole-executors/src/lib.rs index 4d0b50e..d71448a 100644 --- a/stellar/contracts/wormhole-executors/src/lib.rs +++ b/stellar/contracts/wormhole-executors/src/lib.rs @@ -17,8 +17,8 @@ //! the `dst_chain` argument, and the quote has not expired relative to the //! current ledger timestamp. //! 3. Requires the `payer`'s authorization, then transfers `amount` of the -//! native token (XLM, via the Stellar Asset Contract at -//! `NATIVE_TOKEN_ADDRESS`) from `payer` to `signed_quote.payee`. +//! native token (via the Stellar Asset Contract configured at construction) +//! from `payer` to `signed_quote.payee`. //! 4. Emits a [`RequestForExecution`] event that off-chain relayers consume to //! fulfil the delivery on the destination chain. //! @@ -45,12 +45,10 @@ #![no_std] +use executor_soroban_client::{ExecutorError, ExecutorInterface, SignedQuote}; use soroban_sdk::{ Address, Bytes, BytesN, Env, String, contract, contractevent, contractimpl, contracttype, token, }; -use executor_soroban_client::{ - ExecutorError, ExecutorInterface, NATIVE_TOKEN_ADDRESS, SignedQuote, -}; #[cfg(test)] mod tests; @@ -66,6 +64,10 @@ pub enum DataKey { /// [`ExecutorInterface::request_execution`] to validate /// [`SignedQuote::src_chain`]. ChainId, + /// Native-token Stellar Asset Contract address, set once by + /// [`Executor::__constructor`] and used to transfer payment from `payer` + /// to `signed_quote.payee`. + NativeToken, } /// Event emitted when a payer successfully requests a cross-chain delivery. @@ -108,10 +110,6 @@ pub struct RequestForExecution { pub relay_instructions: Bytes, } -fn get_native_token_address(env: &Env) -> Address { - Address::from_string(&String::from_str(env, NATIVE_TOKEN_ADDRESS)) -} - /// Wormhole Executor contract for Stellar/Soroban. /// /// Implements [`ExecutorInterface`]. See the crate-level documentation for @@ -125,16 +123,21 @@ pub struct Executor; impl Executor { /// Constructor called atomically during contract deployment. /// - /// Stores the Wormhole `chain_id` of this deployment in instance - /// storage. The value is read on every call to + /// Stores the Wormhole `chain_id` and native-token address of this + /// deployment in instance storage. `chain_id` is read on every call to /// [`ExecutorInterface::request_execution`] to validate - /// [`SignedQuote::src_chain`]. + /// [`SignedQuote::src_chain`]; `native_token` is the Stellar Asset + /// Contract used to transfer payment. /// /// # Arguments /// /// * `chain_id` - Wormhole chain id this Executor instance runs on. - pub fn __constructor(env: Env, chain_id: u32) { + /// * `native_token` - Stellar Asset Contract address for the native token. + pub fn __constructor(env: Env, chain_id: u32, native_token: Address) { env.storage().instance().set(&DataKey::ChainId, &chain_id); + env.storage() + .instance() + .set(&DataKey::NativeToken, &native_token); } } @@ -189,7 +192,7 @@ impl ExecutorInterface for Executor { relay_instructions, }; - let native_token = get_native_token_address(&env); + let native_token: Address = env.storage().instance().get(&DataKey::NativeToken).unwrap(); let token_client = token::TokenClient::new(&env, &native_token); token_client.transfer(&payer, &event.signed_quote.payee, &amount); event.publish(&env); diff --git a/stellar/contracts/wormhole-executors/src/tests.rs b/stellar/contracts/wormhole-executors/src/tests.rs index 635ae11..84fe200 100644 --- a/stellar/contracts/wormhole-executors/src/tests.rs +++ b/stellar/contracts/wormhole-executors/src/tests.rs @@ -42,13 +42,17 @@ impl MockNativeToken { } fn install_native_token_mock(env: &Env) -> MockNativeTokenClient<'_> { - let native = Address::from_string(&String::from_str(env, NATIVE_TOKEN_ADDRESS)); + let native = Address::generate(env); env.register_at(&native, MockNativeToken, ()); MockNativeTokenClient::new(env, &native) } -fn register_executor(env: &Env, chain_id: u32) -> ExecutorClient<'_> { - let exec_addr = env.register(Executor, (&chain_id,)); +fn register_executor<'a>( + env: &'a Env, + chain_id: u32, + native_token: &Address, +) -> ExecutorClient<'a> { + let exec_addr = env.register(Executor, (&chain_id, native_token)); ExecutorClient::new(env, &exec_addr) } @@ -68,7 +72,7 @@ fn init_roundtrip_and_version() { let env = Env::default(); env.mock_all_auths(); - let client = register_executor(&env, 1234); + let client = register_executor(&env, 1234, &Address::generate(&env)); assert_eq!(client.chain_id(), 1234); assert_eq!( @@ -93,7 +97,7 @@ fn request_happy_path_pays_quote_payee_and_emits_event() { native.set_balance(&payer, &1_000); - let exec_addr = env.register(Executor, (&(src_chain as u32),)); + let exec_addr = env.register(Executor, (&(src_chain as u32), &native.address)); let client = ExecutorClient::new(&env, &exec_addr); let signed_quote = mk_quote( &env, @@ -147,7 +151,7 @@ fn request_accepts_empty_request_like_the_solidity_reference() { native.set_balance(&payer, &99); - let client = register_executor(&env, src_chain as u32); + let client = register_executor(&env, src_chain as u32, &native.address); let signed_quote = mk_quote( &env, payee.clone(), @@ -177,7 +181,7 @@ fn request_rejects_expired_quote() { let env = Env::default(); env.mock_all_auths(); env.ledger().with_mut(|li| li.timestamp = 1000); - install_native_token_mock(&env); + let native = install_native_token_mock(&env); let src_chain = 111u16; let dst_chain = 222u16; @@ -185,7 +189,7 @@ fn request_rejects_expired_quote() { let refund = Address::generate(&env); let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[3u8; 32]); - let client = register_executor(&env, src_chain as u32); + let client = register_executor(&env, src_chain as u32, &native.address); let signed_quote = mk_quote(&env, Address::generate(&env), src_chain, dst_chain, 1000); client.request_execution( @@ -205,7 +209,7 @@ fn request_rejects_expired_quote() { fn request_rejects_source_chain_mismatch() { let env = Env::default(); env.mock_all_auths(); - install_native_token_mock(&env); + let native = install_native_token_mock(&env); let src_chain = 77u16; let dst_chain = 88u16; @@ -213,7 +217,7 @@ fn request_rejects_source_chain_mismatch() { let refund = Address::generate(&env); let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[4u8; 32]); - let client = register_executor(&env, 9999); + let client = register_executor(&env, 9999, &native.address); let signed_quote = mk_quote( &env, Address::generate(&env), @@ -239,7 +243,7 @@ fn request_rejects_source_chain_mismatch() { fn request_rejects_dst_chain_mismatch() { let env = Env::default(); env.mock_all_auths(); - install_native_token_mock(&env); + let native = install_native_token_mock(&env); let src_chain = 55u16; let dst_chain = 66u16; @@ -247,7 +251,7 @@ fn request_rejects_dst_chain_mismatch() { let refund = Address::generate(&env); let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[5u8; 32]); - let client = register_executor(&env, src_chain as u32); + let client = register_executor(&env, src_chain as u32, &native.address); let signed_quote = mk_quote( &env, Address::generate(&env), @@ -273,7 +277,7 @@ fn request_rejects_dst_chain_mismatch() { fn request_rejects_negative_amount() { let env = Env::default(); env.mock_all_auths(); - install_native_token_mock(&env); + let native = install_native_token_mock(&env); let src_chain = 20u16; let dst_chain = 30u16; @@ -281,7 +285,7 @@ fn request_rejects_negative_amount() { let refund = Address::generate(&env); let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[9u8; 32]); - let client = register_executor(&env, src_chain as u32); + let client = register_executor(&env, src_chain as u32, &native.address); let signed_quote = mk_quote( &env, Address::generate(&env), From f7b8b900afea4c1e649d3add77d97760328efdd5 Mon Sep 17 00:00:00 2001 From: Goutham Date: Thu, 9 Jul 2026 18:25:39 +0530 Subject: [PATCH 2/3] feat(executor)!: accept signed quote as opaque bytes --- stellar/Cargo.lock | 267 ++++++++++-------- stellar/Cargo.toml | 2 +- .../executor-soroban-client/src/error.rs | 10 +- .../executor-soroban-client/src/lib.rs | 37 +-- .../executor-soroban-client/src/types.rs | 36 --- .../contracts/wormhole-executors/src/lib.rs | 176 ++++++------ .../contracts/wormhole-executors/src/tests.rs | 157 ++++------ stellar/rust-toolchain.toml | 2 +- 8 files changed, 331 insertions(+), 356 deletions(-) delete mode 100644 stellar/contracts/executor-soroban-client/src/types.rs diff --git a/stellar/Cargo.lock b/stellar/Cargo.lock index 54a50e2..bfe899d 100644 --- a/stellar/Cargo.lock +++ b/stellar/Cargo.lock @@ -14,6 +14,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -34,9 +40,9 @@ dependencies = [ [[package]] name = "ark-bls12-381" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" dependencies = [ "ark-ec", "ark-ff", @@ -46,9 +52,9 @@ dependencies = [ [[package]] name = "ark-bn254" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" dependencies = [ "ark-ec", "ark-ff", @@ -57,110 +63,123 @@ dependencies = [ [[package]] name = "ark-ec" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ + "ahash", "ark-ff", "ark-poly", "ark-serialize", "ark-std", - "derivative", - "hashbrown 0.13.2", + "educe", + "fnv", + "hashbrown 0.15.5", "itertools", + "num-bigint", + "num-integer", "num-traits", "zeroize", ] [[package]] name = "ark-ff" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" dependencies = [ "ark-ff-asm", "ark-ff-macros", "ark-serialize", "ark-std", - "derivative", + "arrayvec", "digest", + "educe", "itertools", "num-bigint", "num-traits", "paste", - "rustc_version", "zeroize", ] [[package]] name = "ark-ff-asm" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "ark-ff-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" dependencies = [ "num-bigint", "num-traits", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "ark-poly" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ + "ahash", "ark-ff", "ark-serialize", "ark-std", - "derivative", - "hashbrown 0.13.2", + "educe", + "fnv", + "hashbrown 0.15.5", ] [[package]] name = "ark-serialize" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ "ark-serialize-derive", "ark-std", + "arrayvec", "digest", "num-bigint", ] [[package]] name = "ark-serialize-derive" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "ark-std" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", "rand", ] +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "autocfg" version = "1.5.0" @@ -208,14 +227,14 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes-lit" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" +checksum = "9b04f2b1d34cb428043f14aa4c853d14294532e8bbde3b6a3bc2faaaae31a1dd" dependencies = [ "num-bigint", "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -242,7 +261,7 @@ checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -289,6 +308,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "crate-git-revision" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54851b5b3f24621804b1cded2820975623c205e3055d2d44031cdb1237339ac8" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -351,7 +381,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -385,7 +415,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn", ] [[package]] @@ -399,7 +429,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn", ] [[package]] @@ -410,7 +440,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -421,7 +451,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -450,17 +480,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -469,7 +488,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -549,6 +568,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.15.0" @@ -573,6 +604,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "enum-ordinalize" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -587,9 +638,9 @@ checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" [[package]] name = "ethnum" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" [[package]] name = "executor-soroban-client" @@ -678,11 +729,11 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "ahash", + "allocator-api2", ] [[package]] @@ -792,9 +843,9 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -862,7 +913,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -895,7 +946,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -972,7 +1023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn", ] [[package]] @@ -1049,7 +1100,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1158,7 +1209,7 @@ checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1204,7 +1255,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1252,24 +1303,24 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "soroban-builtin-sdk-macros" -version = "25.0.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7192e3a5551a7aeee90d2110b11b615798e81951fd8c8293c87ea7f88b0168f5" +checksum = "d769030e3b2c27873e9be4931decbbe79787b943d5b30e31f8c395eae83144b8" dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] name = "soroban-env-common" -version = "25.0.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc49a80a68fc1005847308e63b9fce39874de731940b1807b721d472de3ff01" +checksum = "5aa50d998c0baafcc6078cb94f5228040c7719b1608c15debc3fc78365dc22f5" dependencies = [ "arbitrary", - "crate-git-revision", + "crate-git-revision 0.0.6", "ethnum", "num-derive", "num-traits", @@ -1283,9 +1334,9 @@ dependencies = [ [[package]] name = "soroban-env-guest" -version = "25.0.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2334ba1cfe0a170ab744d96db0b4ca86934de9ff68187ceebc09dc342def55" +checksum = "fde1064f5e92dbcc8018ecc8e92728bf5bbff0236abaf792c6858367a6853415" dependencies = [ "soroban-env-common", "static_assertions", @@ -1293,9 +1344,9 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "25.0.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43af5d53c57bc2f546e122adc0b1cca6f93942c718977379aa19ddd04f06fcec" +checksum = "f47762a1b75b9ccd07558f28e1a0289ca6adec56810c581cde5cf16e329e00b9" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -1330,9 +1381,9 @@ dependencies = [ [[package]] name = "soroban-env-macros" -version = "25.0.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a989167512e3592d455b1e204d703cfe578a36672a77ed2f9e6f7e1bbfd9cc5c" +checksum = "bc04b813a9e32456b37875fc41cdb05912a9e947000b9414b7985bffe07a889a" dependencies = [ "itertools", "proc-macro2", @@ -1340,14 +1391,14 @@ dependencies = [ "serde", "serde_json", "stellar-xdr", - "syn 2.0.106", + "syn", ] [[package]] name = "soroban-ledger-snapshot" -version = "25.1.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d569a1315f05216d024653ad87541aa15d3ff26dad9f8a98719cb53ccf2bf3" +checksum = "1e4bf41f85da648dca4153ca5ed41411658d2029e15d9ba693fdd2e3e5dbe05d" dependencies = [ "serde", "serde_json", @@ -1359,13 +1410,13 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "25.1.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add8d19cfd2c9941bbdc7c8223c3cf9d7ff9af4554ba3bd4ae93e16b19b08aea" +checksum = "a5636f9d62dd0cc6d23f08f775aa60f712ec596e5761f6911aee154337d401bf" dependencies = [ "arbitrary", "bytes-lit", - "crate-git-revision", + "crate-git-revision 0.0.9", "ctor", "derive_arbitrary", "ed25519-dalek", @@ -1383,9 +1434,9 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "25.1.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0107e34575ec704ce29407695462e79e6b0e13ce7af6431b2f15c313e34464" +checksum = "f7c9f001d91196d9cd659634f9113f75609819204aef5fae27b4ab845cdfc741" dependencies = [ "darling 0.20.11", "heck", @@ -1398,16 +1449,17 @@ dependencies = [ "soroban-spec", "soroban-spec-rust", "stellar-xdr", - "syn 2.0.106", + "syn", ] [[package]] name = "soroban-spec" -version = "25.1.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a1c9f6ccc6aa78518545e3cf542bd26f11d9085328a2e1c06c90514733fe15" +checksum = "ca1e22ce713871c6f9d21a321051b58e53080629b6a5be1132cf11f42cca4d10" dependencies = [ "base64", + "sha2", "stellar-xdr", "thiserror", "wasmparser", @@ -1415,9 +1467,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "25.1.1" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8247d3c6256b544b2461606c6892351bb22978d751e07c1aea744377053d852" +checksum = "89fb158f79ec527e46a5d5162f11c4269e17748e6b69391cdf648133d76cfead" dependencies = [ "prettyplease", "proc-macro2", @@ -1425,7 +1477,7 @@ dependencies = [ "sha2", "soroban-spec", "stellar-xdr", - "syn 2.0.106", + "syn", "thiserror", ] @@ -1476,7 +1528,7 @@ version = "0.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee1832fb50c651ad10f734aaf5d31ca5acdfb197a6ecda64d93fcdb8885af913" dependencies = [ - "crate-git-revision", + "crate-git-revision 0.0.6", "data-encoding", ] @@ -1486,21 +1538,21 @@ version = "0.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "084afcb0d458c3d5d5baa2d294b18f881e62cc258ef539d8fdf68be7dbe45520" dependencies = [ - "crate-git-revision", + "crate-git-revision 0.0.6", "data-encoding", "heapless", ] [[package]] name = "stellar-xdr" -version = "25.0.0" +version = "27.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d20dafed80076b227d4b17c0c508a4bbc4d5e4c3d4c1de7cd42242df4b1eaf" +checksum = "05ff843326969bdf1ef673dcdba94c08f4a3c8f1e58d6e6ef39b1bd4f749179a" dependencies = [ "arbitrary", "base64", "cfg_eval", - "crate-git-revision", + "crate-git-revision 0.0.6", "escape-bytes", "ethnum", "hex", @@ -1522,17 +1574,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.106" @@ -1561,7 +1602,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1621,7 +1662,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1653,7 +1694,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn", "wasm-bindgen-shared", ] @@ -1675,7 +1716,7 @@ checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1747,7 +1788,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1758,7 +1799,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1810,7 +1851,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] @@ -1830,7 +1871,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn", ] [[package]] diff --git a/stellar/Cargo.toml b/stellar/Cargo.toml index b535575..0ef2e4f 100644 --- a/stellar/Cargo.toml +++ b/stellar/Cargo.toml @@ -9,7 +9,7 @@ members = [ edition = "2024" [workspace.dependencies] -soroban-sdk = "25.1.1" +soroban-sdk = "27.0.0" executor-soroban-client = { path = "contracts/executor-soroban-client" } [profile.release] diff --git a/stellar/contracts/executor-soroban-client/src/error.rs b/stellar/contracts/executor-soroban-client/src/error.rs index aad202a..0bb5d66 100644 --- a/stellar/contracts/executor-soroban-client/src/error.rs +++ b/stellar/contracts/executor-soroban-client/src/error.rs @@ -9,15 +9,17 @@ use soroban_sdk::contracterror; #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[repr(u32)] pub enum ExecutorError { - /// The `SignedQuote::expiry` is less than or equal to the current ledger - /// timestamp at the time of the call. + /// The quote header's `expiryTime` is less than or equal to the current + /// ledger timestamp at the time of the call. QuoteExpired = 11, - /// The `SignedQuote::src_chain` does not match the Wormhole chain id + /// The quote header's `srcChain` does not match the Wormhole chain id /// configured at construction. QuoteSrcChainMismatch = 12, - /// The `SignedQuote::dst_chain` does not match the `dst_chain` argument + /// The quote header's `dstChain` does not match the `dst_chain` argument /// passed to `request_execution`. QuoteDstChainMismatch = 13, /// The `amount` argument passed to `request_execution` is negative. InvalidAmount = 14, + /// The signed quote is shorter than the 68-byte header. + InvalidQuote = 15, } diff --git a/stellar/contracts/executor-soroban-client/src/lib.rs b/stellar/contracts/executor-soroban-client/src/lib.rs index 81899be..736dfec 100644 --- a/stellar/contracts/executor-soroban-client/src/lib.rs +++ b/stellar/contracts/executor-soroban-client/src/lib.rs @@ -6,30 +6,32 @@ #![no_std] pub mod error; -pub mod types; pub use error::ExecutorError; -pub use types::*; use soroban_sdk::{Address, Bytes, BytesN, Env, String, contractclient}; /// Public interface for the Wormhole Executor contract. /// -/// The Executor is a prepaid cross-chain delivery payment rail. A `payer` -/// submits a [`SignedQuote`] alongside a delivery request, the contract -/// validates the quote, transfers the agreed `amount` of native token from -/// the payer to the quote's `payee`, and emits an event consumed by off-chain +/// The Executor is a stateless, permissionless cross-chain delivery payment +/// rail. A `payer` submits an off-chain-signed quote (as opaque bytes) +/// alongside a delivery request, the contract validates the quote header, +/// transfers the agreed `amount` of native token from the payer to the +/// `payee`, and emits an event carrying the full quote verbatim for off-chain /// relayers that fulfill the delivery on the destination chain. /// -/// # Quote authentication is NOT performed on-chain +/// # Quote authentication and payee binding are OFF-CHAIN /// -/// Despite its name, [`SignedQuote`] is not verified by the Executor. Neither -/// the [`SignedQuote::prefix`] domain tag nor the `quoter`'s signature over -/// the quote are checked on chain. Quote authentication is a caller-side -/// responsibility, performed off-chain before the transaction is submitted. +/// The contract parses only the 68-byte quote header (chain ids and expiry). +/// It does not verify the quote's signature, nor does it bind `payee` to the +/// payee encoded in the quote header — both are the relayer's off-chain +/// responsibility, which the verbatim-emitted quote enables. #[contractclient(name = "ExecutorClient")] pub trait ExecutorInterface { /// Returns the Wormhole chain id configured at construction. + /// + /// Wormhole chain ids are 16-bit; `u32` is used here only because Soroban's + /// ABI has no 16-bit value type. The value is always in `0..=u16::MAX`. fn chain_id(env: Env) -> u32; /// Returns the version string of the Executor implementation. @@ -37,19 +39,20 @@ pub trait ExecutorInterface { /// Records a prepaid cross-chain delivery request. /// - /// Validates the [`SignedQuote`], requires the payer's authorization, - /// transfers `amount` native tokens from `payer` to - /// `signed_quote.payee`, and emits a `RequestForExecution` event consumed - /// by off-chain relayers. + /// Parses the quote header from `signed_quote_bytes`, requires the payer's + /// authorization, transfers `amount` native tokens from `payer` to + /// `payee`, and emits a `RequestForExecution` event carrying the full + /// quote bytes for off-chain relayers. #[allow(clippy::too_many_arguments)] fn request_execution( env: Env, dst_chain: u32, - dst_addr_wa32: BytesN<32>, + dst_addr: BytesN<32>, refund: Address, payer: Address, + payee: Address, amount: i128, - signed_quote: SignedQuote, + signed_quote_bytes: Bytes, request: Bytes, relay_instructions: Bytes, ) -> Result<(), ExecutorError>; diff --git a/stellar/contracts/executor-soroban-client/src/types.rs b/stellar/contracts/executor-soroban-client/src/types.rs deleted file mode 100644 index a1018f6..0000000 --- a/stellar/contracts/executor-soroban-client/src/types.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Public type definitions for the Wormhole Executor contract. - -use soroban_sdk::{Address, BytesN, contracttype}; - -/// Off-chain quote submitted alongside a delivery request to the Wormhole -/// Executor contract. -/// -/// A `SignedQuote` binds a `quoter` and a `payee` to a (`src_chain`, -/// `dst_chain`, `expiry`) tuple so the payer knows who will relay the request, -/// who must be paid, and until when the quote is valid. -/// -/// # Authentication is NOT performed on-chain -/// -/// Neither the [`prefix`](Self::prefix) domain tag nor the `quoter`'s -/// signature over the quote are verified by the Executor contract. The -/// on-chain Executor treats the struct as untrusted data: only `src_chain`, -/// `dst_chain`, and `expiry` are checked, and the payer's authorization is -/// required for the token transfer. -#[contracttype] -#[derive(Clone, Debug, PartialEq)] -pub struct SignedQuote { - /// 4-byte domain separator from the off-chain signing scheme, such as - /// `b"EQ01"`. - pub prefix: BytesN<4>, - /// Address of the party that issued and off-chain-signed this quote. - pub quoter: Address, - /// Address credited with the native token transfer when - /// `request_execution` succeeds. - pub payee: Address, - /// Wormhole chain id the quote was issued for as the source chain. - pub src_chain: u32, - /// Wormhole chain id of the intended destination chain. - pub dst_chain: u32, - /// Unix timestamp in seconds after which the quote is no longer valid. - pub expiry: u64, -} diff --git a/stellar/contracts/wormhole-executors/src/lib.rs b/stellar/contracts/wormhole-executors/src/lib.rs index d71448a..357a883 100644 --- a/stellar/contracts/wormhole-executors/src/lib.rs +++ b/stellar/contracts/wormhole-executors/src/lib.rs @@ -1,51 +1,45 @@ //! Wormhole Executor contract implementation for Stellar/Soroban. //! -//! The Executor is a simple on-chain payment rail: a `payer` prepays a relayer -//! (identified by the `quoter`/`payee` pair inside an off-chain-signed quote) -//! to execute a cross-chain delivery request on a destination chain. The -//! actual relaying happens off-chain; this contract only records the payment -//! and emits the event that off-chain relayers consume. +//! The Executor is a stateless, permissionless payment rail: a `payer` prepays +//! a relayer to execute a cross-chain delivery request on a destination chain. +//! The relaying happens off-chain; this contract only records the payment and +//! emits the event that off-chain relayers consume. //! //! # On-chain responsibilities //! -//! The contract deliberately does very little. On a call to -//! [`ExecutorInterface::request_execution`] it: +//! On a call to [`ExecutorInterface::request_execution`] it: //! -//! 1. Accepts a [`SignedQuote`] and a delivery request from the payer. -//! 2. Validates basic sanity: `amount >= 0`, the quote's `src_chain` matches -//! the chain id configured at construction, the quote's `dst_chain` matches -//! the `dst_chain` argument, and the quote has not expired relative to the -//! current ledger timestamp. +//! 1. Accepts the signed quote as opaque bytes plus a delivery request from the +//! payer, and parses only the 68-byte quote header by byte offset. +//! 2. Validates basic sanity: `amount >= 0`, the quote is at least a full +//! header, the header's `srcChain` matches the chain id configured at +//! construction, the header's `dstChain` matches the `dst_chain` argument, +//! and the quote has not expired relative to the current ledger timestamp. //! 3. Requires the `payer`'s authorization, then transfers `amount` of the //! native token (via the Stellar Asset Contract configured at construction) -//! from `payer` to `signed_quote.payee`. -//! 4. Emits a [`RequestForExecution`] event that off-chain relayers consume to -//! fulfil the delivery on the destination chain. +//! from `payer` to `payee`. +//! 4. Emits a [`RequestForExecution`] event carrying the full quote verbatim so +//! off-chain relayers can verify it and fulfil the delivery. //! -//! # Quote authentication is NOT performed on-chain +//! # Quote authentication and payee binding are OFF-CHAIN //! -//! Despite its name, [`SignedQuote`] is **not** verified by this contract. -//! Neither the [`SignedQuote::prefix`] domain tag (e.g. `EQ01`) nor the -//! `quoter`'s cryptographic signature over the quote are checked here. This -//! is intentional and matches the reference EVM implementation: quote -//! authentication is a caller-side responsibility, performed off-chain by -//! the relayer SDK before the transaction is submitted. The on-chain -//! contract only enforces chain-id, expiry and amount checks plus the -//! `payer`'s auth for the token transfer. +//! The contract does not verify the quote's signature, nor does it bind the +//! `payee` argument to the 32-byte payee inside the quote header: a Soroban +//! `Address` does not expose its raw bytes in deployed wasm. Both the signature +//! and the `payee`/`quote[24..56]` binding are the relayer's off-chain +//! responsibility, which the verbatim-emitted quote enables. //! //! # Architecture //! //! - [`Executor`] - Main contract struct implementing [`ExecutorInterface`] //! - [`ExecutorInterface`] - Public interface (re-exported from //! `executor-soroban-client`) -//! - [`SignedQuote`] - Off-chain quote payload, NOT verified on-chain -//! (re-exported from `executor-soroban-client`) //! - [`RequestForExecution`] - Event emitted on successful requests //! - [`ExecutorError`] - Error codes returned by `request_execution` #![no_std] -use executor_soroban_client::{ExecutorError, ExecutorInterface, SignedQuote}; +use executor_soroban_client::{ExecutorError, ExecutorInterface}; use soroban_sdk::{ Address, Bytes, BytesN, Env, String, contract, contractevent, contractimpl, contracttype, token, }; @@ -55,67 +49,80 @@ mod tests; const EXECUTOR_VERSION: &str = "Executor-0.0.1"; +/// Minimum quote length: the header that is parsed on-chain. Everything past +/// it (EQ01 body and signature) is opaque and emitted verbatim. +const QUOTE_HEADER_LEN: u32 = 68; + /// Instance storage keys for the [`Executor`] contract. #[contracttype] #[derive(Clone)] pub enum DataKey { /// Wormhole chain id of this deployment, set once by /// [`Executor::__constructor`] and read on every call to - /// [`ExecutorInterface::request_execution`] to validate - /// [`SignedQuote::src_chain`]. + /// [`ExecutorInterface::request_execution`] to validate the quote header's + /// `srcChain`. ChainId, /// Native-token Stellar Asset Contract address, set once by /// [`Executor::__constructor`] and used to transfer payment from `payer` - /// to `signed_quote.payee`. + /// to `payee`. NativeToken, } /// Event emitted when a payer successfully requests a cross-chain delivery. /// /// Published by [`ExecutorInterface::request_execution`] after the native -/// token transfer has completed. Off-chain relayers subscribe to this event -/// to learn that they have been paid and must fulfil the associated delivery -/// on the destination chain. +/// token transfer has completed. Off-chain relayers subscribe to this event to +/// learn that they have been paid and must fulfil the associated delivery on +/// the destination chain. /// /// The event is published with topics `["Executor", "RequestForExecution"]`. #[contractevent(topics = ["Executor", "RequestForExecution"])] #[derive(Clone)] pub struct RequestForExecution { - /// Address of the quoter that authored the associated [`SignedQuote`]. - /// Mirrors `signed_quote.quoter` for off-chain indexers that only - /// inspect top-level event fields. - pub quoter: Address, - /// Amount of native token (stroops) paid by the payer to - /// `signed_quote.payee` for this request. + /// 20-byte EVM identity of the quoter, read from the quote header. + pub quoter_address: BytesN<20>, + /// Amount of native token (stroops) paid by the payer to `payee`. pub amt_paid: i128, - /// Wormhole chain id of the destination chain, as passed to - /// [`ExecutorInterface::request_execution`]. + /// Wormhole chain id of the destination chain (16-bit value in a `u32`; + /// Soroban's ABI has no 16-bit type). pub dst_chain: u32, - /// 32-byte destination address on the destination chain, in Wormhole's - /// left-zero-padded 32-byte encoding. Pass-through; not validated on - /// chain. - pub dst_addr_wa32: BytesN<32>, - /// Address that the off-chain relayer should refund if the delivery - /// cannot be completed. Pass-through metadata; this contract never - /// reads it. - pub refund: Address, - /// The full [`SignedQuote`] submitted by the payer, recorded verbatim - /// in the event for off-chain consumption. - pub signed_quote: SignedQuote, - /// Opaque delivery request payload whose format is defined by the - /// off-chain relayer protocol. Not interpreted on chain. + /// 32-byte destination address in Wormhole's left-zero-padded encoding. + pub dst_addr: BytesN<32>, + /// Address the off-chain relayer should refund on failure. Pass-through. + pub refund_addr: Address, + /// The full signed quote submitted by the payer (header, body, and + /// signature), recorded verbatim for off-chain verification. + pub signed_quote: Bytes, + /// Opaque delivery request payload, defined by the off-chain protocol. pub request: Bytes, - /// Opaque relaying instructions whose format is defined by the - /// off-chain relayer protocol. Not interpreted on chain. + /// Opaque relaying instructions, defined by the off-chain protocol. pub relay_instructions: Bytes, } +fn read_u16_be(b: &Bytes, at: u32) -> u16 { + let mut buf = [0u8; 2]; + b.slice(at..at + 2).copy_into_slice(&mut buf); + u16::from_be_bytes(buf) +} + +fn read_u64_be(b: &Bytes, at: u32) -> u64 { + let mut buf = [0u8; 8]; + b.slice(at..at + 8).copy_into_slice(&mut buf); + u64::from_be_bytes(buf) +} + +fn read_quoter(env: &Env, b: &Bytes) -> BytesN<20> { + let mut buf = [0u8; 20]; + b.slice(4..24).copy_into_slice(&mut buf); + BytesN::from_array(env, &buf) +} + /// Wormhole Executor contract for Stellar/Soroban. /// -/// Implements [`ExecutorInterface`]. See the crate-level documentation for -/// the Executor's role as a prepaid cross-chain delivery payment rail and -/// for the important note that quote authentication is **not** performed on -/// chain. +/// Implements [`ExecutorInterface`]. See the crate-level documentation for the +/// Executor's role as a prepaid cross-chain delivery payment rail and for the +/// important note that quote authentication and payee binding are **not** +/// performed on chain. #[contract] pub struct Executor; @@ -125,13 +132,14 @@ impl Executor { /// /// Stores the Wormhole `chain_id` and native-token address of this /// deployment in instance storage. `chain_id` is read on every call to - /// [`ExecutorInterface::request_execution`] to validate - /// [`SignedQuote::src_chain`]; `native_token` is the Stellar Asset - /// Contract used to transfer payment. + /// [`ExecutorInterface::request_execution`] to validate the quote header's + /// `srcChain`; `native_token` is the Stellar Asset Contract used to + /// transfer payment. /// /// # Arguments /// - /// * `chain_id` - Wormhole chain id this Executor instance runs on. + /// * `chain_id` - Wormhole chain id this Executor instance runs on (16-bit + /// value passed as `u32`; Soroban's ABI has no 16-bit type). /// * `native_token` - Stellar Asset Contract address for the native token. pub fn __constructor(env: Env, chain_id: u32, native_token: Address) { env.storage().instance().set(&DataKey::ChainId, &chain_id); @@ -155,47 +163,49 @@ impl ExecutorInterface for Executor { fn request_execution( env: Env, dst_chain: u32, - dst_addr_wa32: BytesN<32>, + dst_addr: BytesN<32>, refund: Address, payer: Address, + payee: Address, amount: i128, - signed_quote: SignedQuote, + signed_quote_bytes: Bytes, request: Bytes, relay_instructions: Bytes, ) -> Result<(), ExecutorError> { if amount < 0 { return Err(ExecutorError::InvalidAmount); } - - let this_chain = Self::chain_id(env.clone()); - - if signed_quote.src_chain != this_chain { + if signed_quote_bytes.len() < QUOTE_HEADER_LEN { + return Err(ExecutorError::InvalidQuote); + } + // Widen the 16-bit wire fields to u32 for comparison; never truncate + // the u32 args down to u16, which would alias out-of-range ids. + if u32::from(read_u16_be(&signed_quote_bytes, 56)) != Self::chain_id(env.clone()) { return Err(ExecutorError::QuoteSrcChainMismatch); } - if signed_quote.dst_chain != dst_chain { + if u32::from(read_u16_be(&signed_quote_bytes, 58)) != dst_chain { return Err(ExecutorError::QuoteDstChainMismatch); } - if signed_quote.expiry <= env.ledger().timestamp() { + if read_u64_be(&signed_quote_bytes, 60) <= env.ledger().timestamp() { return Err(ExecutorError::QuoteExpired); } payer.require_auth(); - let event = RequestForExecution { - quoter: signed_quote.quoter.clone(), + let native_token: Address = env.storage().instance().get(&DataKey::NativeToken).unwrap(); + token::TokenClient::new(&env, &native_token).transfer(&payer, &payee, &amount); + + RequestForExecution { + quoter_address: read_quoter(&env, &signed_quote_bytes), amt_paid: amount, dst_chain, - dst_addr_wa32, - refund, - signed_quote, + dst_addr, + refund_addr: refund, + signed_quote: signed_quote_bytes, request, relay_instructions, - }; - - let native_token: Address = env.storage().instance().get(&DataKey::NativeToken).unwrap(); - let token_client = token::TokenClient::new(&env, &native_token); - token_client.transfer(&payer, &event.signed_quote.payee, &amount); - event.publish(&env); + } + .publish(&env); Ok(()) } diff --git a/stellar/contracts/wormhole-executors/src/tests.rs b/stellar/contracts/wormhole-executors/src/tests.rs index 84fe200..498339b 100644 --- a/stellar/contracts/wormhole-executors/src/tests.rs +++ b/stellar/contracts/wormhole-executors/src/tests.rs @@ -4,8 +4,6 @@ use soroban_sdk::{ testutils::{Address as _, Events, Ledger}, }; -const SIGNED_QUOTE_PREFIX: [u8; 4] = *b"EQ01"; - #[contracttype] #[derive(Clone)] enum MockTokenStorage { @@ -49,22 +47,21 @@ fn install_native_token_mock(env: &Env) -> MockNativeTokenClient<'_> { fn register_executor<'a>( env: &'a Env, - chain_id: u32, + chain_id: u16, native_token: &Address, ) -> ExecutorClient<'a> { - let exec_addr = env.register(Executor, (&chain_id, native_token)); + let exec_addr = env.register(Executor, (&u32::from(chain_id), native_token)); ExecutorClient::new(env, &exec_addr) } -fn mk_quote(env: &Env, payee: Address, src_chain: u16, dst_chain: u16, expiry: u64) -> SignedQuote { - SignedQuote { - prefix: BytesN::from_array(env, &SIGNED_QUOTE_PREFIX), - quoter: Address::generate(env), - payee, - src_chain: u32::from(src_chain), - dst_chain: u32::from(dst_chain), - expiry, - } +/// Builds a 68-byte quote header (quoter and payee left zeroed). +fn build_quote(env: &Env, src: u16, dst: u16, expiry: u64) -> Bytes { + let mut h = [0u8; 68]; + h[0..4].copy_from_slice(b"EQ01"); + h[56..58].copy_from_slice(&src.to_be_bytes()); + h[58..60].copy_from_slice(&dst.to_be_bytes()); + h[60..68].copy_from_slice(&expiry.to_be_bytes()); + Bytes::from_slice(env, &h) } #[test] @@ -82,7 +79,7 @@ fn init_roundtrip_and_version() { } #[test] -fn request_happy_path_pays_quote_payee_and_emits_event() { +fn request_happy_path_pays_payee_and_emits_event() { let env = Env::default(); env.mock_all_auths(); let native = install_native_token_mock(&env); @@ -92,28 +89,22 @@ fn request_happy_path_pays_quote_payee_and_emits_event() { let payee = Address::generate(&env); let payer = Address::generate(&env); let refund = Address::generate(&env); - let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[9u8; 32]); + let dst_addr = BytesN::<32>::from_array(&env, &[9u8; 32]); let amount = 250i128; native.set_balance(&payer, &1_000); - let exec_addr = env.register(Executor, (&(src_chain as u32), &native.address)); - let client = ExecutorClient::new(&env, &exec_addr); - let signed_quote = mk_quote( - &env, - payee.clone(), - src_chain, - dst_chain, - env.ledger().timestamp() + 600, - ); + let client = register_executor(&env, src_chain, &native.address); + let signed_quote = build_quote(&env, src_chain, dst_chain, env.ledger().timestamp() + 600); let request = Bytes::from_slice(&env, b"any-request-bytes"); let relay_instructions = Bytes::from_slice(&env, &[0xCA, 0xFE]); client.request_execution( - &(dst_chain as u32), - &dst_addr_wa32, + &u32::from(dst_chain), + &dst_addr, &refund, &payer, + &payee, &amount, &signed_quote, &request, @@ -121,23 +112,23 @@ fn request_happy_path_pays_quote_payee_and_emits_event() { ); let expected = RequestForExecution { - quoter: signed_quote.quoter.clone(), + quoter_address: BytesN::from_array(&env, &[0u8; 20]), amt_paid: amount, - dst_chain: dst_chain as u32, - dst_addr_wa32: dst_addr_wa32.clone(), - refund: refund.clone(), + dst_chain: u32::from(dst_chain), + dst_addr: dst_addr.clone(), + refund_addr: refund.clone(), signed_quote: signed_quote.clone(), request: request.clone(), relay_instructions: relay_instructions.clone(), }; - assert_eq!(env.events().all(), [expected.to_xdr(&env, &exec_addr)]); + assert_eq!(env.events().all(), [expected.to_xdr(&env, &client.address)]); assert_eq!(native.balance(&payer), 750); assert_eq!(native.balance(&payee), amount); } #[test] -fn request_accepts_empty_request_like_the_solidity_reference() { +fn request_accepts_empty_request_and_relay_instructions() { let env = Env::default(); env.mock_all_auths(); let native = install_native_token_mock(&env); @@ -147,24 +138,19 @@ fn request_accepts_empty_request_like_the_solidity_reference() { let payee = Address::generate(&env); let payer = Address::generate(&env); let refund = Address::generate(&env); - let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[1u8; 32]); + let dst_addr = BytesN::<32>::from_array(&env, &[1u8; 32]); native.set_balance(&payer, &99); - let client = register_executor(&env, src_chain as u32, &native.address); - let signed_quote = mk_quote( - &env, - payee.clone(), - src_chain, - dst_chain, - env.ledger().timestamp() + 60, - ); + let client = register_executor(&env, src_chain, &native.address); + let signed_quote = build_quote(&env, src_chain, dst_chain, env.ledger().timestamp() + 60); let res = client.try_request_execution( - &(dst_chain as u32), - &dst_addr_wa32, + &u32::from(dst_chain), + &dst_addr, &refund, &payer, + &payee, &42, &signed_quote, &Bytes::new(&env), @@ -185,18 +171,16 @@ fn request_rejects_expired_quote() { let src_chain = 111u16; let dst_chain = 222u16; - let payer = Address::generate(&env); - let refund = Address::generate(&env); - let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[3u8; 32]); - let client = register_executor(&env, src_chain as u32, &native.address); - let signed_quote = mk_quote(&env, Address::generate(&env), src_chain, dst_chain, 1000); + let client = register_executor(&env, src_chain, &native.address); + let signed_quote = build_quote(&env, src_chain, dst_chain, 1000); client.request_execution( - &(dst_chain as u32), - &dst_addr_wa32, - &refund, - &payer, + &u32::from(dst_chain), + &BytesN::<32>::from_array(&env, &[3u8; 32]), + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), &1, &signed_quote, &Bytes::from_slice(&env, b"request"), @@ -211,26 +195,16 @@ fn request_rejects_source_chain_mismatch() { env.mock_all_auths(); let native = install_native_token_mock(&env); - let src_chain = 77u16; let dst_chain = 88u16; - let payer = Address::generate(&env); - let refund = Address::generate(&env); - let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[4u8; 32]); - let client = register_executor(&env, 9999, &native.address); - let signed_quote = mk_quote( - &env, - Address::generate(&env), - src_chain, - dst_chain, - env.ledger().timestamp() + 600, - ); + let signed_quote = build_quote(&env, 77, dst_chain, env.ledger().timestamp() + 600); client.request_execution( - &(dst_chain as u32), - &dst_addr_wa32, - &refund, - &payer, + &u32::from(dst_chain), + &BytesN::<32>::from_array(&env, &[4u8; 32]), + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), &1, &signed_quote, &Bytes::from_slice(&env, b"request"), @@ -246,25 +220,15 @@ fn request_rejects_dst_chain_mismatch() { let native = install_native_token_mock(&env); let src_chain = 55u16; - let dst_chain = 66u16; - let payer = Address::generate(&env); - let refund = Address::generate(&env); - let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[5u8; 32]); - - let client = register_executor(&env, src_chain as u32, &native.address); - let signed_quote = mk_quote( - &env, - Address::generate(&env), - src_chain, - dst_chain, - env.ledger().timestamp() + 600, - ); + let client = register_executor(&env, src_chain, &native.address); + let signed_quote = build_quote(&env, src_chain, 66, env.ledger().timestamp() + 600); client.request_execution( - &99u32, - &dst_addr_wa32, - &refund, - &payer, + &99, + &BytesN::<32>::from_array(&env, &[5u8; 32]), + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), &1, &signed_quote, &Bytes::from_slice(&env, b"request"), @@ -281,24 +245,15 @@ fn request_rejects_negative_amount() { let src_chain = 20u16; let dst_chain = 30u16; - let payer = Address::generate(&env); - let refund = Address::generate(&env); - let dst_addr_wa32 = BytesN::<32>::from_array(&env, &[9u8; 32]); - - let client = register_executor(&env, src_chain as u32, &native.address); - let signed_quote = mk_quote( - &env, - Address::generate(&env), - src_chain, - dst_chain, - env.ledger().timestamp() + 600, - ); + let client = register_executor(&env, src_chain, &native.address); + let signed_quote = build_quote(&env, src_chain, dst_chain, env.ledger().timestamp() + 600); client.request_execution( - &(dst_chain as u32), - &dst_addr_wa32, - &refund, - &payer, + &u32::from(dst_chain), + &BytesN::<32>::from_array(&env, &[9u8; 32]), + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), &-1, &signed_quote, &Bytes::from_slice(&env, b"request"), diff --git a/stellar/rust-toolchain.toml b/stellar/rust-toolchain.toml index c041838..7ca0159 100644 --- a/stellar/rust-toolchain.toml +++ b/stellar/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.89.0" +channel = "1.92.0" components = ["rustfmt", "clippy"] targets = ["wasm32v1-none"] \ No newline at end of file From bab7ca9e5b5fb87be047d2131f691f9c1d57e111 Mon Sep 17 00:00:00 2001 From: Goutham Date: Thu, 9 Jul 2026 20:34:24 +0530 Subject: [PATCH 3/3] test(executor): cover wire-format parsing and event fidelity --- .../contracts/wormhole-executors/src/tests.rs | 234 ++++++++++++++---- 1 file changed, 185 insertions(+), 49 deletions(-) diff --git a/stellar/contracts/wormhole-executors/src/tests.rs b/stellar/contracts/wormhole-executors/src/tests.rs index 498339b..64b3fb1 100644 --- a/stellar/contracts/wormhole-executors/src/tests.rs +++ b/stellar/contracts/wormhole-executors/src/tests.rs @@ -54,20 +54,34 @@ fn register_executor<'a>( ExecutorClient::new(env, &exec_addr) } -/// Builds a 68-byte quote header (quoter and payee left zeroed). -fn build_quote(env: &Env, src: u16, dst: u16, expiry: u64) -> Bytes { - let mut h = [0u8; 68]; - h[0..4].copy_from_slice(b"EQ01"); - h[56..58].copy_from_slice(&src.to_be_bytes()); - h[58..60].copy_from_slice(&dst.to_be_bytes()); - h[60..68].copy_from_slice(&expiry.to_be_bytes()); - Bytes::from_slice(env, &h) +/// Assembles raw quote bytes: prefix(4) quoter(20) payee(32) src(2 be) +/// dst(2 be) expiry(8 be), followed by an opaque `tail` (EQ01 body + signature) +/// that the contract must preserve verbatim. +fn build_quote( + env: &Env, + quoter: &[u8; 20], + payee32: &[u8; 32], + src: u16, + dst: u16, + expiry: u64, + tail: &[u8], +) -> Bytes { + let mut header = [0u8; 68]; + header[0..4].copy_from_slice(b"EQ01"); + header[4..24].copy_from_slice(quoter); + header[24..56].copy_from_slice(payee32); + header[56..58].copy_from_slice(&src.to_be_bytes()); + header[58..60].copy_from_slice(&dst.to_be_bytes()); + header[60..68].copy_from_slice(&expiry.to_be_bytes()); + + let mut bytes = Bytes::from_slice(env, &header); + bytes.append(&Bytes::from_slice(env, tail)); + bytes } #[test] fn init_roundtrip_and_version() { let env = Env::default(); - env.mock_all_auths(); let client = register_executor(&env, 1234, &Address::generate(&env)); @@ -79,15 +93,15 @@ fn init_roundtrip_and_version() { } #[test] -fn request_happy_path_pays_payee_and_emits_event() { +fn happy_path() { let env = Env::default(); env.mock_all_auths(); let native = install_native_token_mock(&env); let src_chain = 1234u16; let dst_chain = 4321u16; - let payee = Address::generate(&env); let payer = Address::generate(&env); + let payee = Address::generate(&env); let refund = Address::generate(&env); let dst_addr = BytesN::<32>::from_array(&env, &[9u8; 32]); let amount = 250i128; @@ -95,7 +109,15 @@ fn request_happy_path_pays_payee_and_emits_event() { native.set_balance(&payer, &1_000); let client = register_executor(&env, src_chain, &native.address); - let signed_quote = build_quote(&env, src_chain, dst_chain, env.ledger().timestamp() + 600); + let quote = build_quote( + &env, + &[7u8; 20], + &[0u8; 32], + src_chain, + dst_chain, + env.ledger().timestamp() + 600, + &[], + ); let request = Bytes::from_slice(&env, b"any-request-bytes"); let relay_instructions = Bytes::from_slice(&env, &[0xCA, 0xFE]); @@ -106,20 +128,20 @@ fn request_happy_path_pays_payee_and_emits_event() { &payer, &payee, &amount, - &signed_quote, + "e, &request, &relay_instructions, ); let expected = RequestForExecution { - quoter_address: BytesN::from_array(&env, &[0u8; 20]), + quoter_address: BytesN::from_array(&env, &[7u8; 20]), amt_paid: amount, dst_chain: u32::from(dst_chain), - dst_addr: dst_addr.clone(), - refund_addr: refund.clone(), - signed_quote: signed_quote.clone(), - request: request.clone(), - relay_instructions: relay_instructions.clone(), + dst_addr, + refund_addr: refund, + signed_quote: quote, + request, + relay_instructions, }; assert_eq!(env.events().all(), [expected.to_xdr(&env, &client.address)]); @@ -127,43 +149,126 @@ fn request_happy_path_pays_payee_and_emits_event() { assert_eq!(native.balance(&payee), amount); } +/// A full 165-byte EQ01 quote (68 header + 32 body + 65 signature) must be +/// emitted byte-for-byte, proving the body and signature survive for off-chain +/// verification, and the 20-byte quoter must be lifted out of the header. #[test] -fn request_accepts_empty_request_and_relay_instructions() { +fn event_preserves_full_quote_bytes() { let env = Env::default(); env.mock_all_auths(); let native = install_native_token_mock(&env); - let src_chain = 10u16; - let dst_chain = 20u16; - let payee = Address::generate(&env); + let src_chain = 61u16; + let dst_chain = 2u16; let payer = Address::generate(&env); - let refund = Address::generate(&env); + let payee = Address::generate(&env); + native.set_balance(&payer, &10); + + let quoter = [0xABu8; 20]; + let tail: [u8; 97] = core::array::from_fn(|i| i as u8); // 32-byte body + 65-byte signature + let quote = build_quote( + &env, + "er, + &[0xCD; 32], + src_chain, + dst_chain, + env.ledger().timestamp() + 1, + &tail, + ); + assert_eq!(quote.len(), 165); + + let client = register_executor(&env, src_chain, &native.address); let dst_addr = BytesN::<32>::from_array(&env, &[1u8; 32]); + let refund = Address::generate(&env); + + client.request_execution( + &u32::from(dst_chain), + &dst_addr, + &refund, + &payer, + &payee, + &5, + "e, + &Bytes::new(&env), + &Bytes::new(&env), + ); - native.set_balance(&payer, &99); + let expected = RequestForExecution { + quoter_address: BytesN::from_array(&env, "er), + amt_paid: 5, + dst_chain: u32::from(dst_chain), + dst_addr, + refund_addr: refund, + signed_quote: quote, + request: Bytes::new(&env), + relay_instructions: Bytes::new(&env), + }; + assert_eq!(env.events().all(), [expected.to_xdr(&env, &client.address)]); +} + +#[test] +fn accepts_empty_request_and_relay_instructions() { + let env = Env::default(); + env.mock_all_auths(); + let native = install_native_token_mock(&env); + + let src_chain = 10u16; + let dst_chain = 20u16; + let payer = Address::generate(&env); + let payee = Address::generate(&env); let client = register_executor(&env, src_chain, &native.address); - let signed_quote = build_quote(&env, src_chain, dst_chain, env.ledger().timestamp() + 60); + let quote = build_quote( + &env, + &[0u8; 20], + &[0u8; 32], + src_chain, + dst_chain, + env.ledger().timestamp() + 60, + &[], + ); let res = client.try_request_execution( &u32::from(dst_chain), - &dst_addr, - &refund, + &BytesN::<32>::from_array(&env, &[1u8; 32]), + &Address::generate(&env), &payer, &payee, - &42, - &signed_quote, + &0, + "e, &Bytes::new(&env), &Bytes::new(&env), ); assert_eq!(res, Ok(Ok(()))); - assert_eq!(native.balance(&payee), 42); + assert_eq!(native.balance(&payee), 0); } #[test] -#[should_panic(expected = "Error(Contract, #11)")] // QuoteExpired -fn request_rejects_expired_quote() { +#[should_panic(expected = "Error(Contract, #15)")] // InvalidQuote +fn rejects_quote_shorter_than_header() { + let env = Env::default(); + env.mock_all_auths(); + let native = install_native_token_mock(&env); + + let client = register_executor(&env, 1, &native.address); + + client.request_execution( + &2, + &BytesN::<32>::from_array(&env, &[0u8; 32]), + &Address::generate(&env), + &Address::generate(&env), + &Address::generate(&env), + &1, + &Bytes::from_slice(&env, &[0u8; 67]), + &Bytes::new(&env), + &Bytes::new(&env), + ); +} + +#[test] +#[should_panic(expected = "Error(Contract, #11)")] // QuoteExpired (boundary: strict >) +fn rejects_expired_quote() { let env = Env::default(); env.mock_all_auths(); env.ledger().with_mut(|li| li.timestamp = 1000); @@ -171,9 +276,16 @@ fn request_rejects_expired_quote() { let src_chain = 111u16; let dst_chain = 222u16; - let client = register_executor(&env, src_chain, &native.address); - let signed_quote = build_quote(&env, src_chain, dst_chain, 1000); + let quote = build_quote( + &env, + &[0u8; 20], + &[0u8; 32], + src_chain, + dst_chain, + 1000, + &[], + ); client.request_execution( &u32::from(dst_chain), @@ -182,22 +294,30 @@ fn request_rejects_expired_quote() { &Address::generate(&env), &Address::generate(&env), &1, - &signed_quote, - &Bytes::from_slice(&env, b"request"), + "e, + &Bytes::new(&env), &Bytes::new(&env), ); } #[test] #[should_panic(expected = "Error(Contract, #12)")] // QuoteSrcChainMismatch -fn request_rejects_source_chain_mismatch() { +fn rejects_src_chain_mismatch() { let env = Env::default(); env.mock_all_auths(); let native = install_native_token_mock(&env); let dst_chain = 88u16; let client = register_executor(&env, 9999, &native.address); - let signed_quote = build_quote(&env, 77, dst_chain, env.ledger().timestamp() + 600); + let quote = build_quote( + &env, + &[0u8; 20], + &[0u8; 32], + 77, + dst_chain, + env.ledger().timestamp() + 600, + &[], + ); client.request_execution( &u32::from(dst_chain), @@ -206,22 +326,30 @@ fn request_rejects_source_chain_mismatch() { &Address::generate(&env), &Address::generate(&env), &1, - &signed_quote, - &Bytes::from_slice(&env, b"request"), + "e, + &Bytes::new(&env), &Bytes::new(&env), ); } #[test] #[should_panic(expected = "Error(Contract, #13)")] // QuoteDstChainMismatch -fn request_rejects_dst_chain_mismatch() { +fn rejects_dst_chain_mismatch() { let env = Env::default(); env.mock_all_auths(); let native = install_native_token_mock(&env); let src_chain = 55u16; let client = register_executor(&env, src_chain, &native.address); - let signed_quote = build_quote(&env, src_chain, 66, env.ledger().timestamp() + 600); + let quote = build_quote( + &env, + &[0u8; 20], + &[0u8; 32], + src_chain, + 66, + env.ledger().timestamp() + 600, + &[], + ); client.request_execution( &99, @@ -230,15 +358,15 @@ fn request_rejects_dst_chain_mismatch() { &Address::generate(&env), &Address::generate(&env), &1, - &signed_quote, - &Bytes::from_slice(&env, b"request"), + "e, + &Bytes::new(&env), &Bytes::new(&env), ); } #[test] #[should_panic(expected = "Error(Contract, #14)")] // InvalidAmount -fn request_rejects_negative_amount() { +fn rejects_negative_amount() { let env = Env::default(); env.mock_all_auths(); let native = install_native_token_mock(&env); @@ -246,7 +374,15 @@ fn request_rejects_negative_amount() { let src_chain = 20u16; let dst_chain = 30u16; let client = register_executor(&env, src_chain, &native.address); - let signed_quote = build_quote(&env, src_chain, dst_chain, env.ledger().timestamp() + 600); + let quote = build_quote( + &env, + &[0u8; 20], + &[0u8; 32], + src_chain, + dst_chain, + env.ledger().timestamp() + 600, + &[], + ); client.request_execution( &u32::from(dst_chain), @@ -255,8 +391,8 @@ fn request_rejects_negative_amount() { &Address::generate(&env), &Address::generate(&env), &-1, - &signed_quote, - &Bytes::from_slice(&env, b"request"), + "e, + &Bytes::new(&env), &Bytes::new(&env), ); }