From 7156608593ea768106588dda3b4866bda5d3b966 Mon Sep 17 00:00:00 2001 From: Mackenzie-OO7 Date: Mon, 26 Jan 2026 15:06:23 +0100 Subject: [PATCH 1/3] refactor: add shared types module --- Cargo.toml | 7 ++++ contracts/ephemeral_account/Cargo.toml | 4 ++- contracts/ephemeral_account/src/events.rs | 2 +- contracts/ephemeral_account/src/lib.rs | 18 +++------- contracts/ephemeral_account/src/storage.rs | 16 +-------- contracts/shared/Cargo.toml | 10 ++++++ contracts/shared/src/lib.rs | 5 +++ contracts/shared/src/types.rs | 34 +++++++++++++++++++ contracts/sweep_controller/Cargo.toml | 6 +++- .../sweep_controller/src/authorization.rs | 19 ++++------- contracts/sweep_controller/src/lib.rs | 15 +++++--- contracts/sweep_controller/src/storage.rs | 3 +- scripts/build.sh | 0 13 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 Cargo.toml create mode 100644 contracts/shared/Cargo.toml create mode 100644 contracts/shared/src/lib.rs create mode 100644 contracts/shared/src/types.rs mode change 100644 => 100755 scripts/build.sh diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3f6deba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +resolver = "2" +members = [ + "contracts/ephemeral_account", + "contracts/sweep_controller", + "contracts/shared", +] diff --git a/contracts/ephemeral_account/Cargo.toml b/contracts/ephemeral_account/Cargo.toml index dc80589..062d946 100644 --- a/contracts/ephemeral_account/Cargo.toml +++ b/contracts/ephemeral_account/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] [dependencies] soroban-sdk = "22.0.0" +bridgelet-shared = { path = "../shared", version = "0.1.0" } + [dev-dependencies] soroban-sdk = { version = "22.0.0", features = ["testutils"] } diff --git a/contracts/ephemeral_account/src/events.rs b/contracts/ephemeral_account/src/events.rs index edc9718..49548c4 100644 --- a/contracts/ephemeral_account/src/events.rs +++ b/contracts/ephemeral_account/src/events.rs @@ -1,4 +1,4 @@ -use crate::storage::Payment; +use bridgelet_shared::Payment; use soroban_sdk::{contracttype, symbol_short, Address, Env, Vec}; #[contracttype] diff --git a/contracts/ephemeral_account/src/lib.rs b/contracts/ephemeral_account/src/lib.rs index 54b411c..5c28751 100644 --- a/contracts/ephemeral_account/src/lib.rs +++ b/contracts/ephemeral_account/src/lib.rs @@ -8,11 +8,12 @@ mod test; use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec}; +pub use bridgelet_shared::{AccountInfo, AccountStatus, Payment}; pub use errors::Error; pub use events::{ AccountCreated, AccountExpired, MultiPaymentReceived, PaymentReceived, SweepExecutedMulti, }; -pub use storage::{AccountStatus, DataKey, Payment}; +pub use storage::DataKey; #[contract] pub struct EphemeralAccountContract; @@ -283,16 +284,5 @@ impl EphemeralAccountContract { } } -/// Account information structure -#[derive(Clone)] -#[contracttype] -pub struct AccountInfo { - pub creator: Address, - pub status: AccountStatus, - pub expiry_ledger: u32, - pub recovery_address: Address, - pub payment_received: bool, - pub payment_count: u32, - pub payments: Vec, - pub swept_to: Option
, -} + + diff --git a/contracts/ephemeral_account/src/storage.rs b/contracts/ephemeral_account/src/storage.rs index 73fd04b..6ad268e 100644 --- a/contracts/ephemeral_account/src/storage.rs +++ b/contracts/ephemeral_account/src/storage.rs @@ -1,21 +1,7 @@ use soroban_sdk::{contracttype, Address, Env, Map}; +use bridgelet_shared::{AccountStatus, Payment}; -#[derive(Clone, Debug, Eq, PartialEq)] -#[contracttype] -pub struct Payment { - pub asset: Address, - pub amount: i128, - pub timestamp: u64, -} -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -#[contracttype] -pub enum AccountStatus { - Active = 0, - PaymentReceived = 1, - Swept = 2, - Expired = 3, -} #[contracttype] pub enum DataKey { diff --git a/contracts/shared/Cargo.toml b/contracts/shared/Cargo.toml new file mode 100644 index 0000000..e219632 --- /dev/null +++ b/contracts/shared/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bridgelet-shared" +version = "0.1.0" +edition = "2021" + +[dependencies] +soroban-sdk = "22.0.0" + +[lib] +crate-type = ["rlib"] diff --git a/contracts/shared/src/lib.rs b/contracts/shared/src/lib.rs new file mode 100644 index 0000000..8162091 --- /dev/null +++ b/contracts/shared/src/lib.rs @@ -0,0 +1,5 @@ +#![no_std] + +mod types; + +pub use types::{AccountInfo, AccountStatus, Payment}; diff --git a/contracts/shared/src/types.rs b/contracts/shared/src/types.rs new file mode 100644 index 0000000..6e2387d --- /dev/null +++ b/contracts/shared/src/types.rs @@ -0,0 +1,34 @@ +use soroban_sdk::{contracttype, Address, Vec}; + +// Represents a payment received by the ephemeral account. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Payment { + pub asset: Address, + pub amount: i128, + pub timestamp: u64, +} +// The current status of an ephemeral account. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq, Copy)] +#[repr(u32)] +pub enum AccountStatus { + Active = 0, + PaymentReceived = 1, + Swept = 2, + Expired = 3, +} + +/// Account information structure +#[derive(Clone)] +#[contracttype] +pub struct AccountInfo { + pub creator: Address, + pub status: AccountStatus, + pub expiry_ledger: u32, + pub recovery_address: Address, + pub payment_received: bool, + pub payment_count: u32, + pub payments: Vec, + pub swept_to: Option
, +} diff --git a/contracts/sweep_controller/Cargo.toml b/contracts/sweep_controller/Cargo.toml index 9a488a5..242828b 100644 --- a/contracts/sweep_controller/Cargo.toml +++ b/contracts/sweep_controller/Cargo.toml @@ -4,14 +4,18 @@ version = "0.1.0" edition = "2021" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "rlib"] [dependencies] soroban-sdk = "22.0.0" +bridgelet-shared = { path = "../shared", version = "0.1.0" } + soroban-token-sdk = "22.0.0" [dev-dependencies] soroban-sdk = { version = "22.0.0", features = ["testutils"] } +ephemeral_account = { path = "../ephemeral_account" } + [profile.release] opt-level = "z" diff --git a/contracts/sweep_controller/src/authorization.rs b/contracts/sweep_controller/src/authorization.rs index 78def2c..a5a09ac 100644 --- a/contracts/sweep_controller/src/authorization.rs +++ b/contracts/sweep_controller/src/authorization.rs @@ -1,6 +1,6 @@ use crate::errors::Error; use crate::storage; -use soroban_sdk::{Address, BytesN, Env}; +use soroban_sdk::{xdr::ToXdr, Address, BytesN, Env}; /// Construct the message to be signed for sweep authorization /// @@ -25,13 +25,11 @@ fn construct_sweep_message( // - destination (serialized as bytes) // - nonce (as u64, 8 bytes) // - contract_id (serialized as bytes) - let mut message = soroban_sdk::Vec::new(env); + let mut message = soroban_sdk::Bytes::new(env); // Add destination address bytes let dest_bytes = destination.to_xdr(env); - for byte in dest_bytes.iter() { - message.push_back(byte); - } + message.append(&dest_bytes); // Add nonce bytes (big-endian u64) message.push_back(((nonce >> 56) & 0xFF) as u8); @@ -45,12 +43,10 @@ fn construct_sweep_message( // Add contract id bytes let contract_bytes = contract_id.to_xdr(env); - for byte in contract_bytes.iter() { - message.push_back(byte); - } + message.append(&contract_bytes); // Hash the message using SHA256 - env.crypto().sha256(&message) + env.crypto().sha256(&message).into() } /// Verify sweep authorization signature using Ed25519 @@ -84,9 +80,8 @@ pub fn verify_sweep_auth( // Verify the Ed25519 signature env.crypto() - .ed25519_verify(&authorized_signer, &message, signature) - .map_err(|_| Error::SignatureVerificationFailed)?; - + .ed25519_verify(&authorized_signer, &message.into(), signature); + Ok(()) } diff --git a/contracts/sweep_controller/src/lib.rs b/contracts/sweep_controller/src/lib.rs index f93c29a..d9ed0cf 100644 --- a/contracts/sweep_controller/src/lib.rs +++ b/contracts/sweep_controller/src/lib.rs @@ -6,9 +6,11 @@ mod storage; mod transfers; use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env}; +use crate::ephemeral_account::Client as EphemeralAccountClient; use authorization::AuthContext; pub use errors::Error; +use bridgelet_shared::{AccountInfo, AccountStatus}; use transfers::TransferContext; #[contract] @@ -68,7 +70,7 @@ impl SweepController { // Call ephemeral account contract to validate and authorize sweep // This triggers the account's sweep() method which updates state - let account_client = ephemeral_account::Client::new(&env, &ephemeral_account); + let account_client = EphemeralAccountClient::new(&env, &ephemeral_account); // The account contract validates state and authorizes the sweep account_client.sweep(&destination, &auth_signature); @@ -81,7 +83,10 @@ impl SweepController { return Err(Error::AccountNotReady); } - let amount = info.payment_amount.ok_or(Error::AccountNotReady)?; + let amount = info.payments.iter().map(|p| p.amount).sum(); + if amount == 0 { + return Err(Error::AccountNotReady); + } // Execute the actual token transfer // Note: In production, the ephemeral account would need to authorize this transfer @@ -101,13 +106,13 @@ impl SweepController { /// Check if an account is ready for sweep pub fn can_sweep(env: Env, ephemeral_account: Address) -> bool { - let account_client = ephemeral_account::Client::new(&env, &ephemeral_account); + let account_client = EphemeralAccountClient::new(&env, &ephemeral_account); // Check if account exists and has payment let info = account_client.get_info(); info.payment_received - && info.status == ephemeral_account::AccountStatus::PaymentReceived + && info.status as u32 == AccountStatus::PaymentReceived as u32 && !account_client.is_expired() } } @@ -137,6 +142,6 @@ mod ephemeral_account { // Import from the actual ephemeral_account contract soroban_sdk::contractimport!( - file = "../ephemeral_account/target/wasm32-unknown-unknown/release/ephemeral_account.wasm" + file = "/home/levai/bridgelet-core/target/wasm32-unknown-unknown/release/ephemeral_account.wasm" ); } diff --git a/contracts/sweep_controller/src/storage.rs b/contracts/sweep_controller/src/storage.rs index 65d0429..0f9a837 100644 --- a/contracts/sweep_controller/src/storage.rs +++ b/contracts/sweep_controller/src/storage.rs @@ -1,7 +1,8 @@ -use soroban_sdk::{BytesN, Env, Address}; +use soroban_sdk::{contracttype, BytesN, Env, Address}; /// Data keys for contract storage #[derive(Clone)] +#[contracttype] pub enum DataKey { /// Authorized signer public key (BytesN<32> for Ed25519) AuthorizedSigner, diff --git a/scripts/build.sh b/scripts/build.sh old mode 100644 new mode 100755 From dfae896e606a7483e72c314ac68dd8e227a4b141 Mon Sep 17 00:00:00 2001 From: Mackenzie-OO7 Date: Mon, 26 Jan 2026 15:46:42 +0100 Subject: [PATCH 2/3] style: fix formatting issues --- contracts/ephemeral_account/src/lib.rs | 3 --- contracts/ephemeral_account/src/storage.rs | 4 +--- contracts/sweep_controller/src/authorization.rs | 12 ++++-------- contracts/sweep_controller/src/lib.rs | 13 +++++-------- contracts/sweep_controller/src/storage.rs | 10 +++++++--- contracts/sweep_controller/tests/integration.rs | 17 +++++++---------- 6 files changed, 24 insertions(+), 35 deletions(-) diff --git a/contracts/ephemeral_account/src/lib.rs b/contracts/ephemeral_account/src/lib.rs index 5c28751..243de7b 100644 --- a/contracts/ephemeral_account/src/lib.rs +++ b/contracts/ephemeral_account/src/lib.rs @@ -283,6 +283,3 @@ impl EphemeralAccountContract { Ok(()) } } - - - diff --git a/contracts/ephemeral_account/src/storage.rs b/contracts/ephemeral_account/src/storage.rs index 6ad268e..30a3921 100644 --- a/contracts/ephemeral_account/src/storage.rs +++ b/contracts/ephemeral_account/src/storage.rs @@ -1,7 +1,5 @@ -use soroban_sdk::{contracttype, Address, Env, Map}; use bridgelet_shared::{AccountStatus, Payment}; - - +use soroban_sdk::{contracttype, Address, Env, Map}; #[contracttype] pub enum DataKey { diff --git a/contracts/sweep_controller/src/authorization.rs b/contracts/sweep_controller/src/authorization.rs index 8c80e84..6e254f7 100644 --- a/contracts/sweep_controller/src/authorization.rs +++ b/contracts/sweep_controller/src/authorization.rs @@ -13,17 +13,13 @@ use soroban_sdk::{xdr::ToXdr, Address, BytesN, Env}; /// /// # Returns /// BytesN<32> containing the hash of the message components -fn construct_sweep_message( - env: &Env, - destination: &Address, - contract_id: &Address, -) -> BytesN<32> { +fn construct_sweep_message(env: &Env, destination: &Address, contract_id: &Address) -> BytesN<32> { // Get current nonce let nonce = storage::get_sweep_nonce(env); // Construct the message by concatenating: // - destination (serialized as bytes) - // - nonce (as u64, 8 bytes) + // - nonce (as u64, 8 bytes) // - contract_id (serialized as bytes) let mut message = soroban_sdk::Bytes::new(env); @@ -69,8 +65,8 @@ pub fn verify_sweep_auth( signature: &BytesN<64>, ) -> Result<(), Error> { // Get the authorized signer public key from storage - let authorized_signer = storage::get_authorized_signer(env) - .ok_or(Error::AuthorizedSignerNotSet)?; + let authorized_signer = + storage::get_authorized_signer(env).ok_or(Error::AuthorizedSignerNotSet)?; // Get the sweep controller contract address let contract_id = env.current_contract_address(); diff --git a/contracts/sweep_controller/src/lib.rs b/contracts/sweep_controller/src/lib.rs index a75ab4d..403ea09 100644 --- a/contracts/sweep_controller/src/lib.rs +++ b/contracts/sweep_controller/src/lib.rs @@ -5,12 +5,12 @@ mod errors; mod storage; mod transfers; -use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env}; use crate::ephemeral_account::Client as EphemeralAccountClient; +use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env}; use authorization::AuthContext; -pub use errors::Error; use bridgelet_shared::{AccountInfo, AccountStatus}; +pub use errors::Error; use transfers::TransferContext; #[contract] @@ -79,8 +79,8 @@ impl SweepController { ) -> Result<(), Error> { // Validate destination if authorized destination is set (locked mode) if storage::has_authorized_destination(&env) { - let authorized_dest = storage::get_authorized_destination(&env) - .ok_or(Error::UnauthorizedDestination)?; + let authorized_dest = + storage::get_authorized_destination(&env).ok_or(Error::UnauthorizedDestination)?; if destination != authorized_dest { return Err(Error::UnauthorizedDestination); } @@ -156,10 +156,7 @@ impl SweepController { /// # Errors /// Returns Error::AuthorizationFailed if caller is not the creator /// Returns Error::AccountAlreadySwept if a sweep has already been executed - pub fn update_authorized_destination( - env: Env, - new_destination: Address, - ) -> Result<(), Error> { + pub fn update_authorized_destination(env: Env, new_destination: Address) -> Result<(), Error> { // Verify creator authorization let creator = storage::get_creator(&env).ok_or(Error::AuthorizationFailed)?; creator.require_auth(); diff --git a/contracts/sweep_controller/src/storage.rs b/contracts/sweep_controller/src/storage.rs index 96d76da..12e4511 100644 --- a/contracts/sweep_controller/src/storage.rs +++ b/contracts/sweep_controller/src/storage.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{contracttype, BytesN, Env, Address}; +use soroban_sdk::{contracttype, Address, BytesN, Env}; /// Data keys for contract storage #[contracttype] @@ -20,7 +20,9 @@ pub enum DataKey { /// * `env` - Soroban environment /// * `signer` - Ed25519 public key (32 bytes) pub fn set_authorized_signer(env: &Env, signer: &BytesN<32>) { - env.storage().instance().set(&DataKey::AuthorizedSigner, signer); + env.storage() + .instance() + .set(&DataKey::AuthorizedSigner, signer); } /// Get the authorized signer public key @@ -86,7 +88,9 @@ pub fn set_authorized_destination(env: &Env, destination: &Address) { /// # Returns /// The authorized destination address, or None if not set (flexible mode) pub fn get_authorized_destination(env: &Env) -> Option
{ - env.storage().instance().get(&DataKey::AuthorizedDestination) + env.storage() + .instance() + .get(&DataKey::AuthorizedDestination) } /// Check if an authorized destination is set diff --git a/contracts/sweep_controller/tests/integration.rs b/contracts/sweep_controller/tests/integration.rs index f9cf05b..a68e257 100644 --- a/contracts/sweep_controller/tests/integration.rs +++ b/contracts/sweep_controller/tests/integration.rs @@ -1,9 +1,9 @@ #![cfg(test)] +use ephemeral_account::{AccountStatus, EphemeralAccountContract, EphemeralAccountContractClient}; use soroban_sdk::{testutils::Address as _, Address, BytesN, Env}; -use sweep_controller::{SweepController, SweepControllerClient}; use sweep_controller::Error; -use ephemeral_account::{EphemeralAccountContract, EphemeralAccountContractClient, AccountStatus}; +use sweep_controller::{SweepController, SweepControllerClient}; /// Helper function to generate a valid Ed25519 keypair for testing /// In a real scenario, these would be generated by the off-chain system @@ -141,7 +141,7 @@ fn test_execute_sweep_with_invalid_signature() { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { controller_client.execute_sweep(&ephemeral_id, &destination, &invalid_sig); })); - + // We expect this to fail assert!(result.is_err()); @@ -305,10 +305,7 @@ fn test_unauthorized_signer_not_set() { // Execute sweep without initializing controller - should fail let result = controller_client.execute_sweep(&ephemeral_id, &destination, &auth_sig); - println!( - "Execute sweep without initialization result: {:?}", - result - ); + println!("Execute sweep without initialization result: {:?}", result); } /// Test initialization with authorized destination (locked mode) @@ -488,7 +485,7 @@ fn test_update_destination_before_sweep() { // Update destination before any sweep - should succeed controller_client.update_authorized_destination(&new_dest); - + // Verify the destination was updated by trying to sweep to new destination // (The actual sweep may fail due to signature, but destination validation should pass) let ephemeral_id = env.register_contract(None, EphemeralAccountContract); @@ -503,7 +500,7 @@ fn test_update_destination_before_sweep() { ephemeral_client.record_payment(&100, &asset); let auth_sig = BytesN::from_array(&env, &[1u8; 64]); - + // Try to sweep to the new destination - destination validation should pass // (signature verification may fail, but that's expected in tests) // We're mainly checking that destination validation doesn't fail with UnauthorizedDestination @@ -515,4 +512,4 @@ fn test_update_destination_before_sweep() { // But if it's UnauthorizedDestination, that's a problem // For now, we just check it doesn't panic with UnauthorizedDestination // (In a real test, we'd check the panic message) -} \ No newline at end of file +} From f9d916aa872c61b0c567ae99db075f26bb620d03 Mon Sep 17 00:00:00 2001 From: Mackenzie-OO7 Date: Mon, 26 Jan 2026 21:05:40 +0100 Subject: [PATCH 3/3] remove unused imports --- contracts/ephemeral_account/src/lib.rs | 2 +- contracts/sweep_controller/src/lib.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/ephemeral_account/src/lib.rs b/contracts/ephemeral_account/src/lib.rs index 243de7b..b6e1a07 100644 --- a/contracts/ephemeral_account/src/lib.rs +++ b/contracts/ephemeral_account/src/lib.rs @@ -6,7 +6,7 @@ mod storage; #[cfg(test)] mod test; -use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec}; +use soroban_sdk::{contract, contractimpl, Address, BytesN, Env, Vec}; pub use bridgelet_shared::{AccountInfo, AccountStatus, Payment}; pub use errors::Error; diff --git a/contracts/sweep_controller/src/lib.rs b/contracts/sweep_controller/src/lib.rs index 403ea09..b5bb7d8 100644 --- a/contracts/sweep_controller/src/lib.rs +++ b/contracts/sweep_controller/src/lib.rs @@ -3,15 +3,14 @@ mod authorization; mod errors; mod storage; -mod transfers; +// mod transfers; use crate::ephemeral_account::Client as EphemeralAccountClient; use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env}; use authorization::AuthContext; -use bridgelet_shared::{AccountInfo, AccountStatus}; +use bridgelet_shared::AccountStatus; pub use errors::Error; -use transfers::TransferContext; #[contract] pub struct SweepController;