From 2d9572847f68dcd95db8a2ada00c12a476a461ad Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 9 Sep 2026 13:59:18 +0200 Subject: [PATCH] refactor(platform-wallet)!: remove dead C ABI exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine audit entries in one commit, because they all change the same ABI surface and the cbindgen header has to be regenerated once. rs-platform-wallet-ffi: - rust-ffi-011: `established_contact_{get_incoming_request,get_outgoing_request, is_payment_channel_broken}` and the original `_get_contact_id` (its body moved into the `_get_contact_identity_id` alias Swift actually calls); `contact_request_create`; `managed_identity_{send,accept}_contact_request` and `_ignore_contact_sender` (they operate on a cloned snapshot handle, so even if called they could not change wallet state); the counts-only `platform_wallet_dpns_marketplace_sync` (Swift and the JNI call `_detailed`); the `ContactRequest`/`EstablishedContact` structs in types.rs, absent from the generated header. - rust-ffi-039: `platform_wallet_info.rs`, `identity_manager.rs`, `WALLET_INFO_STORAGE`, `IDENTITY_MANAGER_STORAGE` — a second, unmanaged way to build a wallet and identity manager. - rust-ffi-041: `asset_lock_manager_recover` — 58 lines of unsafe deserializing host-supplied tx and proof bytes, with no consumer. - rust-ffi-042: `platform_address_wallet_{add_provider,restore_sync_state, sync_balances,free_sync_result}`, the `platform_addresses/sync.rs` module, and the `AddressSyncResultFFI`/`FoundAddressEntryFFI`/`AbsentAddressEntryFFI`/ `AddressSyncMetricsFFI` types with their `From` conversion. `AddressSyncConfigFFI` stays. - rust-ffi-043: `platform_address_wallet_withdraw` (the raw-script variant that bypasses the network check); `_withdraw_to_address` stays. - rust-ffi-044: `platform_wallet_ffi_init`, `_ffi_version`, `_identifier_to_hex`, `_identifier_from_hex`, `_serialize_to_json_bytes`, `_deserialize_from_json_bytes`, `platform_wallet_get_id`, `_pubkey_hash_from_private_key`, the non-birth-height `platform_wallet_manager_create_wallet_from_{seed,mnemonic}` pair, and `_list_masternodes`/`_free_masternodes` v1. `MasternodeEntryFFI` stays — V2 embeds it, and the layout tests got a local release helper. - rust-ffi-081: two `PLATFORM_WALLET_PERSISTENCE_CAPABILITY_*` alias constants ("source-compatible alias") with no consumers. This is the same symbol as the FFI half of rust-core-146 — a duplicate in the audit, counted once. - rust-ffi-086: `impl Default for WalletRestoreEntryFFI` — 39 lines to keep in sync with a 26-field struct, with no callers. rs-sdk-ffi (rust-sdk-ffi-005): - `token/emergency_action.rs`, `token/purchase.rs`, `token/config_update.rs` (the last returned "not yet implemented" for 4 of 9 variants and never read `params.action_takers`), and `dash_sdk_document_make_handle` together with `DashSDKDocumentHandleParams`. swift-sdk: the wrappers over the removed exports — `ContactRequest.create` and `ManagedIdentity.{sendContactRequest,acceptContactRequest,ignoreContactSender}`. None had a call site in SwiftDashSDK or SwiftExampleApp. The `test_get_dashpay_profile_unmanaged_identity_reports_not_found` test was passing for the wrong reason: it built its handle in `WALLET_INFO_STORAGE` while `platform_wallet_get_dashpay_profile` reads `PLATFORM_WALLET_STORAGE` — it never had a real wallet and only ever pinned "unknown handle -> NotFound". It stays, but now says what it actually checks. Verified end to end: `cargo check --workspace --all-targets`, `cargo test -p platform-wallet-ffi` (343 tests), clippy, cbindgen header regeneration via `swift-sdk/build_ios.sh --target sim`, the SwiftDashSDK and SwiftExampleApp builds against the regenerated header, the Android `libdash_sdk_jni.so` link, and `./gradlew :sdk:assembleDebug`. The regenerated header was checked both ways: every removed symbol is gone from it, and the surviving siblings (`_list_masternodes_v2`, `_get_contact_identity_id`, `_withdraw_to_address`, `_sync_detailed`, `_create_wallet_from_seed_with_birth_height`) are still present. BREAKING CHANGE: the listed symbols disappear from the generated C header; kotlin-sdk and rs-unified-sdk-jni need a rebuild (neither referenced any of the removed names — grep-verified). Co-Authored-By: Claude Opus 5 --- .../src/asset_lock/sync.rs | 59 -- .../rs-platform-wallet-ffi/src/contact.rs | 70 -- .../src/contact_request.rs | 41 -- .../src/core_wallet_types.rs | 23 +- .../src/dpns_marketplace.rs | 74 +- .../rs-platform-wallet-ffi/src/dpns_sync.rs | 2 +- .../src/established_contact.rs | 73 +- packages/rs-platform-wallet-ffi/src/handle.rs | 8 - .../src/identity_manager.rs | 253 ------- packages/rs-platform-wallet-ffi/src/lib.rs | 38 - .../rs-platform-wallet-ffi/src/manager.rs | 50 -- .../rs-platform-wallet-ffi/src/persistence.rs | 14 - .../src/platform_address_types.rs | 129 ---- .../src/platform_addresses/mod.rs | 2 - .../src/platform_addresses/sync.rs | 42 -- .../src/platform_addresses/wallet.rs | 60 +- .../src/platform_addresses/withdrawal.rs | 86 +-- .../src/platform_wallet_info.rs | 187 ----- packages/rs-platform-wallet-ffi/src/types.rs | 18 - packages/rs-platform-wallet-ffi/src/utils.rs | 260 +------ packages/rs-platform-wallet-ffi/src/wallet.rs | 95 +-- .../src/wallet_restore_types.rs | 40 - .../tests/comprehensive_tests.rs | 217 +----- .../tests/integration_tests.rs | 251 +------ packages/rs-sdk-ffi/src/document/create.rs | 168 +---- .../rs-sdk-ffi/src/token/config_update.rs | 697 ------------------ .../rs-sdk-ffi/src/token/emergency_action.rs | 697 ------------------ packages/rs-sdk-ffi/src/token/mod.rs | 6 - packages/rs-sdk-ffi/src/token/purchase.rs | 690 ----------------- .../PlatformWallet/ContactRequest.swift | 37 - .../PlatformWallet/ManagedIdentity.swift | 21 - 31 files changed, 55 insertions(+), 4353 deletions(-) delete mode 100644 packages/rs-platform-wallet-ffi/src/identity_manager.rs delete mode 100644 packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs delete mode 100644 packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs delete mode 100644 packages/rs-sdk-ffi/src/token/config_update.rs delete mode 100644 packages/rs-sdk-ffi/src/token/emergency_action.rs delete mode 100644 packages/rs-sdk-ffi/src/token/purchase.rs diff --git a/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs b/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs index 585d7c7858b..f4ebe2b8ef2 100644 --- a/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs +++ b/packages/rs-platform-wallet-ffi/src/asset_lock/sync.rs @@ -313,65 +313,6 @@ pub unsafe extern "C" fn asset_lock_manager_catch_up_blocking( } } -/// Recover a tracked asset lock from a serialized transaction. -/// -/// Re-tracks the asset lock in memory so it can be resumed later. -/// The transaction must be a valid asset lock transaction with a -/// special transaction payload. -#[no_mangle] -#[allow(clippy::too_many_arguments)] -pub unsafe extern "C" fn asset_lock_manager_recover( - handle: Handle, - tx_bytes: *const u8, - tx_bytes_len: usize, - amount_duffs: u64, - account_index: u32, - funding_type: u32, - identity_index: u32, - txid: *const [u8; 32], - vout: u32, - proof_bytes: *const u8, - proof_len: usize, -) -> PlatformWalletFFIResult { - check_ptr!(tx_bytes); - check_ptr!(txid); - - // Parse transaction - let tx_data = std::slice::from_raw_parts(tx_bytes, tx_bytes_len); - let tx: dashcore::Transaction = - unwrap_result_or_return!(dashcore::consensus::deserialize(tx_data)); - - let funding = unwrap_option_or_return!(super::build::parse_funding_type(funding_type)); - - let out_point = parse_outpoint(txid, vout); - - // Parse optional proof - let proof = if !proof_bytes.is_null() && proof_len > 0 { - let data = std::slice::from_raw_parts(proof_bytes, proof_len); - let (p, _) = unwrap_result_or_return!(dpp::bincode::decode_from_slice( - data, - dpp::bincode::config::standard() - )); - Some(p) - } else { - None - }; - - let option = ASSET_LOCK_MANAGER_STORAGE.with_item(handle, |manager| { - manager.recover_asset_lock_blocking( - tx, - amount_duffs, - account_index, - funding, - identity_index, - out_point, - proof, - ); - }); - unwrap_option_or_return!(option); - PlatformWalletFFIResult::ok() -} - #[cfg(test)] mod tests { use super::{ diff --git a/packages/rs-platform-wallet-ffi/src/contact.rs b/packages/rs-platform-wallet-ffi/src/contact.rs index ecade1d7fd9..08bf9a593b8 100644 --- a/packages/rs-platform-wallet-ffi/src/contact.rs +++ b/packages/rs-platform-wallet-ffi/src/contact.rs @@ -1,7 +1,5 @@ -use crate::contact_request::CONTACT_REQUEST_STORAGE; use crate::error::*; use crate::handle::*; -use crate::identity_manager::ffi_noop_persister; use crate::types::*; use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; @@ -95,74 +93,6 @@ pub unsafe extern "C" fn managed_identity_is_contact_established( PlatformWalletFFIResult::ok() } -/// Send a contact request from this identity to another -/// The request will be added to sent_contact_requests -/// If there's already an incoming request from the recipient, the contact will be automatically established -#[no_mangle] -pub unsafe extern "C" fn managed_identity_send_contact_request( - identity_handle: Handle, - request_handle: Handle, -) -> PlatformWalletFFIResult { - let request_result = CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()); - - let request = unwrap_option_or_return!(request_result); - - let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { - // Return the persist result so a failure surfaces through the FFI - // result instead of being swallowed — correct for any persister on this - // handle path (today the infallible `ffi_noop_persister`). - identity.add_sent_contact_request(request, &ffi_noop_persister()) - }); - unwrap_result_or_return!(unwrap_option_or_return!(option)); - PlatformWalletFFIResult::ok() -} - -/// Accept an incoming contact request -/// This will add the request to incoming_contact_requests -/// If there's already a sent request to the sender, the contact will be automatically established -#[no_mangle] -pub unsafe extern "C" fn managed_identity_accept_contact_request( - identity_handle: Handle, - request_handle: Handle, -) -> PlatformWalletFFIResult { - let request_result = CONTACT_REQUEST_STORAGE.with_item(request_handle, |req| req.clone()); - - let request = unwrap_option_or_return!(request_result); - - let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { - // Return the persist result so a failure surfaces through the FFI - // result instead of being swallowed — correct for any persister on this - // handle path (today the infallible `ffi_noop_persister`). - identity.add_incoming_contact_request(request, &ffi_noop_persister()) - }); - unwrap_result_or_return!(unwrap_option_or_return!(option)); - PlatformWalletFFIResult::ok() -} - -/// Ignore a contact sender (per-sender mute, = block, reversible). -/// -/// Local in-memory path on a managed-identity handle (no persister) — -/// drops the sender's pending incoming request and records them in -/// `ignored_senders`. The durable, persisted path is the wallet-scoped -/// `platform_wallet_ignore_contact_sender`. -#[no_mangle] -pub unsafe extern "C" fn managed_identity_ignore_contact_sender( - identity_handle: Handle, - sender_id: *const u8, -) -> PlatformWalletFFIResult { - let id = unwrap_result_or_return!(unsafe { read_identifier(sender_id) }); - - let option = MANAGED_IDENTITY_STORAGE.with_item_mut(identity_handle, |identity| { - // `ignore_sender` returns a `ContactChangeSet`, not a `Result` — there is - // no error to surface. This handle has no persister, so the changeset is - // intentionally dropped; the durable `platform_wallet_ignore_contact_sender` - // path persists it. - drop(identity.ignore_sender(&id)); - }); - unwrap_option_or_return!(option); - PlatformWalletFFIResult::ok() -} - #[cfg(test)] mod tests { use super::*; diff --git a/packages/rs-platform-wallet-ffi/src/contact_request.rs b/packages/rs-platform-wallet-ffi/src/contact_request.rs index 433adc5d014..04c16e940e3 100644 --- a/packages/rs-platform-wallet-ffi/src/contact_request.rs +++ b/packages/rs-platform-wallet-ffi/src/contact_request.rs @@ -13,47 +13,6 @@ lazy_static::lazy_static! { pub static ref CONTACT_REQUEST_STORAGE: HandleStorage = HandleStorage::new(); } -/// Create a new contact request -#[no_mangle] -pub unsafe extern "C" fn contact_request_create( - sender_id: *const u8, - recipient_id: *const u8, - sender_key_index: u32, - recipient_key_index: u32, - account_reference: u32, - encrypted_public_key_bytes: *const std::os::raw::c_uchar, - encrypted_public_key_len: usize, - core_height_created_at: u32, - created_at: u64, - out_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(encrypted_public_key_bytes); - check_ptr!(out_handle); - - let sender = unwrap_result_or_return!(unsafe { read_identifier(sender_id) }); - let recipient = unwrap_result_or_return!(unsafe { read_identifier(recipient_id) }); - - let encrypted_key = - unsafe { std::slice::from_raw_parts(encrypted_public_key_bytes, encrypted_public_key_len) } - .to_vec(); - - let contact_request = ContactRequest::new( - sender, - recipient, - sender_key_index, - recipient_key_index, - account_reference, - encrypted_key, - core_height_created_at, - created_at, - ); - - let handle = CONTACT_REQUEST_STORAGE.insert(contact_request); - unsafe { *out_handle = handle }; - - PlatformWalletFFIResult::ok() -} - /// Create a contact request handle from a managed identity's sent request #[no_mangle] pub unsafe extern "C" fn managed_identity_get_sent_contact_request( diff --git a/packages/rs-platform-wallet-ffi/src/core_wallet_types.rs b/packages/rs-platform-wallet-ffi/src/core_wallet_types.rs index e8e717d0a18..153f721ed04 100644 --- a/packages/rs-platform-wallet-ffi/src/core_wallet_types.rs +++ b/packages/rs-platform-wallet-ffi/src/core_wallet_types.rs @@ -1972,6 +1972,23 @@ mod tests { } /// The FFI entry carries the platform HTTP port gated by + + /// Release a heap `MasternodeEntryFFI` array the way the removed + /// `platform_wallet_manager_free_masternodes` export used to. The v1 + /// struct is still part of the ABI (V2 embeds it), so these layout + /// pins keep building one — they just no longer need a public free + /// routine to hand it back. + unsafe fn free_v1_entries(entries: *mut MasternodeEntryFFI, count: usize) { + if entries.is_null() || count == 0 { + return; + } + let slice = std::slice::from_raw_parts_mut(entries, count); + for entry in slice.iter() { + crate::wallet::free_masternode_entry_strings(entry); + } + let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(entries, count)); + } + /// `has_platform_http_port`, and releases its heap C strings through the /// public free routine. #[test] @@ -1986,9 +2003,9 @@ mod tests { !entry.platform_ownership_checked, "default record: unchecked" ); - // Release the entry's heap C strings through the public free routine. + // Release the entry's heap C strings. let entries = Box::into_raw(vec![entry].into_boxed_slice()) as *mut MasternodeEntryFFI; - unsafe { crate::wallet::platform_wallet_manager_free_masternodes(entries, 1) }; + unsafe { free_v1_entries(entries, 1) }; } /// Pin the original array element layout used by already-built C/Swift @@ -2066,7 +2083,7 @@ mod tests { .unwrap(), "2.2.2.2:9999" ); - unsafe { crate::wallet::platform_wallet_manager_free_masternodes(v1, 2) }; + unsafe { free_v1_entries(v1, 2) }; let v2 = vec![ masternode_entry_v2_ffi(&first, dashcore::Network::Testnet), diff --git a/packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs b/packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs index 80a1dcbd1b4..736c99b993c 100644 --- a/packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs +++ b/packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs @@ -770,73 +770,18 @@ pub unsafe extern "C" fn platform_wallet_dpns_purchase_name( // On-demand sync // --------------------------------------------------------------------------- -/// Run one marketplace sync pass on THIS wallet and report its delta. +/// Run one marketplace sync pass on THIS wallet and retain the complete +/// delta and completion timestamp. /// /// Refreshes owned-name rows (price / sale state), adds newly observed /// names to the identity label lists, detects names that LEFT an /// identity (sold or transferred away), and refreshes the balances of -/// identities that sold a name. All four out-params are optional — pass -/// `null` to ignore any of them: -/// -/// * `out_names_tracked`: owned-name rows written this pass. -/// * `out_names_added`: labels newly observed on a wallet identity. -/// * `out_names_departed`: names that left a wallet identity. -/// * `out_prices_changed`: listed-price changes since the last pass. +/// identities that sold a name. /// /// This is the per-wallet, on-demand entry point (pull-to-refresh). The /// recurring cross-wallet sweep is the manager-level coordinator in /// [`crate::dpns_sync`]. #[no_mangle] -pub unsafe extern "C" fn platform_wallet_dpns_marketplace_sync( - wallet_handle: Handle, - out_names_tracked: *mut u32, - out_names_added: *mut u32, - out_names_departed: *mut u32, - out_prices_changed: *mut u32, -) -> PlatformWalletFFIResult { - // Optional out-params: define every non-null slot before the fallible - // work so an error return leaves well-defined zeros, not garbage. - unsafe { - for slot in [ - out_names_tracked, - out_names_added, - out_names_departed, - out_prices_changed, - ] { - if !slot.is_null() { - *slot = 0; - } - } - } - - let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| { - let identity = wallet.identity().clone(); - block_on_worker(async move { identity.sync_dpns_marketplace().await }) - }); - let result = unwrap_option_or_return!(option); - let summary = unwrap_result_or_return!(result); - - unsafe { - if !out_names_tracked.is_null() { - *out_names_tracked = summary.names_tracked; - } - if !out_names_added.is_null() { - *out_names_added = summary.names_added.len() as u32; - } - if !out_names_departed.is_null() { - *out_names_departed = summary.names_departed.len() as u32; - } - if !out_prices_changed.is_null() { - *out_prices_changed = summary.prices_changed.len() as u32; - } - } - PlatformWalletFFIResult::ok() -} - -/// Run one marketplace sync pass and retain the complete delta and completion -/// timestamp. This is the lossless companion to the original counts-only -/// [`platform_wallet_dpns_marketplace_sync`] entry point. -#[no_mangle] pub unsafe extern "C" fn platform_wallet_dpns_marketplace_sync_detailed( wallet_handle: Handle, out_summary: *mut DpnsMarketplaceSyncSummaryFFI, @@ -1383,19 +1328,6 @@ mod tests { }; assert_eq!(r.code, PlatformWalletFFIResultCode::NotFound); - // All four sync out-params are optional — a null-only call must - // still reach the handle lookup. - let r = unsafe { - platform_wallet_dpns_marketplace_sync( - bogus, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ) - }; - assert_eq!(r.code, PlatformWalletFFIResultCode::NotFound); - // Required out-pointer missing: rejected before the handle lookup. let c = CString::new("alice").unwrap(); let r = unsafe { diff --git a/packages/rs-platform-wallet-ffi/src/dpns_sync.rs b/packages/rs-platform-wallet-ffi/src/dpns_sync.rs index 2b31cf6aabb..4ec084c88ba 100644 --- a/packages/rs-platform-wallet-ffi/src/dpns_sync.rs +++ b/packages/rs-platform-wallet-ffi/src/dpns_sync.rs @@ -15,7 +15,7 @@ //! completion timestamp through out-params; all three are optional — //! pass null to ignore any of them. For a single wallet's delta (names //! tracked / added / departed / re-priced) use the per-wallet -//! [`platform_wallet_dpns_marketplace_sync`](crate::dpns_marketplace::platform_wallet_dpns_marketplace_sync) +//! [`platform_wallet_dpns_marketplace_sync_detailed`](crate::dpns_marketplace::platform_wallet_dpns_marketplace_sync_detailed) //! instead. //! //! Not auto-started. The host lifecycle calls diff --git a/packages/rs-platform-wallet-ffi/src/established_contact.rs b/packages/rs-platform-wallet-ffi/src/established_contact.rs index 71eb3c47a67..940dd281f0d 100644 --- a/packages/rs-platform-wallet-ffi/src/established_contact.rs +++ b/packages/rs-platform-wallet-ffi/src/established_contact.rs @@ -38,10 +38,10 @@ pub unsafe extern "C" fn managed_identity_get_established_contact( PlatformWalletFFIResult::ok() } -/// Get the contact identity ID from an established contact into a -/// 32-byte out-buffer. +/// Get the contact identity ID from an established contact. `out_id` +/// must point at writable storage of at least 32 bytes. #[no_mangle] -pub unsafe extern "C" fn established_contact_get_contact_id( +pub unsafe extern "C" fn established_contact_get_contact_identity_id( contact_handle: Handle, out_id: *mut u8, ) -> PlatformWalletFFIResult { @@ -54,51 +54,6 @@ pub unsafe extern "C" fn established_contact_get_contact_id( PlatformWalletFFIResult::ok() } -/// Get a handle to the outgoing contact request from an established contact -#[no_mangle] -pub unsafe extern "C" fn established_contact_get_outgoing_request( - contact_handle: Handle, - out_request_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(out_request_handle); - - let option = ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| contact.outgoing_request.clone()); - let req = unwrap_option_or_return!(option); - unsafe { - *out_request_handle = crate::contact_request::CONTACT_REQUEST_STORAGE.insert(req); - } - PlatformWalletFFIResult::ok() -} - -/// Get a handle to the incoming contact request from an established contact -#[no_mangle] -pub unsafe extern "C" fn established_contact_get_incoming_request( - contact_handle: Handle, - out_request_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(out_request_handle); - - let option = ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| contact.incoming_request.clone()); - let req = unwrap_option_or_return!(option); - unsafe { - *out_request_handle = crate::contact_request::CONTACT_REQUEST_STORAGE.insert(req); - } - PlatformWalletFFIResult::ok() -} - -/// Get the contact identity ID from an established contact (alias -/// for [`established_contact_get_contact_id`]). `out_id` must point -/// at writable storage of at least 32 bytes. -#[no_mangle] -pub unsafe extern "C" fn established_contact_get_contact_identity_id( - contact_handle: Handle, - out_id: *mut u8, -) -> PlatformWalletFFIResult { - unsafe { established_contact_get_contact_id(contact_handle, out_id) } -} - /// Get the alias for an established contact #[no_mangle] pub unsafe extern "C" fn established_contact_get_alias( @@ -152,28 +107,6 @@ pub unsafe extern "C" fn established_contact_is_hidden( PlatformWalletFFIResult::ok() } -/// Check whether an established contact's DashPay payment channel is -/// permanently broken. -/// -/// `true` means the account-building sweep hit a permanent failure -/// (decrypt/decode of the counterparty xpub, or a key-index validation -/// failure) and stopped retrying. The UI should disable "Send Dash" and -/// surface "Payment channel broken — ask the contact to send a new -/// request"; the flag clears automatically when a superseding contact -/// request (re-)establishes the relationship. -#[no_mangle] -pub unsafe extern "C" fn established_contact_is_payment_channel_broken( - contact_handle: Handle, - out_is_broken: *mut bool, -) -> PlatformWalletFFIResult { - check_ptr!(out_is_broken); - - let option = ESTABLISHED_CONTACT_STORAGE - .with_item(contact_handle, |contact| contact.payment_channel_broken); - *out_is_broken = unwrap_option_or_return!(option); - PlatformWalletFFIResult::ok() -} - /// Destroy an established contact handle and free resources #[no_mangle] pub unsafe extern "C" fn established_contact_destroy( diff --git a/packages/rs-platform-wallet-ffi/src/handle.rs b/packages/rs-platform-wallet-ffi/src/handle.rs index ad672e0d431..a369a2d8881 100644 --- a/packages/rs-platform-wallet-ffi/src/handle.rs +++ b/packages/rs-platform-wallet-ffi/src/handle.rs @@ -101,14 +101,6 @@ impl Default for HandleStorage { } } -/// Storage for PlatformWalletInfo handles -pub static WALLET_INFO_STORAGE: Lazy> = - Lazy::new(HandleStorage::new); - -/// Storage for IdentityManager handles -pub static IDENTITY_MANAGER_STORAGE: Lazy> = - Lazy::new(HandleStorage::new); - /// Storage for ManagedIdentity handles pub static MANAGED_IDENTITY_STORAGE: Lazy> = Lazy::new(HandleStorage::new); diff --git a/packages/rs-platform-wallet-ffi/src/identity_manager.rs b/packages/rs-platform-wallet-ffi/src/identity_manager.rs deleted file mode 100644 index 0fe211ae4bc..00000000000 --- a/packages/rs-platform-wallet-ffi/src/identity_manager.rs +++ /dev/null @@ -1,253 +0,0 @@ -use crate::check_ptr; -use crate::error::*; -use crate::handle::*; -use crate::types::*; -use crate::{unwrap_option_or_return, unwrap_result_or_return}; -use platform_wallet::wallet::persister::{NoPlatformPersistence, WalletPersister}; -use platform_wallet::IdentityManager; -use std::sync::Arc; - -pub(crate) fn ffi_noop_persister() -> WalletPersister { - WalletPersister::new([0u8; 32], Arc::new(NoPlatformPersistence)) -} - -/// Create a new empty IdentityManager -#[no_mangle] -pub unsafe extern "C" fn identity_manager_create( - out_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(out_handle); - - let manager = IdentityManager::default(); - let handle = IDENTITY_MANAGER_STORAGE.insert(manager); - unsafe { *out_handle = handle }; - - PlatformWalletFFIResult::ok() -} - -/// Add a managed identity to the manager. -/// -/// Stand-alone identity-manager handles aren't bound to a wallet, so -/// the identity lands in the out-of-wallet bucket — the same place -/// observed identities go. Real wallet flows route through -/// [`crate::IdentityWallet`] APIs which thread `wallet_id` themselves. -#[no_mangle] -pub unsafe extern "C" fn identity_manager_add_identity( - manager_handle: Handle, - identity_handle: Handle, -) -> PlatformWalletFFIResult { - let identity_option = - MANAGED_IDENTITY_STORAGE.with_item(identity_handle, |identity| identity.clone()); - let identity = unwrap_option_or_return!(identity_option); - - let option = IDENTITY_MANAGER_STORAGE.with_item_mut(manager_handle, |manager| { - manager.add_out_of_wallet_identity(identity.identity, &ffi_noop_persister()) - }); - let result = unwrap_option_or_return!(option); - unwrap_result_or_return!(result); - PlatformWalletFFIResult::ok() -} - -/// Remove an identity from the manager. -/// -/// `identity_id` is a `*const u8` pointing at a 32-byte identifier -/// buffer. Pointer-passing rather than by-value `IdentifierBytes` -/// keeps the ABI safe across `@_silgen_name` (Swift would otherwise -/// hand the callee a garbage register slot for >16-byte aggregates). -#[no_mangle] -pub unsafe extern "C" fn identity_manager_remove_identity( - manager_handle: Handle, - identity_id: *const u8, -) -> PlatformWalletFFIResult { - let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); - - let option = IDENTITY_MANAGER_STORAGE.with_item_mut(manager_handle, |manager| { - manager.remove_identity(&id, &ffi_noop_persister()) - }); - let result = unwrap_option_or_return!(option); - if result.is_err() { - return PlatformWalletFFIResult::err( - PlatformWalletFFIResultCode::ErrorIdentityNotFound, - "Identity not found", - ); - } - PlatformWalletFFIResult::ok() -} - -/// Get an identity by ID. `identity_id` is a `*const u8` to a -/// 32-byte buffer; see [`identity_manager_remove_identity`] for the -/// rationale on pointer-passing vs by-value. -#[no_mangle] -pub unsafe extern "C" fn identity_manager_get_identity( - manager_handle: Handle, - identity_id: *const u8, - out_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(out_handle); - - let id = unwrap_result_or_return!(unsafe { read_identifier(identity_id) }); - - let option = IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| { - manager.managed_identity(&id).cloned() - }); - let inner = unwrap_option_or_return!(option); - let identity = unwrap_option_or_return!(inner); - unsafe { *out_handle = MANAGED_IDENTITY_STORAGE.insert(identity) }; - PlatformWalletFFIResult::ok() -} - -/// Get all identity IDs across both buckets. -#[no_mangle] -pub unsafe extern "C" fn identity_manager_get_all_identity_ids( - manager_handle: Handle, - out_array: *mut IdentifierArray, -) -> PlatformWalletFFIResult { - check_ptr!(out_array); - // Sentinel first: the handle lookup below is fallible, and - // `platform_wallet_identifier_array_free` reconstructs a `Vec` from any - // non-null pointer/count pair — see `IdentifierArray::empty`. - unsafe { *out_array = IdentifierArray::empty() }; - - let option = - IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| manager.identity_ids()); - let ids = unwrap_option_or_return!(option); - unsafe { *out_array = IdentifierArray::new(ids) }; - PlatformWalletFFIResult::ok() -} - -/// Get the count of identities across both buckets. -#[no_mangle] -pub unsafe extern "C" fn identity_manager_get_identity_count( - manager_handle: Handle, - out_count: *mut usize, -) -> PlatformWalletFFIResult { - check_ptr!(out_count); - - let option = - IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| manager.identity_count()); - *out_count = unwrap_option_or_return!(option); - PlatformWalletFFIResult::ok() -} - -/// Destroy IdentityManager and free resources -#[no_mangle] -pub unsafe extern "C" fn identity_manager_destroy( - manager_handle: Handle, -) -> PlatformWalletFFIResult { - if IDENTITY_MANAGER_STORAGE.remove(manager_handle).is_some() { - PlatformWalletFFIResult::ok() - } else { - PlatformWalletFFIResult::err( - PlatformWalletFFIResultCode::ErrorInvalidHandle, - "Invalid manager handle", - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use dpp::identity::v0::IdentityV0; - use dpp::identity::{Identity, IdentityPublicKey, KeyType, Purpose, SecurityLevel}; - use dpp::prelude::Identifier; - use platform_wallet::ManagedIdentity; - use std::collections::BTreeMap; - - fn create_test_identity() -> Identity { - let id = Identifier::from([1u8; 32]); - let mut public_keys = BTreeMap::new(); - - public_keys.insert( - 0, - IdentityPublicKey::V0( - dpp::identity::identity_public_key::v0::IdentityPublicKeyV0 { - id: 0, - key_type: KeyType::ECDSA_SECP256K1, - purpose: Purpose::AUTHENTICATION, - security_level: SecurityLevel::MASTER, - read_only: false, - data: dpp::platform_value::BinaryData::new(vec![2u8; 33]), - disabled_at: None, - contract_bounds: None, - }, - ), - ); - - let identity_v0 = IdentityV0 { - id, - public_keys, - balance: 1000, - revision: 1, - }; - Identity::V0(identity_v0) - } - - #[test] - fn test_create_identity_manager() { - unsafe { - let mut handle: Handle = NULL_HANDLE; - - let result = identity_manager_create(&mut handle); - - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(handle, NULL_HANDLE); - - identity_manager_destroy(handle); - } - } - - #[test] - fn test_get_identity_count() { - unsafe { - let mut handle: Handle = NULL_HANDLE; - - identity_manager_create(&mut handle); - - let mut count: usize = 0; - let result = identity_manager_get_identity_count(handle, &mut count); - - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(count, 0); - - identity_manager_destroy(handle); - } - } - - #[test] - fn test_add_and_lookup_out_of_wallet_identity() { - // Standalone manager handle has no wallet — `add_identity` - // routes into the out-of-wallet bucket. Verify lookup works - // round-trip and the count reflects the insert. - unsafe { - let mut manager_handle: Handle = NULL_HANDLE; - - identity_manager_create(&mut manager_handle); - - let identity = create_test_identity(); - let id_bytes: [u8; 32] = [1u8; 32]; - let managed_identity = ManagedIdentity::new(identity, 0); - let identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed_identity); - - identity_manager_add_identity(manager_handle, identity_handle); - - let mut count: usize = 0; - identity_manager_get_identity_count(manager_handle, &mut count); - assert_eq!(count, 1); - - let mut got: Handle = NULL_HANDLE; - let result = identity_manager_get_identity(manager_handle, id_bytes.as_ptr(), &mut got); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(got, NULL_HANDLE); - - identity_manager_destroy(manager_handle); - } - } - - #[test] - fn test_destroy_invalid_handle() { - unsafe { - let result = identity_manager_destroy(9999); - assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorInvalidHandle); - } - } -} diff --git a/packages/rs-platform-wallet-ffi/src/lib.rs b/packages/rs-platform-wallet-ffi/src/lib.rs index 3dc26554a46..adade3610f3 100644 --- a/packages/rs-platform-wallet-ffi/src/lib.rs +++ b/packages/rs-platform-wallet-ffi/src/lib.rs @@ -41,7 +41,6 @@ pub mod identity_discovery; pub mod identity_key_preview; pub mod identity_keys_from_mnemonic; pub mod identity_loading; -pub mod identity_manager; pub mod identity_persistence; pub mod identity_registration; pub mod identity_registration_funded_with_signer; @@ -67,7 +66,6 @@ pub mod persistence; pub mod platform_address_sync; pub mod platform_address_types; pub mod platform_addresses; -pub mod platform_wallet_info; pub mod provider_key_at_index; mod runtime; pub mod secp256k1_primitives; @@ -123,7 +121,6 @@ pub use identity_discovery::*; pub use identity_key_preview::*; pub use identity_keys_from_mnemonic::*; pub use identity_loading::*; -pub use identity_manager::*; pub use identity_persistence::*; pub use identity_registration::*; pub use identity_registration_funded_with_signer::*; @@ -145,7 +142,6 @@ pub use persistence::*; pub use platform_address_sync::*; pub use platform_address_types::*; pub use platform_addresses::*; -pub use platform_wallet_info::*; pub use provider_key_at_index::*; pub use secp256k1_primitives::*; #[cfg(feature = "shielded")] @@ -163,37 +159,3 @@ pub use wallet::*; pub use wallet_registration_persistence::*; pub use wallet_restore_types::*; pub use xpub_render::*; - -/// Initialize the FFI library -/// Must be called before using any other functions -#[no_mangle] -pub extern "C" fn platform_wallet_ffi_init() { - // Initialize any global state if needed - // Currently a no-op but kept for future compatibility -} - -/// Get the version of the platform wallet FFI library -#[no_mangle] -pub extern "C" fn platform_wallet_ffi_version() -> *const std::os::raw::c_char { - concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const std::os::raw::c_char -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_init() { - platform_wallet_ffi_init(); - // Should not panic - } - - #[test] - fn test_version() { - let version = platform_wallet_ffi_version(); - assert!(!version.is_null()); - - let version_str = unsafe { std::ffi::CStr::from_ptr(version).to_str().unwrap() }; - assert!(!version_str.is_empty()); - } -} diff --git a/packages/rs-platform-wallet-ffi/src/manager.rs b/packages/rs-platform-wallet-ffi/src/manager.rs index 84ef9bddac9..ba1da81ac4d 100644 --- a/packages/rs-platform-wallet-ffi/src/manager.rs +++ b/packages/rs-platform-wallet-ffi/src/manager.rs @@ -517,32 +517,6 @@ unsafe fn create_wallet_from_mnemonic_impl( PlatformWalletFFIResult::ok() } -/// Create a wallet from raw seed bytes (64 bytes). -/// -/// On success, `out_wallet_handle` is set to a `PlatformWallet` handle and -/// `out_wallet_id` is filled with the 32-byte wallet ID. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( - manager_handle: Handle, - network: FFINetwork, - seed_bytes: *const u8, - seed_len: usize, - account_options: u32, - out_wallet_handle: *mut Handle, - out_wallet_id: *mut [u8; 32], -) -> PlatformWalletFFIResult { - create_wallet_from_seed_impl( - manager_handle, - network, - seed_bytes, - seed_len, - account_options, - None, - out_wallet_handle, - out_wallet_id, - ) -} - /// Create a wallet from raw seed bytes (64 bytes) with an optional /// birth-height override. /// @@ -578,30 +552,6 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed_with_bi ) } -/// Create a wallet from a BIP39 mnemonic phrase (English). -/// -/// On success, `out_wallet_handle` is set to a `PlatformWallet` handle and -/// `out_wallet_id` is filled with the 32-byte wallet ID. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( - manager_handle: Handle, - mnemonic: *const std::os::raw::c_char, - network: FFINetwork, - account_options: u32, - out_wallet_handle: *mut Handle, - out_wallet_id: *mut [u8; 32], -) -> PlatformWalletFFIResult { - create_wallet_from_mnemonic_impl( - manager_handle, - mnemonic, - network, - account_options, - None, - out_wallet_handle, - out_wallet_id, - ) -} - /// Create a wallet from a BIP39 mnemonic phrase (English) with an optional /// birth-height override. /// diff --git a/packages/rs-platform-wallet-ffi/src/persistence.rs b/packages/rs-platform-wallet-ffi/src/persistence.rs index 2b0b23dbd49..cf158115e5d 100644 --- a/packages/rs-platform-wallet-ffi/src/persistence.rs +++ b/packages/rs-platform-wallet-ffi/src/persistence.rs @@ -116,16 +116,10 @@ pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITIES_VERSION: u32 = 1; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_ATOMIC_CHANGESETS: u64 = 1 << 0; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_INVITATIONS: u64 = 1 << 1; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_ASSET_LOCK_FUNDING_INDICES: u64 = 1 << 2; -/// Source-compatible alias for the original capability name. -pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_ACCOUNT_ADDRESS_POOLS: u64 = - PLATFORM_WALLET_PERSISTENCE_CAPABILITY_ASSET_LOCK_FUNDING_INDICES; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_SHIELDED_VIEWING_KEYS: u64 = 1 << 3; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_PROVIDER_TRANSACTIONS: u64 = 1 << 4; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_UNSIGNED_TOKEN_STORAGE: u64 = 1 << 5; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_PENDING_CONTACT_CRYPTO: u64 = 1 << 6; -/// Source-compatible alias for the original capability name. -pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_DEFERRED_CONTACT_CRYPTO: u64 = - PLATFORM_WALLET_PERSISTENCE_CAPABILITY_PENDING_CONTACT_CRYPTO; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_WALLET_RESTORE: u64 = 1 << 7; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_DPNS_NAME_STATES: u64 = 1 << 8; pub const PLATFORM_WALLET_PERSISTENCE_CAPABILITY_TRACKED_ASSET_LOCKS: u64 = 1 << 9; @@ -7564,14 +7558,6 @@ mod tests { PLATFORM_WALLET_PERSISTENCE_CAPABILITY_DASHPAY_PAYMENTS, PersistenceCapabilities::DASHPAY_PAYMENTS.bits() ); - assert_eq!( - PLATFORM_WALLET_PERSISTENCE_CAPABILITY_ACCOUNT_ADDRESS_POOLS, - PLATFORM_WALLET_PERSISTENCE_CAPABILITY_ASSET_LOCK_FUNDING_INDICES - ); - assert_eq!( - PLATFORM_WALLET_PERSISTENCE_CAPABILITY_DEFERRED_CONTACT_CRYPTO, - PLATFORM_WALLET_PERSISTENCE_CAPABILITY_PENDING_CONTACT_CRYPTO - ); } /// A store round carrying a `dashpay_payments_overlay` — the diff --git a/packages/rs-platform-wallet-ffi/src/platform_address_types.rs b/packages/rs-platform-wallet-ffi/src/platform_address_types.rs index 7d56610a1e7..5323c10bbc5 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_address_types.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_address_types.rs @@ -350,57 +350,6 @@ impl From for dash_sdk::platform::address_sync::AddressSyn } } -// --------------------------------------------------------------------------- -// Sync result -// --------------------------------------------------------------------------- - -/// Found address entry in sync result. -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct FoundAddressEntryFFI { - pub index: u32, - pub address: PlatformAddressFFI, - pub nonce: u32, - pub balance: u64, -} - -/// Absent address entry in sync result. -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct AbsentAddressEntryFFI { - pub index: u32, - pub address: PlatformAddressFFI, -} - -/// Sync metrics. -#[repr(C)] -#[derive(Debug, Clone, Copy, Default)] -pub struct AddressSyncMetricsFFI { - pub trunk_queries: u32, - pub branch_queries: u32, - pub total_elements_seen: u32, - pub total_proof_bytes: u32, - pub iterations: u32, - pub compacted_queries: u32, - pub recent_queries: u32, - pub recent_entries_returned: u32, - pub compacted_entries_returned: u32, -} - -/// Single account sync result. -#[repr(C)] -pub struct AddressSyncResultFFI { - pub found: *mut FoundAddressEntryFFI, - pub found_count: usize, - pub absent: *mut AbsentAddressEntryFFI, - pub absent_count: usize, - pub checkpoint_height: u64, - pub new_sync_height: u64, - pub new_sync_timestamp: u64, - pub last_known_recent_block: u64, - pub metrics: AddressSyncMetricsFFI, -} - /// Changeset output. #[repr(C)] pub struct PlatformAddressChangeSetFFI { @@ -432,84 +381,6 @@ impl PlatformAddressChangeSetFFI { // Conversion helpers // --------------------------------------------------------------------------- -impl - From< - &dash_sdk::platform::address_sync::AddressSyncResult< - platform_wallet::PlatformAddressTag, - key_wallet::PlatformP2PKHAddress, - >, - > for AddressSyncResultFFI -{ - fn from( - result: &dash_sdk::platform::address_sync::AddressSyncResult< - platform_wallet::PlatformAddressTag, - key_wallet::PlatformP2PKHAddress, - >, - ) -> Self { - // FFI consumers only care about the derivation index from the - // tag (the caller already knows which wallet/account is - // syncing). Flatten the tuple by dropping wallet_id and - // account_index here. - let found: Vec = result - .found - .iter() - .map(|(&((_, _, index), address), funds)| FoundAddressEntryFFI { - index, - address: address.into(), - nonce: funds.nonce, - balance: funds.balance, - }) - .collect(); - - let absent: Vec = result - .absent - .iter() - .map(|&((_, _, index), address)| AbsentAddressEntryFFI { - index, - address: address.into(), - }) - .collect(); - - let found_count = found.len(); - let absent_count = absent.len(); - - let found_ptr = if found.is_empty() { - std::ptr::null_mut() - } else { - Box::into_raw(found.into_boxed_slice()) as *mut FoundAddressEntryFFI - }; - - let absent_ptr = if absent.is_empty() { - std::ptr::null_mut() - } else { - Box::into_raw(absent.into_boxed_slice()) as *mut AbsentAddressEntryFFI - }; - - let m = &result.metrics; - Self { - found: found_ptr, - found_count, - absent: absent_ptr, - absent_count, - checkpoint_height: result.checkpoint_height, - new_sync_height: result.new_sync_height, - new_sync_timestamp: result.new_sync_timestamp, - last_known_recent_block: result.last_known_recent_block, - metrics: AddressSyncMetricsFFI { - trunk_queries: m.trunk_queries as u32, - branch_queries: m.branch_queries as u32, - total_elements_seen: m.total_elements_seen as u32, - total_proof_bytes: m.total_proof_bytes as u32, - iterations: m.iterations as u32, - compacted_queries: m.compacted_queries as u32, - recent_queries: m.recent_queries as u32, - recent_entries_returned: m.recent_entries_returned as u32, - compacted_entries_returned: m.compacted_entries_returned as u32, - }, - } - } -} - impl From<&platform_wallet::PlatformAddressChangeSet> for PlatformAddressChangeSetFFI { fn from(cs: &platform_wallet::PlatformAddressChangeSet) -> Self { let updated: Vec = cs diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs index e4edbbe926b..66098b877fa 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/mod.rs @@ -4,7 +4,6 @@ mod fund_from_asset_lock; mod funding_fee; -mod sync; mod transfer; mod wallet; mod withdrawal; @@ -12,7 +11,6 @@ mod withdrawal; // Re-export all FFI types and functions. pub use fund_from_asset_lock::*; pub use funding_fee::*; -pub use sync::*; pub use transfer::*; pub use wallet::*; pub use withdrawal::*; diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs deleted file mode 100644 index 27fe3c47b7d..00000000000 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/sync.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! FFI bindings for platform address sync operations. - -use crate::check_ptr; -use crate::error::*; -use crate::handle::*; -use crate::platform_address_types::*; -use crate::{unwrap_option_or_return, unwrap_result_or_return}; - -use super::runtime; - -/// Sync platform address balances across every platform payment account -/// on the wallet in a single trunk/branch scan. -/// -/// The changeset is persisted internally by the wallet. The returned -/// `AddressSyncResultFFI` aggregates results from every account — per- -/// account detail can be rebuilt by the caller using each found -/// address's derivation context. -#[no_mangle] -pub unsafe extern "C" fn platform_address_wallet_sync_balances( - handle: Handle, - has_config: bool, - config: *const AddressSyncConfigFFI, - out_result: *mut AddressSyncResultFFI, -) -> PlatformWalletFFIResult { - check_ptr!(out_result); - - let config_opt = if has_config && !config.is_null() { - Some(dash_sdk::platform::address_sync::AddressSyncConfig::from( - *config, - )) - } else { - None - }; - - let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { - runtime().block_on(wallet.sync_balances(config_opt)) - }); - let result = unwrap_option_or_return!(option); - let sync = unwrap_result_or_return!(result); - *out_result = AddressSyncResultFFI::from(&sync); - PlatformWalletFFIResult::ok() -} diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs index d42a6dfb6a6..286e2ccbbee 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/wallet.rs @@ -4,7 +4,7 @@ use crate::check_ptr; use crate::error::*; use crate::handle::*; use crate::platform_address_types::*; -use crate::{unwrap_option_or_return, unwrap_result_or_return}; +use crate::unwrap_option_or_return; use super::runtime; @@ -21,43 +21,6 @@ pub unsafe extern "C" fn platform_address_wallet_destroy( PlatformWalletFFIResult::ok() } -/// Add a provider for a new account index. -#[no_mangle] -pub unsafe extern "C" fn platform_address_wallet_add_provider( - handle: Handle, - account_index: u32, -) -> PlatformWalletFFIResult { - let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { - runtime().block_on(wallet.add_provider(account_index)) - }); - let result = unwrap_option_or_return!(option); - unwrap_result_or_return!(result); - PlatformWalletFFIResult::ok() -} - -/// Restore sync state from persisted values. -/// -/// Call after wallet creation and before the first sync to resume -/// incremental mode. Without this, every app launch does a full -/// trunk/branch/compact rescan. -#[no_mangle] -pub unsafe extern "C" fn platform_address_wallet_restore_sync_state( - handle: Handle, - sync_height: u64, - sync_timestamp: u64, - last_known_recent_block: u64, -) -> PlatformWalletFFIResult { - let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| { - runtime().block_on(wallet.restore_sync_state( - sync_height, - sync_timestamp, - last_known_recent_block, - )); - }); - unwrap_option_or_return!(option); - PlatformWalletFFIResult::ok() -} - // --------------------------------------------------------------------------- // Queries // --------------------------------------------------------------------------- @@ -191,24 +154,3 @@ pub unsafe extern "C" fn platform_address_wallet_free_changeset( )); } } - -/// Free a single sync result. -#[no_mangle] -pub unsafe extern "C" fn platform_address_wallet_free_sync_result( - result: *const AddressSyncResultFFI, -) { - if result.is_null() { - return; - } - let r = &*result; - if !r.found.is_null() && r.found_count > 0 { - drop(Vec::from_raw_parts(r.found, r.found_count, r.found_count)); - } - if !r.absent.is_null() && r.absent_count > 0 { - drop(Vec::from_raw_parts( - r.absent, - r.absent_count, - r.absent_count, - )); - } -} diff --git a/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs b/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs index cd9fe15f2a2..21d5ea9b328 100644 --- a/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs +++ b/packages/rs-platform-wallet-ffi/src/platform_addresses/withdrawal.rs @@ -15,89 +15,15 @@ use std::str::FromStr; use super::parse_input_selection; use crate::runtime::block_on_worker; -/// Withdraw platform credits to a Core L1 address. -#[no_mangle] -#[allow(clippy::too_many_arguments)] -pub unsafe extern "C" fn platform_address_wallet_withdraw( - handle: Handle, - account_index: u32, - input_type: InputSelectionType, - explicit_inputs: *const ExplicitInputFFI, - explicit_inputs_count: usize, - nonce_inputs: *const ExplicitInputWithNonceFFI, - nonce_inputs_count: usize, - output_script: *const u8, - output_script_len: usize, - core_fee_per_byte: u32, - fee_strategy: *const FeeStrategyStepFFI, - fee_strategy_count: usize, - signer_address_handle: *mut SignerHandle, - out_changeset: *mut PlatformAddressChangeSetFFI, -) -> PlatformWalletFFIResult { - check_ptr!(out_changeset); - // Sentinel first: input parsing, the wallet lookup, and the async - // withdraw below are all fallible. See - // `PlatformAddressChangeSetFFI::empty` for the double-free rationale. - *out_changeset = PlatformAddressChangeSetFFI::empty(); - check_ptr!(output_script); - check_ptr!(signer_address_handle); - - let script_bytes = std::slice::from_raw_parts(output_script, output_script_len); - let core_script = CoreScript::from_bytes(script_bytes.to_vec()); - - let input_selection = unwrap_result_or_return!(parse_input_selection( - input_type, - explicit_inputs, - explicit_inputs_count, - nonce_inputs, - nonce_inputs_count, - )); - - let fee = parse_fee_strategy(fee_strategy, fee_strategy_count); - - // Clone the wallet out of handle storage so the read lock is released - // before the long-running withdraw, then poll on a worker thread - // (8 MB stack): the withdraw future verifies the execution proof, and - // GroveDB proof verification recurses past the ~512 KB stacks of iOS - // dispatch / Swift-concurrency threads (see runtime.rs) — polling it - // on the calling thread crashes with EXC_BAD_ACCESS after the funds - // already moved on-chain. Round-trip the signer pointer through - // `usize` so the future's capture is `Send + 'static`; the caller - // guarantees the handle outlives this synchronously-awaited call. - let option = PLATFORM_ADDRESS_WALLET_STORAGE.with_item(handle, |wallet| wallet.clone()); - let wallet = unwrap_option_or_return!(option); - let signer_addr = signer_address_handle as usize; - let result = block_on_worker(async move { - let address_signer: &VTableSigner = unsafe { &*(signer_addr as *const VTableSigner) }; - wallet - .withdraw( - account_index, - input_selection, - core_script, - core_fee_per_byte, - fee, - None, - address_signer, - ) - .await - }); - let changeset = unwrap_result_or_return!(result); - *out_changeset = PlatformAddressChangeSetFFI::from(&changeset); - PlatformWalletFFIResult::ok() -} - /// Withdraw platform credits to a Core L1 address given as a base58 /// string (e.g. `yXV…` on testnet / `X…` on mainnet). /// -/// Sibling of [`platform_address_wallet_withdraw`] that accepts a -/// human-facing Core address instead of a pre-built `output_script` -/// byte buffer. The address is parsed and **network-checked against -/// the wallet's own network** entirely on the Rust side — a -/// testnet-shaped address can never be withdrawn to on a mainnet -/// wallet (and vice versa). The resulting P2PKH/P2SH `script_pubkey` -/// is then handed to the same `wallet.withdraw(...)` entry point, so -/// input selection, fee strategy, and signing are identical to the -/// raw-script path. +/// Takes a human-facing Core address rather than a pre-built +/// `output_script` byte buffer. The address is parsed and +/// **network-checked against the wallet's own network** entirely on the +/// Rust side — a testnet-shaped address can never be withdrawn to on a +/// mainnet wallet (and vice versa). The resulting P2PKH/P2SH +/// `script_pubkey` is handed to `wallet.withdraw(...)`. /// /// `signer_address_handle` is a `*mut SignerHandle` produced by /// `dash_sdk_signer_create_with_ctx` (e.g. via `KeychainSigner.handle`) diff --git a/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs b/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs deleted file mode 100644 index 64790230cbf..00000000000 --- a/packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::error::*; -use crate::handle::*; -use crate::types::{FFINetwork, Network}; -use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; -use key_wallet::wallet::initialization::WalletAccountCreationOptions; -use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; -use platform_wallet::PlatformWalletInfo; -use std::os::raw::{c_char, c_uchar}; - -/// Create a new PlatformWalletInfo from seed bytes. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_info_create_from_seed( - network: FFINetwork, - seed_bytes: *const c_uchar, - seed_len: usize, - out_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(seed_bytes); - check_ptr!(out_handle); - - let network: Network = network.into(); - - // Validate seed length (should be 64 bytes for BIP39) - if seed_len != 64 { - return PlatformWalletFFIResult::err( - PlatformWalletFFIResultCode::ErrorInvalidParameter, - format!("Invalid seed length: expected 64 bytes, got {seed_len}"), - ); - } - - let seed_slice = unsafe { std::slice::from_raw_parts(seed_bytes, seed_len) }; - - // Convert to fixed-size array - let mut seed_array = [0u8; 64]; - seed_array.copy_from_slice(seed_slice); - - let wallet = unwrap_result_or_return!(key_wallet::Wallet::from_seed_bytes( - seed_array, - network, - WalletAccountCreationOptions::None, - )); - - let platform_wallet = PlatformWalletInfo::from_wallet(&wallet, 0); - - // Store in handle storage - let handle = WALLET_INFO_STORAGE.insert(platform_wallet); - unsafe { *out_handle = handle }; - - PlatformWalletFFIResult::ok() -} - -/// Create a new PlatformWalletInfo from mnemonic. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_info_create_from_mnemonic( - network: FFINetwork, - mnemonic: *const c_char, - out_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(mnemonic); - check_ptr!(out_handle); - - let network: Network = network.into(); - - let mnemonic_str = - unwrap_result_or_return!(unsafe { std::ffi::CStr::from_ptr(mnemonic).to_str() }); - - let mnemonic_obj = unwrap_result_or_return!(mnemonic_str.parse::()); - - let wallet = unwrap_result_or_return!(key_wallet::Wallet::from_mnemonic( - mnemonic_obj, - network, - WalletAccountCreationOptions::None, - )); - - // Create PlatformWalletInfo from the wallet - let platform_wallet = PlatformWalletInfo::from_wallet(&wallet, 0); - - // Store in handle storage - let handle = WALLET_INFO_STORAGE.insert(platform_wallet); - unsafe { *out_handle = handle }; - - PlatformWalletFFIResult::ok() -} - -/// Get the identity manager -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_info_get_identity_manager( - wallet_handle: Handle, - out_handle: *mut Handle, -) -> PlatformWalletFFIResult { - check_ptr!(out_handle); - - let option = WALLET_INFO_STORAGE.with_item(wallet_handle, |wallet_info| { - wallet_info.identity_manager.clone() - }); - let manager = unwrap_option_or_return!(option); - let handle = IDENTITY_MANAGER_STORAGE.insert(manager); - unsafe { *out_handle = handle }; - PlatformWalletFFIResult::ok() -} - -/// Set the identity manager -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_info_set_identity_manager( - wallet_handle: Handle, - manager_handle: Handle, -) -> PlatformWalletFFIResult { - let manager_option = - IDENTITY_MANAGER_STORAGE.with_item(manager_handle, |manager| manager.clone()); - let manager = unwrap_option_or_return!(manager_option); - - let option = WALLET_INFO_STORAGE.with_item_mut(wallet_handle, |wallet_info| { - wallet_info.identity_manager = manager; - }); - unwrap_option_or_return!(option); - PlatformWalletFFIResult::ok() -} - -/// Destroy PlatformWalletInfo and free resources -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_info_destroy( - wallet_handle: Handle, -) -> PlatformWalletFFIResult { - if WALLET_INFO_STORAGE.remove(wallet_handle).is_some() { - PlatformWalletFFIResult::ok() - } else { - PlatformWalletFFIResult::err( - PlatformWalletFFIResultCode::ErrorInvalidHandle, - "Invalid wallet handle", - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_create_from_seed() { - unsafe { - let seed = [0u8; 64]; - let mut handle: Handle = NULL_HANDLE; - - let result = platform_wallet_info_create_from_seed( - FFINetwork::Testnet, - seed.as_ptr(), - seed.len(), - &mut handle, - ); - - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(handle, NULL_HANDLE); - - platform_wallet_info_destroy(handle); - } - } - - #[test] - fn test_create_from_mnemonic() { - unsafe { - let mnemonic = std::ffi::CString::new( - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" - ).unwrap(); - - let mut handle: Handle = NULL_HANDLE; - - let result = platform_wallet_info_create_from_mnemonic( - FFINetwork::Testnet, - mnemonic.as_ptr(), - &mut handle, - ); - - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(handle, NULL_HANDLE); - - platform_wallet_info_destroy(handle); - } - } - - #[test] - fn test_destroy_invalid_handle() { - unsafe { - let result = platform_wallet_info_destroy(9999); - assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorInvalidHandle); - } - } -} diff --git a/packages/rs-platform-wallet-ffi/src/types.rs b/packages/rs-platform-wallet-ffi/src/types.rs index e92b5366444..e3e68cf6bc3 100644 --- a/packages/rs-platform-wallet-ffi/src/types.rs +++ b/packages/rs-platform-wallet-ffi/src/types.rs @@ -75,24 +75,6 @@ pub unsafe fn write_identifier(ptr: *mut u8, id: &dpp::prelude::Identifier) { } } -/// Contact request structure -#[repr(C)] -pub struct ContactRequest { - /// 32-byte identifier (raw bytes, not a struct, to keep this - /// struct ABI-compatible with the C-side cbindgen view). - pub identity_id: [u8; 32], - pub label: *mut c_char, - pub timestamp: u64, -} - -/// Established contact structure -#[repr(C)] -pub struct EstablishedContact { - pub identity_id: [u8; 32], - pub label: *mut c_char, - pub established_at: u64, -} - /// Array wrapper for returning multiple identifiers. /// /// `items` points at a contiguous `[[u8; 32]; count]` buffer — flat diff --git a/packages/rs-platform-wallet-ffi/src/utils.rs b/packages/rs-platform-wallet-ffi/src/utils.rs index bf84c88170e..a86da273c9d 100644 --- a/packages/rs-platform-wallet-ffi/src/utils.rs +++ b/packages/rs-platform-wallet-ffi/src/utils.rs @@ -1,75 +1,6 @@ +use crate::check_ptr; use crate::error::*; -use crate::{check_ptr, unwrap_result_or_return}; -use std::os::raw::{c_char, c_uchar}; - -/// RAII guard that scrubs a `secp256k1::SecretKey`'s scalar on drop. `from_slice` -/// allocates a 32-byte scalar copy of the caller's private key, and `SecretKey` -/// has no `Drop` wipe of its own — so without this the copy would survive on the -/// stack after the call returns. Mirrors `WipingSecretKey` in -/// `rs-sdk-ffi/src/mnemonic_resolver_core_signer.rs`. -struct WipingSecretKey(dashcore::secp256k1::SecretKey); - -impl Drop for WipingSecretKey { - fn drop(&mut self) { - self.0.non_secure_erase(); - } -} - -/// Serialize any object to JSON bytes -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_serialize_to_json_bytes( - json_string: *const c_char, - out_bytes: *mut *mut c_uchar, - out_len: *mut usize, -) -> PlatformWalletFFIResult { - check_ptr!(json_string); - check_ptr!(out_bytes); - check_ptr!(out_len); - // Sentinel first: the UTF-8 check below is fallible, and - // `platform_wallet_bytes_free` reconstructs a `Vec` from any non-null - // pointer / non-zero length pair — a cleanup-on-error caller must never - // see stack garbage here. - unsafe { - *out_bytes = std::ptr::null_mut(); - *out_len = 0; - } - - let json_str = - unwrap_result_or_return!(unsafe { std::ffi::CStr::from_ptr(json_string).to_str() }); - - let bytes = json_str.as_bytes().to_vec(); - let len = bytes.len(); - let ptr = bytes.as_ptr() as *mut c_uchar; - std::mem::forget(bytes); - - unsafe { - *out_bytes = ptr; - *out_len = len; - } - - PlatformWalletFFIResult::ok() -} - -/// Deserialize JSON bytes to string -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_deserialize_from_json_bytes( - bytes: *const c_uchar, - len: usize, - out_json_string: *mut *mut c_char, -) -> PlatformWalletFFIResult { - check_ptr!(bytes); - check_ptr!(out_json_string); - // Null the out-pointer before the fallible UTF-8 / NUL checks below so - // an error return never leaves it holding stack garbage for a - // cleanup-on-error caller to `platform_wallet_string_free`. - unsafe { *out_json_string = std::ptr::null_mut() }; - - let data = unsafe { std::slice::from_raw_parts(bytes, len) }; - let s = unwrap_result_or_return!(std::str::from_utf8(data)); - let c_str = unwrap_result_or_return!(std::ffi::CString::new(s)); - unsafe { *out_json_string = c_str.into_raw() }; - PlatformWalletFFIResult::ok() -} +use std::os::raw::c_uchar; /// Free bytes allocated by FFI functions #[no_mangle] @@ -92,44 +23,6 @@ pub unsafe extern "C" fn platform_wallet_generate_random_identifier( PlatformWalletFFIResult::ok() } -/// Convert identifier (32 bytes pointed to by `id`) to base58 hex -/// string. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_identifier_to_hex( - id: *const u8, - out_hex: *mut *mut c_char, -) -> PlatformWalletFFIResult { - check_ptr!(id); - check_ptr!(out_hex); - - let identifier = unwrap_result_or_return!(unsafe { crate::types::read_identifier(id) }); - - let hex = identifier.to_string(dpp::platform_value::string_encoding::Encoding::Base58); - let c_str = unwrap_result_or_return!(std::ffi::CString::new(hex)); - unsafe { *out_hex = c_str.into_raw() }; - PlatformWalletFFIResult::ok() -} - -/// Convert base58 hex string to identifier (writes 32 bytes into -/// `out_id`). -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_identifier_from_hex( - hex: *const c_char, - out_id: *mut u8, -) -> PlatformWalletFFIResult { - check_ptr!(hex); - check_ptr!(out_id); - - let hex_str = unwrap_result_or_return!(unsafe { std::ffi::CStr::from_ptr(hex).to_str() }); - - let identifier = unwrap_result_or_return!(dpp::prelude::Identifier::from_string( - hex_str, - dpp::platform_value::string_encoding::Encoding::Base58, - )); - unsafe { crate::types::write_identifier(out_id, &identifier) }; - PlatformWalletFFIResult::ok() -} - /// Compute hash160 (RIPEMD160(SHA256(data))) of the input bytes. /// /// Exposed so the Swift side can stamp a 20-byte public-key hash onto @@ -170,57 +63,6 @@ pub unsafe extern "C" fn platform_wallet_hash160( 0 } -/// Compute the hash160 of the **compressed** secp256k1 public key for a -/// 32-byte ECDSA private scalar — i.e. the on-chain `ECDSA_SECP256K1` -/// public-key hash that scalar would own. -/// -/// Lets the Swift Keychain layer cheaply re-verify, after re-deriving an -/// identity key's private scalar, that the scalar actually reproduces the -/// stored on-chain key's hash before persisting it — a cross-FFI guard -/// against the Rust-side derivation path and the Swift-side re-derivation -/// ever drifting (compute it here rather than pull a secp256k1 + RIPEMD-160 -/// stack into Swift). Compression is network-independent, so no network -/// parameter is needed. -/// -/// # Parameters -/// - `private_key`: pointer to the 32-byte ECDSA secret scalar. -/// - `out_hash`: caller-allocated 20-byte buffer; the hash160 of the -/// compressed pubkey is written here on success. -/// -/// Returns 0 on success, -1 on null pointer or an invalid (out-of-range) -/// secret scalar. -/// -/// # Safety -/// - `private_key` must be a valid `[u8; 32]` buffer for the call. -/// - `out_hash` must be a valid `[u8; 20]` writable buffer. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_pubkey_hash_from_private_key( - private_key: *const u8, - out_hash: *mut u8, -) -> i32 { - if private_key.is_null() || out_hash.is_null() { - return -1; - } - use dashcore::hashes::Hash; - use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; - - let sk_bytes = std::slice::from_raw_parts(private_key, 32); - let secp = Secp256k1::new(); - // `WipingSecretKey` scrubs the `from_slice`-allocated scalar copy on every - // exit path (the success return below, the `Err` early return, and any - // panic) — the caller's `private_key` bytes are theirs to manage, but this - // copy must not linger. - let secret_key = match SecretKey::from_slice(sk_bytes) { - Ok(sk) => WipingSecretKey(sk), - Err(_) => return -1, - }; - let pubkey = PublicKey::from_secret_key(&secp, &secret_key.0).serialize(); - let hash = dashcore::hashes::hash160::Hash::hash(&pubkey); - let h: [u8; 20] = hash.to_byte_array(); - std::ptr::copy_nonoverlapping(h.as_ptr(), out_hash, 20); - 0 -} - #[cfg(test)] mod tests { use super::*; @@ -260,83 +102,6 @@ mod tests { assert_eq!(rc, -1); } - #[test] - fn test_pubkey_hash_from_private_key_matches_canonical_derivation() { - use dashcore::hashes::Hash; - use dashcore::secp256k1::{PublicKey, Secp256k1, SecretKey}; - - // A fixed, in-range scalar. - let mut scalar = [0u8; 32]; - scalar[31] = 1; - - let mut out = [0u8; 20]; - let rc = unsafe { - platform_wallet_pubkey_hash_from_private_key(scalar.as_ptr(), out.as_mut_ptr()) - }; - assert_eq!(rc, 0); - - let secp = Secp256k1::new(); - let sk = SecretKey::from_slice(&scalar).expect("in-range scalar"); - let pubkey = PublicKey::from_secret_key(&secp, &sk).serialize(); - let expected: [u8; 20] = dashcore::hashes::hash160::Hash::hash(&pubkey).to_byte_array(); - assert_eq!(out, expected); - } - - #[test] - fn test_pubkey_hash_from_private_key_rejects_null() { - let mut out = [0u8; 20]; - let scalar = [1u8; 32]; - assert_eq!( - unsafe { - platform_wallet_pubkey_hash_from_private_key(std::ptr::null(), out.as_mut_ptr()) - }, - -1 - ); - assert_eq!( - unsafe { - platform_wallet_pubkey_hash_from_private_key(scalar.as_ptr(), std::ptr::null_mut()) - }, - -1 - ); - } - - #[test] - fn test_pubkey_hash_from_private_key_rejects_invalid_scalar() { - // All-zero scalar is out of secp256k1's valid range. - let scalar = [0u8; 32]; - let mut out = [0u8; 20]; - let rc = unsafe { - platform_wallet_pubkey_hash_from_private_key(scalar.as_ptr(), out.as_mut_ptr()) - }; - assert_eq!(rc, -1); - } - - #[test] - fn test_serialize_deserialize_json_bytes() { - unsafe { - let json = std::ffi::CString::new(r#"{"test":"value"}"#).unwrap(); - let mut bytes: *mut c_uchar = std::ptr::null_mut(); - let mut len: usize = 0; - - let result = - platform_wallet_serialize_to_json_bytes(json.as_ptr(), &mut bytes, &mut len); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert!(!bytes.is_null()); - assert!(len > 0); - - let mut json_out: *mut c_char = std::ptr::null_mut(); - let result = platform_wallet_deserialize_from_json_bytes(bytes, len, &mut json_out); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert!(!json_out.is_null()); - - let json_str = std::ffi::CStr::from_ptr(json_out).to_str().unwrap(); - assert_eq!(json_str, r#"{"test":"value"}"#); - - platform_wallet_bytes_free(bytes, len); - crate::platform_wallet_string_free(json_out); - } - } - #[test] fn test_generate_random_identifier() { unsafe { @@ -346,25 +111,4 @@ mod tests { assert_ne!(id, [0u8; 32]); } } - - #[test] - fn test_identifier_to_from_hex() { - unsafe { - let mut id = [0u8; 32]; - platform_wallet_generate_random_identifier(id.as_mut_ptr()); - - let mut hex: *mut c_char = std::ptr::null_mut(); - let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut hex); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert!(!hex.is_null()); - - let mut id2 = [0u8; 32]; - let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - assert_eq!(id, id2); - - crate::platform_wallet_string_free(hex); - } - } } diff --git a/packages/rs-platform-wallet-ffi/src/wallet.rs b/packages/rs-platform-wallet-ffi/src/wallet.rs index b27125b65da..f627ee36649 100644 --- a/packages/rs-platform-wallet-ffi/src/wallet.rs +++ b/packages/rs-platform-wallet-ffi/src/wallet.rs @@ -5,19 +5,6 @@ use crate::handle::*; use crate::runtime::runtime; use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return}; -/// Get the wallet ID (32 bytes). -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_get_id( - handle: Handle, - out_wallet_id: *mut [u8; 32], -) -> PlatformWalletFFIResult { - check_ptr!(out_wallet_id); - - let option = PLATFORM_WALLET_STORAGE.with_item(handle, |wallet| wallet.wallet_id()); - *out_wallet_id = unwrap_option_or_return!(option); - PlatformWalletFFIResult::ok() -} - /// Get lock-free balance (spendable, unconfirmed, immature, locked). /// /// These are atomic reads — no lock contention. @@ -195,67 +182,6 @@ pub unsafe extern "C" fn platform_wallet_manager_free_account_balances( } } -/// Aggregate the wallet's masternodes from its retained provider special -/// transactions (ProRegTx / ProUpServTx / ProUpRegTx / ProUpRevTx), -/// grouped by proTxHash. Returns an array of -/// [`MasternodeEntryFFI`](crate::core_wallet_types::MasternodeEntryFFI), -/// one per masternode, sorted by registration order. The caller owns the -/// array and must free it via -/// [`platform_wallet_manager_free_masternodes`]. -/// -/// The record source (rust-dashcore #876 provider-payload retention) is -/// populated in every feature configuration; see -/// `PlatformWalletManager::wallet_masternodes_blocking`, which also resolves -/// status and operator / platform key ownership — this function only -/// marshals. `out_*` are set to null / 0 when the wallet has no masternodes -/// or isn't found. -/// -/// Reads the wallet manager lock via `blocking_read` — must not be called -/// from within a tokio async context. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_manager_list_masternodes( - manager_handle: Handle, - wallet_id: *const u8, - out_entries: *mut *const crate::core_wallet_types::MasternodeEntryFFI, - out_count: *mut usize, -) -> PlatformWalletFFIResult { - check_ptr!(wallet_id); - check_ptr!(out_entries); - check_ptr!(out_count); - // Initialise outputs immediately so the invalid-handle / unknown-wallet - // early returns below leave the caller looking at valid empty state - // rather than uninitialised / stale pointers. - *out_entries = std::ptr::null(); - *out_count = 0; - - let wid: [u8; 32] = std::ptr::read(wallet_id as *const [u8; 32]); - - let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { - manager.wallet_masternodes_blocking(&wid) - }); - // Outer Option: handle resolved. Inner Option: wallet found. - let inner = unwrap_option_or_return!(option); - let masternodes = unwrap_option_or_return!(inner); - - let entries: Vec = masternodes - .records - .iter() - .map(|mn| crate::core_wallet_types::masternode_entry_ffi(mn, masternodes.network)) - .collect(); - let count = entries.len(); - - if count == 0 { - *out_entries = std::ptr::null(); - *out_count = 0; - return PlatformWalletFFIResult::ok(); - } - - let boxed = entries.into_boxed_slice(); - *out_entries = Box::into_raw(boxed) as *const _; - *out_count = count; - PlatformWalletFFIResult::ok() -} - /// Version 2 of [`platform_wallet_manager_list_masternodes`]. It returns /// [`MasternodeEntryV2FFI`](crate::core_wallet_types::MasternodeEntryV2FFI), /// which adds record provenance and an optional tracked-node label without @@ -295,7 +221,9 @@ pub unsafe extern "C" fn platform_wallet_manager_list_masternodes_v2( PlatformWalletFFIResult::ok() } -unsafe fn free_masternode_entry_strings(entry: &crate::core_wallet_types::MasternodeEntryFFI) { +pub(crate) unsafe fn free_masternode_entry_strings( + entry: &crate::core_wallet_types::MasternodeEntryFFI, +) { for ptr in [ entry.service_address, entry.owner_address, @@ -310,23 +238,6 @@ unsafe fn free_masternode_entry_strings(entry: &crate::core_wallet_types::Master } } -/// Free an array returned by [`platform_wallet_manager_list_masternodes`], -/// including each entry's heap C strings. -#[no_mangle] -pub unsafe extern "C" fn platform_wallet_manager_free_masternodes( - entries: *mut crate::core_wallet_types::MasternodeEntryFFI, - count: usize, -) { - if entries.is_null() || count == 0 { - return; - } - let slice = std::slice::from_raw_parts_mut(entries, count); - for entry in slice.iter() { - free_masternode_entry_strings(entry); - } - let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(entries, count)); -} - /// Free an array returned by /// [`platform_wallet_manager_list_masternodes_v2`] or any tracked-masternode /// API returning `MasternodeEntryV2FFI`. diff --git a/packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs b/packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs index c49be7de1b7..15af21db875 100644 --- a/packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs +++ b/packages/rs-platform-wallet-ffi/src/wallet_restore_types.rs @@ -672,46 +672,6 @@ pub struct WalletRestoreEntryFFI { pub last_applied_chain_lock_bytes_len: usize, } -/// Every field named explicitly so that adding a field to this ABI struct -/// is a compile error here rather than a silently-widened `mem::zeroed()` -/// in test code: the all-zero bit pattern is valid for today's pointers, -/// integers and `FFINetwork`, but stops being valid the moment a field -/// with a validity niche (a `NonNull`, a reference, a gap-ful enum) joins -/// the struct — and that regression would otherwise be silent UB. -impl Default for WalletRestoreEntryFFI { - fn default() -> Self { - Self { - wallet_id: [0u8; 32], - network: crate::types::FFINetwork::Testnet, - accounts: std::ptr::null(), - accounts_count: 0, - platform_address_balances: std::ptr::null(), - platform_address_balances_count: 0, - platform_sync_height: 0, - platform_sync_timestamp: 0, - platform_last_known_recent_block: 0, - identities: std::ptr::null(), - identities_count: 0, - birth_height: 0, - synced_height: 0, - last_processed_height: 0, - last_synced: 0, - utxos: std::ptr::null(), - utxos_count: 0, - tracked_asset_locks: std::ptr::null(), - tracked_asset_locks_count: 0, - unresolved_asset_lock_tx_records: std::ptr::null(), - unresolved_asset_lock_tx_records_count: 0, - provider_special_txs: std::ptr::null(), - provider_special_txs_count: 0, - core_address_pools: std::ptr::null(), - core_address_pools_count: 0, - last_applied_chain_lock_bytes: std::ptr::null(), - last_applied_chain_lock_bytes_len: 0, - } - } -} - // SAFETY: Pointers are Swift-owned and lifetime-scoped to the callback. // Sending the struct across threads without being used is fine; any // use must happen within the callback window. diff --git a/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs b/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs index 3936adf9a5d..fd8a1400724 100644 --- a/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs +++ b/packages/rs-platform-wallet-ffi/tests/comprehensive_tests.rs @@ -254,60 +254,6 @@ fn test_mixed_contact_scenario() { } } -#[test] -fn test_identity_manager_with_multiple_identities() { - unsafe { - use dpp::identity::accessors::IdentityGettersV0; - - // Create identity manager - let mut manager_handle: Handle = NULL_HANDLE; - let result = identity_manager_create(&mut manager_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Add Alice, Bob, and Carol - let alice = identities::alice(); - let bob = identities::bob(); - let carol = identities::carol(); - - let alice_id = alice.identity.id(); - let _bob_id = bob.identity.id(); - let _carol_id = carol.identity.id(); - - let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice); - let bob_handle = MANAGED_IDENTITY_STORAGE.insert(bob); - let carol_handle = MANAGED_IDENTITY_STORAGE.insert(carol); - - identity_manager_add_identity(manager_handle, alice_handle); - identity_manager_add_identity(manager_handle, bob_handle); - identity_manager_add_identity(manager_handle, carol_handle); - - // Verify count - let mut count: usize = 0; - let result = identity_manager_get_identity_count(manager_handle, &mut count); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(count, 3); - - // Get all identity IDs - let mut array = IdentifierArray { - items: std::ptr::null_mut(), - count: 0, - }; - let result = identity_manager_get_all_identity_ids(manager_handle, &mut array); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(array.count, 3); - - // Primary-identity FFI was dropped along with the field; - // the test_data fixture's `alice_id` is no longer relevant - // here. - let alice_id_bytes: [u8; 32] = alice_id.to_buffer(); - let _ = alice_id_bytes; - - // Cleanup - platform_wallet_identifier_array_free(&mut array); - identity_manager_destroy(manager_handle); - } -} - #[test] fn test_managed_identity_label_operations() { // `ManagedIdentity` no longer carries a `label` field — the FFI @@ -447,43 +393,6 @@ fn test_contact_request_not_found() { } } -#[test] -fn test_identifier_operations() { - unsafe { - // Generate random identifier - let mut id = [0u8; 32]; - let result = platform_wallet_generate_random_identifier(id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - // Should not be all zeros - assert_ne!(id, [0u8; 32]); - - // Convert to string (actually Base58, despite function name) - let mut id_string: *mut std::os::raw::c_char = std::ptr::null_mut(); - let result = platform_wallet_identifier_to_hex(id.as_ptr(), &mut id_string); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert!(!id_string.is_null()); - - let id_str = std::ffi::CStr::from_ptr(id_string).to_str().unwrap(); - // Base58-encoded 32-byte identifier is 43-44 chars (variable length encoding) - assert!( - id_str.len() == 43 || id_str.len() == 44, - "Expected Base58 identifier length 43-44, got {}", - id_str.len() - ); - - // Convert back from string - let mut id2 = [0u8; 32]; - let result = platform_wallet_identifier_from_hex(id_string, id2.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Should match original - assert_eq!(id, id2); - - // Cleanup - platform_wallet_string_free(id_string); - } -} - #[test] fn test_memory_lifecycle() { unsafe { @@ -638,124 +547,12 @@ fn test_get_established_contact_and_fields() { // Get contact ID let mut retrieved_id = [0u8; 32]; - let result = established_contact_get_contact_id(contact_handle, retrieved_id.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(retrieved_id, bob_id_bytes); - - // Cleanup - established_contact_destroy(contact_handle); - managed_identity_destroy(alice_handle); - } -} - -#[test] -fn test_established_contact_outgoing_and_incoming_requests() { - unsafe { - use dpp::identity::accessors::IdentityGettersV0; - - let (alice, _contacts) = test_data::scenarios::alice_with_established_contacts(); - let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice.clone()); - - let bob_id = test_data::identities::bob().identity.id(); - let bob_id_bytes: [u8; 32] = bob_id.to_buffer(); - - let mut contact_handle: Handle = NULL_HANDLE; - - managed_identity_get_established_contact( - alice_handle, - bob_id_bytes.as_ptr(), - &mut contact_handle, - ); - - // Get outgoing request - let mut outgoing_handle: Handle = NULL_HANDLE; - let result = established_contact_get_outgoing_request(contact_handle, &mut outgoing_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(outgoing_handle, NULL_HANDLE); - - // Get incoming request - let mut incoming_handle: Handle = NULL_HANDLE; - let result = established_contact_get_incoming_request(contact_handle, &mut incoming_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(incoming_handle, NULL_HANDLE); - - // Verify the requests have correct sender/recipient - let alice_id = alice.identity.id(); - let alice_id_bytes: [u8; 32] = alice_id.to_buffer(); - let mut sender_id = [0u8; 32]; - let mut recipient_id = [0u8; 32]; - - // Outgoing: from alice to bob - contact_request_get_sender_id(outgoing_handle, sender_id.as_mut_ptr()); - contact_request_get_recipient_id(outgoing_handle, recipient_id.as_mut_ptr()); - assert_eq!(sender_id, alice_id_bytes); - assert_eq!(recipient_id, bob_id_bytes); - - // Incoming: from bob to alice - contact_request_get_sender_id(incoming_handle, sender_id.as_mut_ptr()); - contact_request_get_recipient_id(incoming_handle, recipient_id.as_mut_ptr()); - assert_eq!(sender_id, bob_id_bytes); - assert_eq!(recipient_id, alice_id_bytes); - - // Cleanup - contact_request_destroy(outgoing_handle); - contact_request_destroy(incoming_handle); - established_contact_destroy(contact_handle); - managed_identity_destroy(alice_handle); - } -} - -#[test] -fn test_established_contact_request_fields() { - unsafe { - use dpp::identity::accessors::IdentityGettersV0; - - let (alice, _contacts) = test_data::scenarios::alice_with_established_contacts(); - let alice_handle = MANAGED_IDENTITY_STORAGE.insert(alice.clone()); - - let bob_id = test_data::identities::bob().identity.id(); - let bob_id_bytes: [u8; 32] = bob_id.to_buffer(); - - let mut contact_handle: Handle = NULL_HANDLE; - - managed_identity_get_established_contact( - alice_handle, - bob_id_bytes.as_ptr(), - &mut contact_handle, - ); - - // Get outgoing request and verify all fields - let mut outgoing_handle: Handle = NULL_HANDLE; - established_contact_get_outgoing_request(contact_handle, &mut outgoing_handle); - - let mut sender_key_idx: u32 = 0; - let mut recipient_key_idx: u32 = 0; - let mut account_ref: u32 = 0; - let mut created_at: u64 = 0; - - contact_request_get_sender_key_index(outgoing_handle, &mut sender_key_idx); - contact_request_get_recipient_key_index(outgoing_handle, &mut recipient_key_idx); - contact_request_get_account_reference(outgoing_handle, &mut account_ref); - contact_request_get_created_at(outgoing_handle, &mut created_at); - - // The test data should have specific values - assert_eq!(sender_key_idx, 0); - assert_eq!(recipient_key_idx, 1); - assert_eq!(account_ref, 0); - assert!(created_at > 0); - - // Get encrypted public key - let mut bytes_ptr: *mut std::os::raw::c_uchar = std::ptr::null_mut(); - let mut len: usize = 0; let result = - contact_request_get_encrypted_public_key(outgoing_handle, &mut bytes_ptr, &mut len); + established_contact_get_contact_identity_id(contact_handle, retrieved_id.as_mut_ptr()); assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(len, 96); // Standard encrypted key length - assert!(!bytes_ptr.is_null()); + assert_eq!(retrieved_id, bob_id_bytes); // Cleanup - platform_wallet_bytes_free(bytes_ptr, len); - contact_request_destroy(outgoing_handle); established_contact_destroy(contact_handle); managed_identity_destroy(alice_handle); } @@ -830,12 +627,18 @@ fn test_multiple_established_contacts() { // Verify Bob's contact ID let mut retrieved_bob_id = [0u8; 32]; - established_contact_get_contact_id(bob_contact_handle, retrieved_bob_id.as_mut_ptr()); + established_contact_get_contact_identity_id( + bob_contact_handle, + retrieved_bob_id.as_mut_ptr(), + ); assert_eq!(retrieved_bob_id, bob_id_bytes); // Verify Carol's contact ID let mut retrieved_carol_id = [0u8; 32]; - established_contact_get_contact_id(carol_contact_handle, retrieved_carol_id.as_mut_ptr()); + established_contact_get_contact_identity_id( + carol_contact_handle, + retrieved_carol_id.as_mut_ptr(), + ); assert_eq!(retrieved_carol_id, carol_id_bytes); // Cleanup diff --git a/packages/rs-platform-wallet-ffi/tests/integration_tests.rs b/packages/rs-platform-wallet-ffi/tests/integration_tests.rs index 45180c51c69..c8d1dd8a43c 100644 --- a/packages/rs-platform-wallet-ffi/tests/integration_tests.rs +++ b/packages/rs-platform-wallet-ffi/tests/integration_tests.rs @@ -1,116 +1,6 @@ -use dpp::identity::accessors::IdentityGettersV0; use platform_wallet_ffi::*; use std::ffi::CString; -#[test] -fn test_library_init_and_version() { - platform_wallet_ffi_init(); - - let version = platform_wallet_ffi_version(); - assert!(!version.is_null()); - - let version_str = unsafe { std::ffi::CStr::from_ptr(version).to_str().unwrap() }; - assert!(!version_str.is_empty()); -} - -#[test] -fn test_wallet_creation_and_destruction() { - unsafe { - let seed = [0u8; 64]; - let mut handle: Handle = NULL_HANDLE; - - let result = platform_wallet_info_create_from_seed( - Network::Testnet.into(), - seed.as_ptr(), - seed.len(), - &mut handle, - ); - - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(handle, NULL_HANDLE); - - let result = platform_wallet_info_destroy(handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Double destroy should fail - let result = platform_wallet_info_destroy(handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorInvalidHandle); - } -} - -#[test] -fn test_wallet_from_mnemonic() { - unsafe { - let mnemonic = CString::new( - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" - ).unwrap(); - - let mut handle: Handle = NULL_HANDLE; - - let result = platform_wallet_info_create_from_mnemonic( - Network::Testnet.into(), - mnemonic.as_ptr(), - &mut handle, - ); - - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(handle, NULL_HANDLE); - - platform_wallet_info_destroy(handle); - } -} - -#[test] -#[ignore] // Stubbed - requires PlatformWalletInfo -fn test_identity_manager_workflow() { - unsafe { - // Create identity manager - let mut manager_handle: Handle = NULL_HANDLE; - - let result = identity_manager_create(&mut manager_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Check initial count - let mut count: usize = 0; - let result = identity_manager_get_identity_count(manager_handle, &mut count); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(count, 0); - - // Create a mock identity for testing - let identity = dpp::tests::fixtures::get_identity_fixture(0).unwrap(); - let identity_id = identity.id(); - let managed = platform_wallet::ManagedIdentity::new(identity, 0); - let identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed); - - let result = identity_manager_add_identity(manager_handle, identity_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Check count increased - let result = identity_manager_get_identity_count(manager_handle, &mut count); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(count, 1); - - // Primary-identity FFI was dropped along with the field — - // selection moved to the UI layer. - let id_bytes: [u8; 32] = identity_id.to_buffer(); - let _ = id_bytes; - - // Get all identity IDs - let mut array = IdentifierArray { - items: std::ptr::null_mut(), - count: 0, - }; - let result = identity_manager_get_all_identity_ids(manager_handle, &mut array); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_eq!(array.count, 1); - - platform_wallet_identifier_array_free(&mut array); - - // Cleanup - identity_manager_destroy(manager_handle); - } -} - #[test] #[ignore] // Stubbed - requires PlatformWalletInfo fn test_managed_identity_operations() { @@ -170,61 +60,6 @@ fn test_managed_identity_operations() { } } -#[test] -#[ignore] // TODO: Requires serde support on PlatformWalletInfo -fn test_serialization() { - unsafe { - let seed = [0u8; 64]; - let mut handle: Handle = NULL_HANDLE; - - platform_wallet_info_create_from_seed( - Network::Testnet.into(), - seed.as_ptr(), - seed.len(), - &mut handle, - ); - - // Serialize to JSON - function not yet implemented - // let mut json_ptr: *mut std::os::raw::c_char = std::ptr::null_mut(); - // let result = platform_wallet_info_to_json(handle, &mut json_ptr); - // assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - // assert!(!json_ptr.is_null()); - - // let json_str = unsafe { std::ffi::CStr::from_ptr(json_ptr).to_str().unwrap() }; - // assert!(!json_str.is_empty()); - // assert!(json_str.contains("wallet_info")); - - // platform_wallet_string_free(json_ptr); - platform_wallet_info_destroy(handle); - } -} - -#[test] -fn test_utils_identifier_operations() { - unsafe { - // Generate random identifier - let mut id1 = [0u8; 32]; - let result = platform_wallet_generate_random_identifier(id1.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Convert to hex - let mut hex: *mut std::os::raw::c_char = std::ptr::null_mut(); - let result = platform_wallet_identifier_to_hex(id1.as_ptr(), &mut hex); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert!(!hex.is_null()); - - // Convert back from hex - let mut id2 = [0u8; 32]; - let result = platform_wallet_identifier_from_hex(hex, id2.as_mut_ptr()); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Should match - assert_eq!(id1, id2); - - platform_wallet_string_free(hex); - } -} - #[test] fn test_error_handling() { unsafe { @@ -237,77 +72,6 @@ fn test_error_handling() { // Result carries a diagnostic message on the error path. assert!(!result.message.is_null()); - - // Try to create wallet with null pointer - let result = platform_wallet_info_create_from_seed( - Network::Testnet.into(), - std::ptr::null(), - 0, - std::ptr::null_mut(), - ); - assert_eq!(result.code, PlatformWalletFFIResultCode::ErrorNullPointer); - } -} - -#[test] -#[ignore] // Stubbed - requires PlatformWalletInfo -fn test_full_workflow() { - unsafe { - // Initialize - platform_wallet_ffi_init(); - - // Create wallet from mnemonic - let mnemonic = CString::new( - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" - ).unwrap(); - - let mut wallet_handle: Handle = NULL_HANDLE; - let result = platform_wallet_info_create_from_mnemonic( - Network::Testnet.into(), - mnemonic.as_ptr(), - &mut wallet_handle, - ); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Create identity manager - let mut manager_handle: Handle = NULL_HANDLE; - let result = identity_manager_create(&mut manager_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Create identity - let identity = dpp::tests::fixtures::get_identity_fixture(0).unwrap(); - let managed = platform_wallet::ManagedIdentity::new(identity, 0); - let identity_id = managed.identity.id(); - let identity_handle = MANAGED_IDENTITY_STORAGE.insert(managed); - - // Label setter is now a no-op stub (ManagedIdentity dropped - // its label field) — kept here only to verify the call still - // links and returns Success. - let label = CString::new("My Primary Identity").unwrap(); - managed_identity_set_label(identity_handle, label.as_ptr()); - - // Add identity to manager - identity_manager_add_identity(manager_handle, identity_handle); - - // Primary-identity FFI was dropped along with the field. - let id_bytes: [u8; 32] = identity_id.to_buffer(); - let _ = id_bytes; - - // Set identity manager on wallet - let result = platform_wallet_info_set_identity_manager(wallet_handle, manager_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - - // Get identity manager back - let mut retrieved_manager_handle: Handle = NULL_HANDLE; - let result = - platform_wallet_info_get_identity_manager(wallet_handle, &mut retrieved_manager_handle); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); - assert_ne!(retrieved_manager_handle, NULL_HANDLE); - - // Cleanup - identity_manager_destroy(retrieved_manager_handle); - identity_manager_destroy(manager_handle); - platform_wallet_info_destroy(wallet_handle); } } @@ -326,17 +90,11 @@ fn test_get_dashpay_profile_unmanaged_identity_reports_not_found() { }; unsafe { - let seed = [0u8; 64]; - let mut wallet_handle: Handle = NULL_HANDLE; - let result = platform_wallet_info_create_from_seed( - Network::Testnet.into(), - seed.as_ptr(), - seed.len(), - &mut wallet_handle, - ); - assert_eq!(result.code, PlatformWalletFFIResultCode::Success); + // A handle the wallet storage does not know; the profile read resolves + // through `PLATFORM_WALLET_STORAGE`, so this is the same `Option::None` + // arm an unmanaged identity takes. + let wallet_handle: Handle = 9999; - // A fresh wallet manages no identities, so any id is "unknown". let unmanaged_id = [0x11u8; 32]; let mut profile = DashPayProfileFFI::empty(); let mut has_profile = true; @@ -359,6 +117,5 @@ fn test_get_dashpay_profile_unmanaged_identity_reports_not_found() { dashpay_profile_ffi_free(&mut profile as *mut DashPayProfileFFI); platform_wallet_ffi::error::platform_wallet_ffi_result_free(&mut result); - platform_wallet_info_destroy(wallet_handle); } } diff --git a/packages/rs-sdk-ffi/src/document/create.rs b/packages/rs-sdk-ffi/src/document/create.rs index 6aa744398d6..f79e664960a 100644 --- a/packages/rs-sdk-ffi/src/document/create.rs +++ b/packages/rs-sdk-ffi/src/document/create.rs @@ -1,10 +1,10 @@ //! Document creation operations use crate::sdk::SDKWrapper; -use crate::types::{DashSDKResultDataType, DocumentHandle, SDKHandle}; +use crate::types::{DocumentHandle, SDKHandle}; use crate::{DashSDKError, DashSDKErrorCode, DashSDKResult, FFIError}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; -use dash_sdk::dpp::document::{Document, DocumentV0}; +use dash_sdk::dpp::document::Document; // identity getters not used here use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::platform_value::Value; @@ -36,23 +36,6 @@ pub struct DashSDKDocumentCreateParams { pub properties_json: *const c_char, } -/// Document handle creation parameters -#[repr(C)] -pub struct DashSDKDocumentHandleParams { - /// Document ID (base58 encoded) - pub id: *const c_char, - /// Data contract ID (base58 encoded) - pub data_contract_id: *const c_char, - /// Document type name - pub document_type: *const c_char, - /// Owner identity ID (base58 encoded) - pub owner_identity_id: *const c_char, - /// JSON string of document properties - pub properties_json: *const c_char, - /// Optional revision number (0 means no revision) - pub revision: u64, -} - /// Create a new document /// /// # Safety @@ -219,153 +202,6 @@ pub unsafe extern "C" fn dash_sdk_document_create_result_free( } } -/// Create a document handle from parameters -/// This creates a Document object directly without broadcasting to the network -/// -/// # Safety -/// - `params` must be a valid, non-null pointer to a `DashSDKDocumentHandleParams` structure. -/// - All C string fields inside `params` must be valid pointers to NUL-terminated strings and remain valid -/// for the duration of the call. -/// - On success, the returned `DashSDKResult` contains a heap-allocated `DocumentHandle` which must be freed by the caller -/// using the appropriate SDK destroy function. -/// - Passing dangling or invalid pointers results in undefined behavior. -#[no_mangle] -pub unsafe extern "C" fn dash_sdk_document_make_handle( - params: *const DashSDKDocumentHandleParams, -) -> DashSDKResult { - // Validate input - if params.is_null() { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "Parameters are null".to_string(), - )); - } - - let params = &*params; - - // Validate required fields - if params.id.is_null() - || params.data_contract_id.is_null() - || params.document_type.is_null() - || params.owner_identity_id.is_null() - || params.properties_json.is_null() - { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "One or more required parameters is null".to_string(), - )); - } - - // Parse document ID - let id_str = match CStr::from_ptr(params.id).to_str() { - Ok(s) => s, - Err(e) => return DashSDKResult::error(FFIError::from(e).into()), - }; - - let document_id = match Identifier::from_string(id_str, Encoding::Base58) { - Ok(id) => id, - Err(e) => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - format!("Invalid document ID: {}", e), - )) - } - }; - - // Parse owner identity ID - let owner_id_str = match CStr::from_ptr(params.owner_identity_id).to_str() { - Ok(s) => s, - Err(e) => return DashSDKResult::error(FFIError::from(e).into()), - }; - - let owner_id = match Identifier::from_string(owner_id_str, Encoding::Base58) { - Ok(id) => id, - Err(e) => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - format!("Invalid owner identity ID: {}", e), - )) - } - }; - - // Parse properties JSON - let properties_json_str = match CStr::from_ptr(params.properties_json).to_str() { - Ok(s) => s, - Err(e) => return DashSDKResult::error(FFIError::from(e).into()), - }; - - // Parse JSON into Value - let properties_value: Value = match serde_json::from_str(properties_json_str) { - Ok(val) => val, - Err(e) => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - format!("Invalid JSON properties: {}", e), - )) - } - }; - - // Convert Value to BTreeMap - let properties = match properties_value { - Value::Map(map) => { - let mut btree_map = BTreeMap::new(); - for (key, value) in map { - match key { - Value::Text(key_str) => { - btree_map.insert(key_str, value); - } - _ => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "Property keys must be strings".to_string(), - )) - } - } - } - btree_map - } - _ => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "Properties must be a JSON object".to_string(), - )) - } - }; - - // Handle optional revision - let revision = if params.revision == 0 { - None - } else { - Some(params.revision) - }; - - // Create the document - let document = Document::V0(DocumentV0 { - contract_version: None, - id: document_id, - owner_id, - properties, - revision, - created_at: None, - updated_at: None, - transferred_at: None, - created_at_block_height: None, - updated_at_block_height: None, - transferred_at_block_height: None, - created_at_core_block_height: None, - updated_at_core_block_height: None, - transferred_at_core_block_height: None, - creator_id: None, - }); - - // Box and return as handle - let handle = Box::into_raw(Box::new(document)) as *mut DocumentHandle; - DashSDKResult::success_handle( - handle as *mut std::os::raw::c_void, - DashSDKResultDataType::ResultDocumentHandle, - ) -} - #[cfg(test)] mod tests { use super::*; diff --git a/packages/rs-sdk-ffi/src/token/config_update.rs b/packages/rs-sdk-ffi/src/token/config_update.rs deleted file mode 100644 index 4e173a784e6..00000000000 --- a/packages/rs-sdk-ffi/src/token/config_update.rs +++ /dev/null @@ -1,697 +0,0 @@ -//! Token configuration update operations - -use super::types::{DashSDKTokenConfigUpdateParams, DashSDKTokenConfigUpdateType}; -use super::utils::{ - convert_state_transition_creation_options, extract_user_fee_increase, - parse_identifier_from_bytes, parse_optional_note, validate_contract_params, -}; -use crate::sdk::SDKWrapper; -use crate::types::{ - DashSDKPutSettings, DashSDKStateTransitionCreationOptions, SDKHandle, SignerHandle, -}; -use crate::{DashSDKError, DashSDKErrorCode, DashSDKResult, FFIError}; -use dash_sdk::dpp::balances::credits::TokenAmount; -use dash_sdk::dpp::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; -use dash_sdk::dpp::data_contract::TokenContractPosition; -use dash_sdk::dpp::platform_value::string_encoding::Encoding; -use dash_sdk::dpp::prelude::Identifier; -use dash_sdk::platform::tokens::builders::config_update::TokenConfigUpdateTransitionBuilder; -use dash_sdk::platform::tokens::transitions::ConfigUpdateResult; -use dash_sdk::platform::IdentityPublicKey; -use std::ffi::CStr; -use std::sync::Arc; - -/// Update token configuration and wait for confirmation -/// -/// # Safety -/// - `sdk_handle` must be a valid pointer to an initialized SDKHandle. -/// - `transition_owner_id` must point to at least 32 readable bytes. -/// - `params`, `identity_public_key_handle`, `signer_handle` must be valid pointers to initialized structures. -/// - Optional pointers (`put_settings`, `state_transition_creation_options`) may be null; when non-null they must be valid. -/// - Caller must free any returned heap memory in the result using SDK free routines. -#[no_mangle] -pub unsafe extern "C" fn dash_sdk_token_update_contract_token_configuration( - sdk_handle: *mut SDKHandle, - transition_owner_id: *const u8, - params: *const DashSDKTokenConfigUpdateParams, - identity_public_key_handle: *const crate::types::IdentityPublicKeyHandle, - signer_handle: *const SignerHandle, - put_settings: *const DashSDKPutSettings, - state_transition_creation_options: *const DashSDKStateTransitionCreationOptions, -) -> DashSDKResult { - // Validate parameters - if sdk_handle.is_null() - || transition_owner_id.is_null() - || params.is_null() - || identity_public_key_handle.is_null() - || signer_handle.is_null() - { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "One or more required parameters is null".to_string(), - )); - } - - // SAFETY: We've verified all pointers are non-null above - let wrapper = unsafe { &mut *(sdk_handle as *mut SDKWrapper) }; - - // Convert transition_owner_id from bytes to Identifier (32 bytes) - let transition_owner_id = { - let id_bytes = unsafe { std::slice::from_raw_parts(transition_owner_id, 32) }; - match Identifier::from_bytes(id_bytes) { - Ok(id) => id, - Err(e) => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - format!("Invalid transition owner ID: {}", e), - )) - } - } - }; - - let identity_public_key = unsafe { &*(identity_public_key_handle as *const IdentityPublicKey) }; - let signer = unsafe { &*(signer_handle as *const crate::signer::VTableSigner) }; - let params = unsafe { &*params }; - - // Validate contract parameters - let has_serialized_contract = match validate_contract_params( - params.token_contract_id, - params.serialized_contract, - params.serialized_contract_len, - ) { - Ok(result) => result, - Err(e) => return DashSDKResult::error(e.into()), - }; - - // Parse optional public note - let public_note = match parse_optional_note(params.public_note) { - Ok(note) => note, - Err(e) => return DashSDKResult::error(e.into()), - }; - - // Parse optional identity ID for certain update types - let identity_id = if params.identity_id.is_null() { - None - } else { - match parse_identifier_from_bytes(params.identity_id) { - Ok(id) => Some(id), - Err(e) => return DashSDKResult::error(e.into()), - } - }; - - let result: Result = wrapper.runtime.block_on(async { - // Convert FFI types to Rust types - let settings = crate::identity::convert_put_settings(put_settings); - let creation_options = convert_state_transition_creation_options(state_transition_creation_options); - let user_fee_increase = extract_user_fee_increase(put_settings); - - // Get the data contract either by fetching or deserializing - use dash_sdk::platform::Fetch; - use dash_sdk::dpp::prelude::DataContract; - - let data_contract = if !has_serialized_contract { - // Parse and fetch the contract ID - let token_contract_id_str = match unsafe { CStr::from_ptr(params.token_contract_id) }.to_str() { - Ok(s) => s, - Err(e) => return Err(FFIError::from(e)), - }; - - let token_contract_id = match Identifier::from_string(token_contract_id_str, Encoding::Base58) { - Ok(id) => id, - Err(e) => { - return Err(FFIError::InternalError(format!("Invalid token contract ID: {}", e))) - } - }; - - // Fetch the data contract - DataContract::fetch(&wrapper.sdk, token_contract_id) - .await - .map_err(FFIError::from)? - .ok_or_else(|| FFIError::InternalError("Token contract not found".to_string()))? - } else { - // Deserialize the provided contract - let contract_slice = unsafe { - std::slice::from_raw_parts( - params.serialized_contract, - params.serialized_contract_len - ) - }; - - use dash_sdk::dpp::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure; - - DataContract::versioned_deserialize( - contract_slice, - false, // skip validation since it's already validated - wrapper.sdk.version(), - ) - .map_err(|e| FFIError::InternalError(format!("Failed to deserialize contract: {}", e)))? - }; - - // Create the appropriate token configuration change item based on the update type - let update_item = match params.update_type { - DashSDKTokenConfigUpdateType::MaxSupply => { - TokenConfigurationChangeItem::MaxSupply(if params.amount == 0 { - None // 0 means unlimited - } else { - Some(params.amount as TokenAmount) - }) - } - DashSDKTokenConfigUpdateType::MintingAllowChoosingDestination => { - TokenConfigurationChangeItem::MintingAllowChoosingDestination(params.bool_value) - } - DashSDKTokenConfigUpdateType::NewTokensDestinationIdentity => { - if let Some(id) = identity_id { - TokenConfigurationChangeItem::NewTokensDestinationIdentity(Some(id)) - } else { - return Err(FFIError::InternalError( - "Identity ID required for NewTokensDestinationIdentity update".to_string() - )); - } - } - DashSDKTokenConfigUpdateType::ManualMinting => { - // Note: This would need proper implementation based on the actual SDK types - // For now, return an error indicating this needs implementation - return Err(FFIError::InternalError( - "ManualMinting config update not yet implemented".to_string() - )); - } - DashSDKTokenConfigUpdateType::ManualBurning => { - return Err(FFIError::InternalError( - "ManualBurning config update not yet implemented".to_string() - )); - } - DashSDKTokenConfigUpdateType::Freeze => { - return Err(FFIError::InternalError( - "Freeze config update not yet implemented".to_string() - )); - } - DashSDKTokenConfigUpdateType::Unfreeze => { - return Err(FFIError::InternalError( - "Unfreeze config update not yet implemented".to_string() - )); - } - DashSDKTokenConfigUpdateType::MainControlGroup => { - TokenConfigurationChangeItem::MainControlGroup(Some(params.group_position)) - } - DashSDKTokenConfigUpdateType::NoChange => { - TokenConfigurationChangeItem::TokenConfigurationNoChange - } - }; - - // Create token config update transition builder - let mut builder = TokenConfigUpdateTransitionBuilder::new( - Arc::new(data_contract), - params.token_position as TokenContractPosition, - transition_owner_id, - update_item, - ); - - // Add optional public note - if let Some(note) = public_note { - builder = builder.with_public_note(note); - } - - // Add settings - if let Some(settings) = settings { - builder = builder.with_settings(settings); - } - - // Add user fee increase - if user_fee_increase > 0 { - builder = builder.with_user_fee_increase(user_fee_increase); - } - - // Add state transition creation options - if let Some(options) = creation_options { - builder = builder.with_state_transition_creation_options(options); - } - - // Use SDK method to update config and wait - let result = wrapper - .sdk - .token_update_contract_token_configuration(builder, identity_public_key, signer) - .await - .map_err(|e| { - FFIError::InternalError(format!("Failed to update token config and wait: {}", e)) - })?; - - Ok(result) - }); - - match result { - Ok(_config_update_result) => DashSDKResult::success(std::ptr::null_mut()), - Err(e) => DashSDKResult::error(e.into()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::token::types::{DashSDKAuthorizedActionTakers, DashSDKTokenConfigUpdateType}; - use crate::types::{DashSDKPutSettings, DashSDKStateTransitionCreationOptions, SDKHandle}; - use crate::DashSDKErrorCode; - use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; - use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; - use dash_sdk::dpp::platform_value::BinaryData; - use dash_sdk::platform::IdentityPublicKey; - use std::ffi::{CStr, CString}; - use std::ptr; - - // Helper function to create a mock SDK handle - fn create_mock_sdk_handle() -> *mut SDKHandle { - let wrapper = Box::new(SDKWrapper::new_mock()); - Box::into_raw(wrapper) as *mut SDKHandle - } - - // Helper function to create a mock identity public key - fn create_mock_identity_public_key() -> Box { - Box::new(IdentityPublicKey::V0(IdentityPublicKeyV0 { - id: 1, - purpose: Purpose::AUTHENTICATION, - security_level: SecurityLevel::MEDIUM, - contract_bounds: None, - key_type: KeyType::ECDSA_SECP256K1, - read_only: false, - data: BinaryData::new(vec![0u8; 33]), - disabled_at: None, - })) - } - - // Mock callbacks for signer - // Mock async sign callback for the completion-callback signer vtable. - unsafe extern "C" fn mock_sign_callback( - _signer: *const std::os::raw::c_void, - _pubkey_bytes: *const u8, - _pubkey_len: usize, - _key_type: u8, - _data: *const u8, - _data_len: usize, - completion_ctx: *mut std::os::raw::c_void, - completion: crate::signer::SignCompletionCallback, - ) { - // Fake 64-byte signature. Completion is invoked synchronously here; - // that's legal — nothing in VTableSigner requires async completion. - let signature = [0u8; 64]; - completion( - completion_ctx, - signature.as_ptr(), - signature.len(), - 0, - std::ptr::null(), - ); - } - - unsafe extern "C" fn mock_can_sign_callback( - _signer: *const std::os::raw::c_void, - _pubkey_bytes: *const u8, - _pubkey_len: usize, - _key_type: u8, - ) -> bool { - true - } - - // Helper function to create a mock signer. - fn create_mock_signer() -> Box { - let vtable = Box::new(crate::signer::SignerVTable { - sign_async: mock_sign_callback, - can_sign_with: mock_can_sign_callback, - destroy: mock_destroy_callback, - }); - let vtable_ptr = Box::into_raw(vtable); - // SAFETY: vtable_ptr was just produced by Box::into_raw and we take - // ownership of it (owns_vtable = true). - Box::new(unsafe { - crate::signer::VTableSigner::from_callback(std::ptr::null_mut(), vtable_ptr, true) - }) - } - - // Mock destroy callback - unsafe extern "C" fn mock_destroy_callback(_signer: *mut std::os::raw::c_void) { - // No-op for mock - } - - fn create_valid_transition_owner_id() -> [u8; 32] { - [1u8; 32] - } - - fn create_valid_config_update_params() -> DashSDKTokenConfigUpdateParams { - DashSDKTokenConfigUpdateParams { - token_contract_id: CString::new("GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec") - .unwrap() - .into_raw(), - serialized_contract: ptr::null(), - serialized_contract_len: 0, - token_position: 0, - update_type: DashSDKTokenConfigUpdateType::MaxSupply, - amount: 1000000, - bool_value: false, - identity_id: ptr::null(), - group_position: 0, - action_takers: DashSDKAuthorizedActionTakers::AuthorizedContractOwner, - public_note: ptr::null(), - } - } - - unsafe fn cleanup_config_update_params(params: &DashSDKTokenConfigUpdateParams) { - if !params.token_contract_id.is_null() { - let _ = CString::from_raw(params.token_contract_id as *mut std::os::raw::c_char); - } - if !params.public_note.is_null() { - let _ = CString::from_raw(params.public_note as *mut std::os::raw::c_char); - } - } - - fn create_put_settings() -> DashSDKPutSettings { - DashSDKPutSettings { - connect_timeout_ms: 0, - timeout_ms: 0, - retries: 0, - ban_failed_address: false, - identity_nonce_stale_time_s: 0, - user_fee_increase: 0, - allow_signing_with_any_security_level: false, - allow_signing_with_any_purpose: false, - wait_timeout_ms: 0, - } - } - - #[test] - fn test_config_update_with_null_sdk_handle() { - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_config_update_params(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_update_contract_token_configuration( - ptr::null_mut(), - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - let error_msg = CStr::from_ptr(error.message).to_str().unwrap(); - assert!(error_msg.contains("null")); - } - - unsafe { - cleanup_config_update_params(¶ms); - } - } - - #[test] - fn test_config_update_with_null_transition_owner_id() { - let sdk_handle = create_mock_sdk_handle(); - let params = create_valid_config_update_params(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_update_contract_token_configuration( - sdk_handle, - ptr::null(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - unsafe { - cleanup_config_update_params(¶ms); - } - } - - #[test] - fn test_config_update_with_null_params() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_update_contract_token_configuration( - sdk_handle, - transition_owner_id.as_ptr(), - ptr::null(), - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - } - - #[test] - fn test_config_update_with_null_identity_public_key() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_config_update_params(); - let signer_handle = std::ptr::dangling::(); - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_update_contract_token_configuration( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - ptr::null(), - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - unsafe { - cleanup_config_update_params(¶ms); - } - } - - #[test] - fn test_config_update_with_null_signer() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_config_update_params(); - let identity_public_key_handle = - std::ptr::dangling::(); - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_update_contract_token_configuration( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - ptr::null(), - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - unsafe { - cleanup_config_update_params(¶ms); - } - } - - #[test] - fn test_config_update_different_update_types() { - let mut params = create_valid_config_update_params(); - - // Test MaxSupply - params.update_type = DashSDKTokenConfigUpdateType::MaxSupply; - params.amount = 1000000; - assert_eq!( - params.update_type as u32, - DashSDKTokenConfigUpdateType::MaxSupply as u32 - ); - - // Test MintingAllowChoosingDestination - params.update_type = DashSDKTokenConfigUpdateType::MintingAllowChoosingDestination; - params.bool_value = true; - assert_eq!( - params.update_type as u32, - DashSDKTokenConfigUpdateType::MintingAllowChoosingDestination as u32 - ); - - // Test MainControlGroup - params.update_type = DashSDKTokenConfigUpdateType::MainControlGroup; - params.group_position = 1; - assert_eq!( - params.update_type as u32, - DashSDKTokenConfigUpdateType::MainControlGroup as u32 - ); - - // Test NoChange - params.update_type = DashSDKTokenConfigUpdateType::NoChange; - assert_eq!( - params.update_type as u32, - DashSDKTokenConfigUpdateType::NoChange as u32 - ); - - unsafe { - cleanup_config_update_params(¶ms); - } - } - - #[test] - fn test_config_update_with_identity_id() { - let identity_id = [2u8; 32]; - let params = DashSDKTokenConfigUpdateParams { - token_contract_id: CString::new("GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec") - .unwrap() - .into_raw(), - serialized_contract: ptr::null(), - serialized_contract_len: 0, - token_position: 0, - update_type: DashSDKTokenConfigUpdateType::NewTokensDestinationIdentity, - amount: 0, - bool_value: false, - identity_id: identity_id.as_ptr(), - group_position: 0, - action_takers: DashSDKAuthorizedActionTakers::AuthorizedContractOwner, - public_note: ptr::null(), - }; - - assert!(!params.identity_id.is_null()); - unsafe { - cleanup_config_update_params(¶ms); - } - } - - #[test] - fn test_config_update_with_public_note() { - let public_note = CString::new("Config update note").unwrap(); - let contract_id = CString::new("GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec").unwrap(); - - let params = DashSDKTokenConfigUpdateParams { - token_contract_id: contract_id.as_ptr(), - serialized_contract: ptr::null(), - serialized_contract_len: 0, - token_position: 0, - update_type: DashSDKTokenConfigUpdateType::MaxSupply, - amount: 500000, - bool_value: false, - identity_id: ptr::null(), - group_position: 0, - action_takers: DashSDKAuthorizedActionTakers::AuthorizedContractOwner, - public_note: public_note.as_ptr(), - }; - - unsafe { - let note_str = CStr::from_ptr(params.public_note); - assert_eq!(note_str.to_str().unwrap(), "Config update note"); - } - } - - #[test] - fn test_config_update_with_different_action_takers() { - let mut params = create_valid_config_update_params(); - - // Test different action takers - params.action_takers = DashSDKAuthorizedActionTakers::NoOne; - assert_eq!( - params.action_takers as u32, - DashSDKAuthorizedActionTakers::NoOne as u32 - ); - - params.action_takers = DashSDKAuthorizedActionTakers::AuthorizedContractOwner; - assert_eq!( - params.action_takers as u32, - DashSDKAuthorizedActionTakers::AuthorizedContractOwner as u32 - ); - - params.action_takers = DashSDKAuthorizedActionTakers::MainGroup; - assert_eq!( - params.action_takers as u32, - DashSDKAuthorizedActionTakers::MainGroup as u32 - ); - - params.action_takers = DashSDKAuthorizedActionTakers::Identity; - assert_eq!( - params.action_takers as u32, - DashSDKAuthorizedActionTakers::Identity as u32 - ); - - params.action_takers = DashSDKAuthorizedActionTakers::Group; - assert_eq!( - params.action_takers as u32, - DashSDKAuthorizedActionTakers::Group as u32 - ); - - unsafe { - cleanup_config_update_params(¶ms); - } - } - - #[test] - fn test_config_update_with_serialized_contract() { - let contract_data = [1u8, 2, 3, 4, 5]; - let params = DashSDKTokenConfigUpdateParams { - token_contract_id: ptr::null(), - serialized_contract: contract_data.as_ptr(), - serialized_contract_len: contract_data.len(), - token_position: 0, - update_type: DashSDKTokenConfigUpdateType::MaxSupply, - amount: 100000, - bool_value: false, - identity_id: ptr::null(), - group_position: 0, - action_takers: DashSDKAuthorizedActionTakers::AuthorizedContractOwner, - public_note: ptr::null(), - }; - - assert_eq!(params.serialized_contract_len, 5); - assert!(!params.serialized_contract.is_null()); - assert!(params.token_contract_id.is_null()); - } -} diff --git a/packages/rs-sdk-ffi/src/token/emergency_action.rs b/packages/rs-sdk-ffi/src/token/emergency_action.rs deleted file mode 100644 index 1cf00009b07..00000000000 --- a/packages/rs-sdk-ffi/src/token/emergency_action.rs +++ /dev/null @@ -1,697 +0,0 @@ -//! Token emergency action operations - -use super::types::{DashSDKTokenEmergencyAction, DashSDKTokenEmergencyActionParams}; -use super::utils::{ - convert_state_transition_creation_options, extract_user_fee_increase, parse_optional_note, - validate_contract_params, -}; -use crate::sdk::SDKWrapper; -use crate::types::{ - DashSDKPutSettings, DashSDKStateTransitionCreationOptions, SDKHandle, SignerHandle, -}; -use crate::{DashSDKError, DashSDKErrorCode, DashSDKResult, FFIError}; -use dash_sdk::dpp::data_contract::TokenContractPosition; -use dash_sdk::dpp::platform_value::string_encoding::Encoding; -use dash_sdk::dpp::prelude::Identifier; -use dash_sdk::platform::tokens::builders::emergency_action::TokenEmergencyActionTransitionBuilder; -use dash_sdk::platform::tokens::transitions::EmergencyActionResult; -use dash_sdk::platform::IdentityPublicKey; -use std::ffi::CStr; -use std::sync::Arc; - -/// Perform emergency action on token and wait for confirmation -/// -/// # Safety -/// - `sdk_handle` must be a valid pointer to an initialized SDKHandle. -/// - `transition_owner_id` must point to at least 32 readable bytes. -/// - `params`, `identity_public_key_handle`, `signer_handle` must be valid, non-null pointers to initialized structures. -/// - Optional pointers (`put_settings`, `state_transition_creation_options`) may be null; when non-null they must be valid. -/// - Returned pointers embedded in DashSDKResult must be freed by the caller using SDK free routines. -#[no_mangle] -pub unsafe extern "C" fn dash_sdk_token_emergency_action( - sdk_handle: *mut SDKHandle, - transition_owner_id: *const u8, - params: *const DashSDKTokenEmergencyActionParams, - identity_public_key_handle: *const crate::types::IdentityPublicKeyHandle, - signer_handle: *const SignerHandle, - put_settings: *const DashSDKPutSettings, - state_transition_creation_options: *const DashSDKStateTransitionCreationOptions, -) -> DashSDKResult { - // Validate parameters - if sdk_handle.is_null() - || transition_owner_id.is_null() - || params.is_null() - || identity_public_key_handle.is_null() - || signer_handle.is_null() - { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "One or more required parameters is null".to_string(), - )); - } - - // Convert transition_owner_id from bytes to Identifier (32 bytes) - let transition_owner_id = { - let id_bytes = unsafe { std::slice::from_raw_parts(transition_owner_id, 32) }; - match Identifier::from_bytes(id_bytes) { - Ok(id) => id, - Err(e) => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - format!("Invalid transition owner ID: {}", e), - )) - } - } - }; - - // SAFETY: We've verified all pointers are non-null above - // However, we cannot validate if they point to valid memory without dereferencing - // For test safety, we should create proper mock handles instead of using arbitrary values - let wrapper = unsafe { &mut *(sdk_handle as *mut SDKWrapper) }; - let identity_public_key = unsafe { &*(identity_public_key_handle as *const IdentityPublicKey) }; - let signer = unsafe { &*(signer_handle as *const crate::signer::VTableSigner) }; - let params = unsafe { &*params }; - - // Validate contract parameters - let has_serialized_contract = match validate_contract_params( - params.token_contract_id, - params.serialized_contract, - params.serialized_contract_len, - ) { - Ok(result) => result, - Err(e) => return DashSDKResult::error(e.into()), - }; - - // Parse optional public note - let public_note = match parse_optional_note(params.public_note) { - Ok(note) => note, - Err(e) => return DashSDKResult::error(e.into()), - }; - - let result: Result = wrapper.runtime.block_on(async { - // Convert FFI types to Rust types - let settings = crate::identity::convert_put_settings(put_settings); - let creation_options = convert_state_transition_creation_options(state_transition_creation_options); - let user_fee_increase = extract_user_fee_increase(put_settings); - - // Get the data contract either by fetching or deserializing - use dash_sdk::platform::Fetch; - use dash_sdk::dpp::prelude::DataContract; - - let data_contract = if !has_serialized_contract { - // Parse and fetch the contract ID - let token_contract_id_str = match unsafe { CStr::from_ptr(params.token_contract_id) }.to_str() { - Ok(s) => s, - Err(e) => return Err(FFIError::from(e)), - }; - - let token_contract_id = match Identifier::from_string(token_contract_id_str, Encoding::Base58) { - Ok(id) => id, - Err(e) => { - return Err(FFIError::InternalError(format!("Invalid token contract ID: {}", e))) - } - }; - - // Fetch the data contract - DataContract::fetch(&wrapper.sdk, token_contract_id) - .await - .map_err(FFIError::from)? - .ok_or_else(|| FFIError::InternalError("Token contract not found".to_string()))? - } else { - // Deserialize the provided contract - let contract_slice = unsafe { - std::slice::from_raw_parts( - params.serialized_contract, - params.serialized_contract_len - ) - }; - - use dash_sdk::dpp::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure; - - DataContract::versioned_deserialize( - contract_slice, - false, // skip validation since it's already validated - wrapper.sdk.version(), - ) - .map_err(|e| FFIError::InternalError(format!("Failed to deserialize contract: {}", e)))? - }; - - // Create token emergency action transition builder based on action type - let mut builder = match params.action { - DashSDKTokenEmergencyAction::Pause => { - TokenEmergencyActionTransitionBuilder::pause( - Arc::new(data_contract), - params.token_position as TokenContractPosition, - transition_owner_id, - ) - } - DashSDKTokenEmergencyAction::Resume => { - TokenEmergencyActionTransitionBuilder::resume( - Arc::new(data_contract), - params.token_position as TokenContractPosition, - transition_owner_id, - ) - } - }; - - // Add optional public note - if let Some(note) = public_note { - builder = builder.with_public_note(note); - } - - // Add settings - if let Some(settings) = settings { - builder = builder.with_settings(settings); - } - - // Add user fee increase - if user_fee_increase > 0 { - builder = builder.with_user_fee_increase(user_fee_increase); - } - - // Add state transition creation options - if let Some(options) = creation_options { - builder = builder.with_state_transition_creation_options(options); - } - - // Use SDK method to perform emergency action and wait - let result = wrapper - .sdk - .token_emergency_action(builder, identity_public_key, signer) - .await - .map_err(|e| { - FFIError::InternalError(format!("Failed to perform emergency action and wait: {}", e)) - })?; - - Ok(result) - }); - - match result { - Ok(_emergency_action_result) => DashSDKResult::success(std::ptr::null_mut()), - Err(e) => DashSDKResult::error(e.into()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::DashSDKConfig; - use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; - use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; - use dash_sdk::dpp::platform_value::BinaryData; - use std::ffi::CString; - use std::ptr; - - // Helper function to create a mock SDK handle - fn create_mock_sdk_handle() -> *mut SDKHandle { - let config = DashSDKConfig { - network: crate::types::FFINetwork::Regtest, - dapi_addresses: ptr::null(), // Use mock SDK - skip_asset_lock_proof_verification: false, - request_retry_count: 3, - request_timeout_ms: 5000, - quorum_url: ptr::null(), - platform_version: 0, - }; - - let result = unsafe { crate::sdk::dash_sdk_create(&config) }; - assert!(result.error.is_null()); - result.data as *mut SDKHandle - } - - // Helper function to destroy mock SDK handle - fn destroy_mock_sdk_handle(handle: *mut SDKHandle) { - unsafe { - crate::sdk::dash_sdk_destroy(handle); - } - } - - // Helper function to create a mock identity public key - fn create_mock_identity_public_key() -> Box { - let key_v0 = IdentityPublicKeyV0 { - id: 0, - purpose: Purpose::AUTHENTICATION, - security_level: SecurityLevel::MASTER, - key_type: KeyType::ECDSA_SECP256K1, - read_only: false, - data: BinaryData::new(vec![0u8; 33]), // 33 bytes for compressed secp256k1 key - disabled_at: None, - contract_bounds: None, - }; - Box::new(IdentityPublicKey::V0(key_v0)) - } - - // Mock async sign callback for the completion-callback signer vtable. - unsafe extern "C" fn mock_sign_callback( - _signer: *const std::os::raw::c_void, - _pubkey_bytes: *const u8, - _pubkey_len: usize, - _key_type: u8, - _data: *const u8, - _data_len: usize, - completion_ctx: *mut std::os::raw::c_void, - completion: crate::signer::SignCompletionCallback, - ) { - // Fake 64-byte signature. Completion is invoked synchronously here; - // that's legal — nothing in VTableSigner requires async completion. - let signature = [0u8; 64]; - completion( - completion_ctx, - signature.as_ptr(), - signature.len(), - 0, - std::ptr::null(), - ); - } - - unsafe extern "C" fn mock_can_sign_callback( - _signer: *const std::os::raw::c_void, - _pubkey_bytes: *const u8, - _pubkey_len: usize, - _key_type: u8, - ) -> bool { - true - } - - // Helper function to create a mock signer. - fn create_mock_signer() -> Box { - let vtable = Box::new(crate::signer::SignerVTable { - sign_async: mock_sign_callback, - can_sign_with: mock_can_sign_callback, - destroy: mock_destroy_callback, - }); - let vtable_ptr = Box::into_raw(vtable); - // SAFETY: vtable_ptr was just produced by Box::into_raw and we take - // ownership of it (owns_vtable = true). - Box::new(unsafe { - crate::signer::VTableSigner::from_callback(std::ptr::null_mut(), vtable_ptr, true) - }) - } - - // Mock destroy callback - unsafe extern "C" fn mock_destroy_callback(_signer: *mut std::os::raw::c_void) { - // No-op for mock - } - - fn create_valid_transition_owner_id() -> [u8; 32] { - [1u8; 32] - } - - fn create_valid_emergency_action_params() -> DashSDKTokenEmergencyActionParams { - // Note: In real tests, the caller is responsible for freeing the CString memory - DashSDKTokenEmergencyActionParams { - token_contract_id: CString::new("GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec") - .unwrap() - .into_raw(), - serialized_contract: ptr::null(), - serialized_contract_len: 0, - token_position: 0, - action: DashSDKTokenEmergencyAction::Pause, - public_note: ptr::null(), - } - } - - // Helper to clean up params after use - unsafe fn cleanup_emergency_action_params(params: &DashSDKTokenEmergencyActionParams) { - if !params.token_contract_id.is_null() { - let _ = CString::from_raw(params.token_contract_id as *mut std::os::raw::c_char); - } - if !params.public_note.is_null() { - let _ = CString::from_raw(params.public_note as *mut std::os::raw::c_char); - } - } - - fn create_put_settings() -> DashSDKPutSettings { - DashSDKPutSettings { - connect_timeout_ms: 0, - timeout_ms: 0, - retries: 0, - ban_failed_address: false, - identity_nonce_stale_time_s: 0, - user_fee_increase: 0, - allow_signing_with_any_security_level: false, - allow_signing_with_any_purpose: false, - wait_timeout_ms: 0, - } - } - - #[test] - fn test_emergency_action_with_null_sdk_handle() { - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_emergency_action_params(); - let identity_public_key_handle = - std::ptr::dangling::(); - let signer_handle = std::ptr::dangling::(); - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_emergency_action( - ptr::null_mut(), // null SDK handle - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - // Check that the error message contains "null" - let error_msg = CStr::from_ptr(error.message).to_str().unwrap(); - assert!(error_msg.contains("null")); - } - - // Clean up params memory - unsafe { - cleanup_emergency_action_params(¶ms); - } - } - - #[test] - fn test_emergency_action_with_null_transition_owner_id() { - let sdk_handle = create_mock_sdk_handle(); - let params = create_valid_emergency_action_params(); - let identity_public_key = create_mock_identity_public_key(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer = create_mock_signer(); - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - ptr::null(), // null transition owner ID - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - // Clean up params memory - unsafe { - cleanup_emergency_action_params(¶ms); - let _ = Box::from_raw(identity_public_key_handle as *mut IdentityPublicKey); - let _ = Box::from_raw(signer_handle as *mut crate::signer::VTableSigner); - } - destroy_mock_sdk_handle(sdk_handle); - } - - #[test] - fn test_emergency_action_with_null_params() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let identity_public_key = create_mock_identity_public_key(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer = create_mock_signer(); - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - transition_owner_id.as_ptr(), - ptr::null(), // null params - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - let _ = Box::from_raw(identity_public_key_handle as *mut IdentityPublicKey); - let _ = Box::from_raw(signer_handle as *mut crate::signer::VTableSigner); - } - - // No params to clean up since we passed null - destroy_mock_sdk_handle(sdk_handle); - } - - #[test] - fn test_emergency_action_with_null_identity_public_key() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_emergency_action_params(); - let signer = create_mock_signer(); - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - ptr::null(), // null identity public key - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - // Clean up params memory - unsafe { - cleanup_emergency_action_params(¶ms); - let _ = Box::from_raw(signer_handle as *mut crate::signer::VTableSigner); - } - destroy_mock_sdk_handle(sdk_handle); - } - - #[test] - fn test_emergency_action_with_null_signer() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_emergency_action_params(); - let identity_public_key = create_mock_identity_public_key(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - ptr::null(), // null signer - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - // Clean up params memory - unsafe { - cleanup_emergency_action_params(¶ms); - let _ = Box::from_raw(identity_public_key_handle as *mut IdentityPublicKey); - } - destroy_mock_sdk_handle(sdk_handle); - } - - #[test] - fn test_emergency_action_with_resume_action() { - let sdk_handle = create_mock_sdk_handle(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - - let transition_owner_id = create_valid_transition_owner_id(); - let mut params = create_valid_emergency_action_params(); - params.action = DashSDKTokenEmergencyAction::Resume; - - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - // This will fail because we're using a mock SDK, but it validates that we can safely - // call the function without segfaults - let result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - // The result will contain an error because the mock SDK doesn't have real network connectivity - // but the important part is that we didn't get a segfault - assert!(!result.error.is_null()); - - // Clean up - unsafe { - cleanup_emergency_action_params(¶ms); - let _ = Box::from_raw(identity_public_key_handle as *mut IdentityPublicKey); - let _ = Box::from_raw(signer_handle as *mut crate::signer::VTableSigner); - } - destroy_mock_sdk_handle(sdk_handle); - } - - #[test] - fn test_emergency_action_with_public_note() { - let sdk_handle = create_mock_sdk_handle(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - - let transition_owner_id = create_valid_transition_owner_id(); - let mut params = create_valid_emergency_action_params(); - params.public_note = CString::new("Emergency action reason").unwrap().into_raw(); - - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - // This will fail because we're using a mock SDK, but it validates that we can safely - // call the function without segfaults - let result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - // The result will contain an error because the mock SDK doesn't have real network connectivity - // but the important part is that we didn't get a segfault - assert!(!result.error.is_null()); - - // Clean up - unsafe { - cleanup_emergency_action_params(¶ms); - let _ = Box::from_raw(identity_public_key_handle as *mut IdentityPublicKey); - let _ = Box::from_raw(signer_handle as *mut crate::signer::VTableSigner); - } - destroy_mock_sdk_handle(sdk_handle); - } - - #[test] - fn test_emergency_action_with_serialized_contract() { - let transition_owner_id = create_valid_transition_owner_id(); - let mut params = create_valid_emergency_action_params(); - let contract_data = [0u8; 100]; // Mock serialized contract - params.serialized_contract = contract_data.as_ptr(); - params.serialized_contract_len = contract_data.len(); - - let sdk_handle = create_mock_sdk_handle(); - let identity_public_key = create_mock_identity_public_key(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer = create_mock_signer(); - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - // Note: This test will fail when actually executed against a real SDK - // but it validates the parameter handling - let _result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - // Clean up params memory (but not the contract data since we don't own it) - unsafe { - let _ = CString::from_raw(params.token_contract_id as *mut std::os::raw::c_char); - let _ = Box::from_raw(identity_public_key_handle as *mut IdentityPublicKey); - let _ = Box::from_raw(signer_handle as *mut crate::signer::VTableSigner); - } - destroy_mock_sdk_handle(sdk_handle); - } - - #[test] - fn test_emergency_action_with_different_token_positions() { - let sdk_handle = create_mock_sdk_handle(); - let token_positions = [0u16, 1u16, 10u16, 255u16]; - - for position in token_positions { - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - - let transition_owner_id = create_valid_transition_owner_id(); - let mut params = create_valid_emergency_action_params(); - params.token_position = position; - - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = - ptr::null(); - - // This will fail because we're using a mock SDK, but it validates that we can safely - // call the function without segfaults - let result = unsafe { - dash_sdk_token_emergency_action( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - // The result will contain an error because the mock SDK doesn't have real network connectivity - // but the important part is that we didn't get a segfault - assert!(!result.error.is_null()); - - // Clean up - unsafe { - cleanup_emergency_action_params(¶ms); - let _ = Box::from_raw(identity_public_key_handle as *mut IdentityPublicKey); - let _ = Box::from_raw(signer_handle as *mut crate::signer::VTableSigner); - } - } - - destroy_mock_sdk_handle(sdk_handle); - } -} diff --git a/packages/rs-sdk-ffi/src/token/mod.rs b/packages/rs-sdk-ffi/src/token/mod.rs index aecb5263e24..7dd59bb79f7 100644 --- a/packages/rs-sdk-ffi/src/token/mod.rs +++ b/packages/rs-sdk-ffi/src/token/mod.rs @@ -14,14 +14,11 @@ mod mint; mod transfer; // Token management operations -mod config_update; mod destroy_frozen_funds; -mod emergency_action; mod freeze; mod unfreeze; // Token trading operations -mod purchase; mod set_price; mod queries; @@ -29,12 +26,9 @@ mod queries; // Re-export all public functions for backward compatibility pub use burn::*; pub use claim::*; -pub use config_update::*; pub use destroy_frozen_funds::*; -pub use emergency_action::*; pub use freeze::*; pub use mint::*; -pub use purchase::*; pub use queries::*; pub use set_price::*; pub use transfer::*; diff --git a/packages/rs-sdk-ffi/src/token/purchase.rs b/packages/rs-sdk-ffi/src/token/purchase.rs deleted file mode 100644 index 14e3847234e..00000000000 --- a/packages/rs-sdk-ffi/src/token/purchase.rs +++ /dev/null @@ -1,690 +0,0 @@ -//! Token purchase operations - -use super::types::DashSDKTokenPurchaseParams; -use super::utils::{ - convert_state_transition_creation_options, extract_user_fee_increase, validate_contract_params, -}; -use crate::sdk::SDKWrapper; -use crate::types::{ - DashSDKPutSettings, DashSDKStateTransitionCreationOptions, SDKHandle, SignerHandle, -}; -use crate::{DashSDKError, DashSDKErrorCode, DashSDKResult, FFIError}; -use dash_sdk::dpp::balances::credits::{Credits, TokenAmount}; -use dash_sdk::dpp::data_contract::TokenContractPosition; -use dash_sdk::dpp::platform_value::string_encoding::Encoding; -use dash_sdk::dpp::prelude::Identifier; -use dash_sdk::platform::tokens::builders::purchase::TokenDirectPurchaseTransitionBuilder; -use dash_sdk::platform::tokens::transitions::DirectPurchaseResult; -use dash_sdk::platform::IdentityPublicKey; -use std::ffi::CStr; -use std::sync::Arc; - -/// Purchase tokens directly and wait for confirmation -/// -/// # Safety -/// - `sdk_handle` must be a valid pointer to an initialized SDKHandle. -/// - `transition_owner_id` must point to at least 32 readable bytes. -/// - `params`, `identity_public_key_handle`, and `signer_handle` must be valid pointers to initialized structures. -/// - Optional pointers (`put_settings`, `state_transition_creation_options`) may be null; when non-null they must be valid. -/// - Caller must free any returned heap memory in the result using SDK-provided free routines. -#[no_mangle] -pub unsafe extern "C" fn dash_sdk_token_purchase( - sdk_handle: *mut SDKHandle, - transition_owner_id: *const u8, - params: *const DashSDKTokenPurchaseParams, - identity_public_key_handle: *const crate::types::IdentityPublicKeyHandle, - signer_handle: *const SignerHandle, - put_settings: *const DashSDKPutSettings, - state_transition_creation_options: *const DashSDKStateTransitionCreationOptions, -) -> DashSDKResult { - // Validate parameters - if sdk_handle.is_null() - || transition_owner_id.is_null() - || params.is_null() - || identity_public_key_handle.is_null() - || signer_handle.is_null() - { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "One or more required parameters is null".to_string(), - )); - } - - // SAFETY: We've verified all pointers are non-null above - let wrapper = unsafe { &mut *(sdk_handle as *mut SDKWrapper) }; - let identity_public_key = unsafe { &*(identity_public_key_handle as *const IdentityPublicKey) }; - let signer = unsafe { &*(signer_handle as *const crate::signer::VTableSigner) }; - let params = unsafe { &*params }; - - // Convert transition owner ID from bytes - let transition_owner_id_slice = unsafe { std::slice::from_raw_parts(transition_owner_id, 32) }; - let buyer_id = match Identifier::from_bytes(transition_owner_id_slice) { - Ok(id) => id, - Err(e) => { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - format!("Invalid transition owner ID: {}", e), - )) - } - }; - - // Validate contract parameters - let has_serialized_contract = match validate_contract_params( - params.token_contract_id, - params.serialized_contract, - params.serialized_contract_len, - ) { - Ok(result) => result, - Err(e) => return DashSDKResult::error(e.into()), - }; - - // Validate amount and price - if params.amount == 0 { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "Amount must be greater than 0".to_string(), - )); - } - - if params.total_agreed_price == 0 { - return DashSDKResult::error(DashSDKError::new( - DashSDKErrorCode::InvalidParameter, - "Total agreed price must be greater than 0".to_string(), - )); - } - - let result: Result = wrapper.runtime.block_on(async { - // Convert FFI types to Rust types - let settings = crate::identity::convert_put_settings(put_settings); - let creation_options = convert_state_transition_creation_options(state_transition_creation_options); - let user_fee_increase = extract_user_fee_increase(put_settings); - - // Get the data contract either by fetching or deserializing - use dash_sdk::platform::Fetch; - use dash_sdk::dpp::prelude::DataContract; - - let data_contract = if !has_serialized_contract { - // Parse and fetch the contract ID - let token_contract_id_str = match unsafe { CStr::from_ptr(params.token_contract_id) }.to_str() { - Ok(s) => s, - Err(e) => return Err(FFIError::from(e)), - }; - - let token_contract_id = match Identifier::from_string(token_contract_id_str, Encoding::Base58) { - Ok(id) => id, - Err(e) => { - return Err(FFIError::InternalError(format!("Invalid token contract ID: {}", e))) - } - }; - - // Fetch the data contract - DataContract::fetch(&wrapper.sdk, token_contract_id) - .await - .map_err(FFIError::from)? - .ok_or_else(|| FFIError::InternalError("Token contract not found".to_string()))? - } else { - // Deserialize the provided contract - let contract_slice = unsafe { - std::slice::from_raw_parts( - params.serialized_contract, - params.serialized_contract_len - ) - }; - - use dash_sdk::dpp::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure; - - DataContract::versioned_deserialize( - contract_slice, - false, // skip validation since it's already validated - wrapper.sdk.version(), - ) - .map_err(|e| FFIError::InternalError(format!("Failed to deserialize contract: {}", e)))? - }; - - // Create token purchase transition builder - let mut builder = TokenDirectPurchaseTransitionBuilder::new( - Arc::new(data_contract), - params.token_position as TokenContractPosition, - buyer_id, - params.amount as TokenAmount, - params.total_agreed_price as Credits, - ); - - // Add settings - if let Some(settings) = settings { - builder = builder.with_settings(settings); - } - - // Add user fee increase - if user_fee_increase > 0 { - builder = builder.with_user_fee_increase(user_fee_increase); - } - - // Add state transition creation options - if let Some(options) = creation_options { - builder = builder.with_state_transition_creation_options(options); - } - - // Use SDK method to purchase and wait - let result = wrapper - .sdk - .token_purchase(builder, identity_public_key, signer) - .await - .map_err(|e| { - FFIError::InternalError(format!("Failed to purchase token and wait: {}", e)) - })?; - - Ok(result) - }); - - match result { - Ok(_purchase_result) => DashSDKResult::success(std::ptr::null_mut()), - Err(e) => DashSDKResult::error(e.into()), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; - use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; - use dash_sdk::dpp::platform_value::BinaryData; - use dash_sdk::platform::IdentityPublicKey; - use std::ffi::CString; - use std::ptr; - - // Helper function to create a mock SDK handle - fn create_mock_sdk_handle() -> *mut SDKHandle { - let wrapper = Box::new(crate::sdk::SDKWrapper::new_mock()); - Box::into_raw(wrapper) as *mut SDKHandle - } - - // Helper function to create a mock identity public key - fn create_mock_identity_public_key() -> Box { - Box::new(IdentityPublicKey::V0(IdentityPublicKeyV0 { - id: 1, - purpose: Purpose::AUTHENTICATION, - security_level: SecurityLevel::MEDIUM, - contract_bounds: None, - key_type: KeyType::ECDSA_SECP256K1, - read_only: false, - data: BinaryData::new(vec![0u8; 33]), - disabled_at: None, - })) - } - - // Mock callbacks for signer - // Mock async sign callback for the completion-callback signer vtable. - unsafe extern "C" fn mock_sign_callback( - _signer: *const std::os::raw::c_void, - _pubkey_bytes: *const u8, - _pubkey_len: usize, - _key_type: u8, - _data: *const u8, - _data_len: usize, - completion_ctx: *mut std::os::raw::c_void, - completion: crate::signer::SignCompletionCallback, - ) { - // Fake 64-byte signature. Completion is invoked synchronously here; - // that's legal — nothing in VTableSigner requires async completion. - let signature = [0u8; 64]; - completion( - completion_ctx, - signature.as_ptr(), - signature.len(), - 0, - std::ptr::null(), - ); - } - - unsafe extern "C" fn mock_can_sign_callback( - _signer: *const std::os::raw::c_void, - _pubkey_bytes: *const u8, - _pubkey_len: usize, - _key_type: u8, - ) -> bool { - true - } - - // Helper function to create a mock signer. - fn create_mock_signer() -> Box { - let vtable = Box::new(crate::signer::SignerVTable { - sign_async: mock_sign_callback, - can_sign_with: mock_can_sign_callback, - destroy: mock_destroy_callback, - }); - let vtable_ptr = Box::into_raw(vtable); - // SAFETY: vtable_ptr was just produced by Box::into_raw and we take - // ownership of it (owns_vtable = true). - Box::new(unsafe { - crate::signer::VTableSigner::from_callback(std::ptr::null_mut(), vtable_ptr, true) - }) - } - - // Mock destroy callback - unsafe extern "C" fn mock_destroy_callback(_signer: *mut std::os::raw::c_void) { - // No-op for mock - } - - fn create_valid_transition_owner_id() -> [u8; 32] { - [1u8; 32] - } - - fn create_valid_purchase_params() -> DashSDKTokenPurchaseParams { - // Note: In real tests, the caller is responsible for freeing the CString memory - DashSDKTokenPurchaseParams { - token_contract_id: CString::new("GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec") - .unwrap() - .into_raw(), - serialized_contract: ptr::null(), - serialized_contract_len: 0, - token_position: 0, - amount: 1000, - total_agreed_price: 50000, - } - } - - // Helper to clean up params after use - unsafe fn cleanup_purchase_params(params: &DashSDKTokenPurchaseParams) { - if !params.token_contract_id.is_null() { - let _ = CString::from_raw(params.token_contract_id as *mut std::os::raw::c_char); - } - } - - fn create_put_settings() -> DashSDKPutSettings { - DashSDKPutSettings { - connect_timeout_ms: 0, - timeout_ms: 0, - retries: 0, - ban_failed_address: false, - identity_nonce_stale_time_s: 0, - user_fee_increase: 0, - allow_signing_with_any_security_level: false, - allow_signing_with_any_purpose: false, - wait_timeout_ms: 0, - } - } - - #[test] - fn test_purchase_with_null_sdk_handle() { - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_purchase_params(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_purchase( - ptr::null_mut(), // null SDK handle - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - // Check that the error message contains "null" - let error_msg = CStr::from_ptr(error.message).to_str().unwrap(); - assert!(error_msg.contains("null")); - } - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - - #[test] - fn test_purchase_with_null_transition_owner_id() { - let sdk_handle = create_mock_sdk_handle(); - let params = create_valid_purchase_params(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - ptr::null(), // null transition owner ID - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - - #[test] - fn test_purchase_with_null_params() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ptr::null(), // null params - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - // No params to clean up since we passed null - } - - #[test] - fn test_purchase_with_null_identity_public_key() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_purchase_params(); - let signer = create_mock_signer(); - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - ptr::null(), // null identity public key - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - - #[test] - fn test_purchase_with_null_signer() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let params = create_valid_purchase_params(); - let identity_public_key = create_mock_identity_public_key(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - ptr::null(), // null signer - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - } - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - - #[test] - fn test_purchase_with_zero_amount() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let mut params = create_valid_purchase_params(); - params.amount = 0; // Invalid amount - - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - let error_msg = CStr::from_ptr(error.message).to_str().unwrap(); - assert!(error_msg.contains("Amount must be greater than 0")); - } - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - - #[test] - fn test_purchase_with_zero_price() { - let sdk_handle = create_mock_sdk_handle(); - let transition_owner_id = create_valid_transition_owner_id(); - let mut params = create_valid_purchase_params(); - params.total_agreed_price = 0; // Invalid price - - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - let result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - assert!(!result.error.is_null()); - unsafe { - let error = &*result.error; - assert_eq!(error.code, DashSDKErrorCode::InvalidParameter); - let error_msg = CStr::from_ptr(error.message).to_str().unwrap(); - assert!(error_msg.contains("Total agreed price must be greater than 0")); - } - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - - #[test] - fn test_purchase_with_serialized_contract() { - let transition_owner_id = create_valid_transition_owner_id(); - let mut params = create_valid_purchase_params(); - let contract_data = [0u8; 100]; // Mock serialized contract - params.serialized_contract = contract_data.as_ptr(); - params.serialized_contract_len = contract_data.len(); - - let sdk_handle = create_mock_sdk_handle(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = ptr::null(); - - // Note: This test will fail when actually executed against a real SDK - // but it validates the parameter handling - let _result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - // Clean up params memory (but not the contract data since we don't own it) - unsafe { - let _ = CString::from_raw(params.token_contract_id as *mut std::os::raw::c_char); - } - } - - #[test] - fn test_purchase_with_different_amounts_and_prices() { - let transition_owner_id = create_valid_transition_owner_id(); - let test_cases = [ - (1u64, 100u64), - (100u64, 10000u64), - (1000u64, 50000u64), - (u64::MAX / 2, u64::MAX / 2), - ]; - - for (amount, price) in test_cases { - let mut params = create_valid_purchase_params(); - params.amount = amount; - params.total_agreed_price = price; - - let sdk_handle = create_mock_sdk_handle(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = - ptr::null(); - - // Note: This test will fail when actually executed against a real SDK - // but it validates the parameter handling - let _result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - } - - #[test] - fn test_purchase_with_different_token_positions() { - let transition_owner_id = create_valid_transition_owner_id(); - let token_positions = [0u16, 1u16, 10u16, 255u16]; - - for position in token_positions { - let mut params = create_valid_purchase_params(); - params.token_position = position; - - let sdk_handle = create_mock_sdk_handle(); - let identity_public_key = create_mock_identity_public_key(); - let signer = create_mock_signer(); - let identity_public_key_handle = - Box::into_raw(identity_public_key) as *const crate::types::IdentityPublicKeyHandle; - let signer_handle = Box::into_raw(signer) as *const SignerHandle; - let put_settings = create_put_settings(); - let state_transition_options: *const DashSDKStateTransitionCreationOptions = - ptr::null(); - - // Note: This test will fail when actually executed against a real SDK - // but it validates the parameter handling - let _result = unsafe { - dash_sdk_token_purchase( - sdk_handle, - transition_owner_id.as_ptr(), - ¶ms, - identity_public_key_handle, - signer_handle, - &put_settings, - state_transition_options, - ) - }; - - // Clean up params memory - unsafe { - cleanup_purchase_params(¶ms); - } - } - } -} diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift index 78d38fd6a08..ba1a52169b5 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift @@ -18,43 +18,6 @@ public final class ContactRequest: @unchecked Sendable { contact_request_destroy(handle).discard() } - /// Create a new contact request - public static func create( - senderId: Identifier, - recipientId: Identifier, - senderKeyIndex: UInt32, - recipientKeyIndex: UInt32, - accountReference: UInt32, - encryptedPublicKey: Data, - coreHeightCreatedAt: UInt32, - createdAt: UInt64 - ) throws -> ContactRequest { - var handle: Handle = NULL_HANDLE - - // Nest the two `withFFIBytes` closures + `withUnsafeBytes` - // so all three buffers stay live for the FFI call window. - try senderId.withFFIBytes { senderPtr in - try recipientId.withFFIBytes { recipientPtr in - try encryptedPublicKey.withUnsafeBytes { keyPtr in - try contact_request_create( - senderPtr, - recipientPtr, - senderKeyIndex, - recipientKeyIndex, - accountReference, - keyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), - UInt(encryptedPublicKey.count), - coreHeightCreatedAt, - createdAt, - &handle - ).check() - } - } - } - - return ContactRequest(handle: handle) - } - /// Get the sender identity ID public func getSenderId() throws -> Identifier { var buf = [UInt8](repeating: 0, count: 32) diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift index 182e49b380c..67913d51105 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedIdentity.swift @@ -365,27 +365,6 @@ public final class ManagedIdentity: @unchecked Sendable { return isEstablished } - /// Send a contact request by attaching a pre-built request handle. - /// Build the request via `ContactRequest.create(...)` first. - public func sendContactRequest(_ request: ContactRequest) throws { - try managed_identity_send_contact_request(handle, request.handle).check() - } - - /// Accept an incoming contact request. - /// The request handle typically comes from `getIncomingContactRequest(senderId:)`. - public func acceptContactRequest(_ request: ContactRequest) throws { - try managed_identity_accept_contact_request(handle, request.handle).check() - } - - /// Ignore a contact sender (per-sender mute, = block, reversible). - /// Local in-memory path on this handle (no persister) — the durable - /// path is `ManagedPlatformWallet.ignoreContactSender`. - public func ignoreContactSender(senderId: Identifier) throws { - try senderId.withFFIBytes { idPtr in - try managed_identity_ignore_contact_sender(handle, idPtr).check() - } - } - // MARK: - DPNS names /// Read the cached DPNS labels for this identity. Empty when