From cc48076f06009ff7dc7aaef704ce27a784ecee9e Mon Sep 17 00:00:00 2001 From: LimpidCrypto <97235361+LimpidCrypto@users.noreply.github.com> Date: Sat, 10 Aug 2024 17:41:35 +0200 Subject: [PATCH] add transaction auto filling as in #76 (#80) --- .cargo-husky/hooks/pre-commit | 5 +- .github/workflows/unit_test.yml | 6 +- Cargo.toml | 13 +- examples/std/Cargo.toml | 2 +- src/asynch/account/mod.rs | 52 ++++ src/asynch/clients/async_client.rs | 20 +- src/asynch/clients/mod.rs | 8 + src/asynch/clients/websocket/mod.rs | 2 +- src/asynch/ledger/mod.rs | 71 +++++ src/asynch/mod.rs | 6 + src/asynch/transaction/exceptions.rs | 16 + src/asynch/transaction/mod.rs | 292 ++++++++++++++++++ src/lib.rs | 9 +- src/models/amount/exceptions.rs | 10 +- src/models/amount/issued_currency_amount.rs | 18 +- src/models/amount/mod.rs | 3 +- src/models/amount/xrp_amount.rs | 101 +++++- src/models/exceptions.rs | 9 +- src/models/results/account_info.rs | 30 ++ src/models/results/exceptions.rs | 12 + src/models/results/fee.rs | 24 +- src/models/results/ledger.rs | 49 +++ src/models/results/mod.rs | 73 ++++- src/models/results/server_state.rs | 48 +++ src/models/transactions/account_delete.rs | 13 +- src/models/transactions/account_set.rs | 13 +- src/models/transactions/check_cancel.rs | 13 +- src/models/transactions/check_cash.rs | 13 +- src/models/transactions/check_create.rs | 13 +- src/models/transactions/deposit_preauth.rs | 13 +- src/models/transactions/escrow_cancel.rs | 13 +- src/models/transactions/escrow_create.rs | 13 +- src/models/transactions/escrow_finish.rs | 15 +- src/models/transactions/mod.rs | 48 ++- .../transactions/nftoken_accept_offer.rs | 38 ++- src/models/transactions/nftoken_burn.rs | 15 +- .../transactions/nftoken_cancel_offer.rs | 15 +- .../transactions/nftoken_create_offer.rs | 40 +-- src/models/transactions/nftoken_mint.rs | 15 +- src/models/transactions/offer_cancel.rs | 15 +- src/models/transactions/offer_create.rs | 15 +- src/models/transactions/payment.rs | 15 +- .../transactions/payment_channel_claim.rs | 15 +- .../transactions/payment_channel_create.rs | 15 +- .../transactions/payment_channel_fund.rs | 15 +- .../pseudo_transactions/enable_amendment.rs | 15 +- .../pseudo_transactions/set_fee.rs | 15 +- .../pseudo_transactions/unl_modify.rs | 15 +- src/models/transactions/set_regular_key.rs | 15 +- src/models/transactions/signer_list_set.rs | 15 +- src/models/transactions/ticket_create.rs | 13 +- src/models/transactions/trust_set.rs | 15 +- tests/integration_tests.rs | 23 +- 53 files changed, 1256 insertions(+), 129 deletions(-) create mode 100644 src/asynch/account/mod.rs create mode 100644 src/asynch/ledger/mod.rs create mode 100644 src/asynch/transaction/exceptions.rs create mode 100644 src/asynch/transaction/mod.rs create mode 100644 src/models/results/account_info.rs create mode 100644 src/models/results/exceptions.rs create mode 100644 src/models/results/ledger.rs create mode 100644 src/models/results/server_state.rs diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit index 3dfe18e5..eb096f90 100755 --- a/.cargo-husky/hooks/pre-commit +++ b/.cargo-husky/hooks/pre-commit @@ -4,9 +4,10 @@ set -e echo 'Running all pre-commit checks:' cargo fmt cargo test --no-default-features --features core,models,utils -cargo test --no-default-features --features core,models,utils,embedded-ws -cargo test --no-default-features --features core,models,utils,tungstenite +cargo test --no-default-features --features std,models,utils,websocket,websocket-codec +cargo test --no-default-features --features core,models,utils,websocket-std cargo test --no-default-features --features core,models,utils,json-rpc-std +cargo test --no-default-features --features websocket-std,helpers cargo test --all-features cargo clippy --fix --allow-staged cargo doc --no-deps diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index c4fb65d4..6f2bb2d0 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -37,4 +37,8 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: --no-default-features --features core,models,websocket + args: --no-default-features --features std,models,websocket,websocket-codec + - uses: actions-rs/cargo@v1 + with: + command: test + args: --no-default-features --features websocket-std,helpers diff --git a/Cargo.toml b/Cargo.toml index eb90d848..ed8569c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,12 +95,23 @@ name = "benchmarks" harness = false [features] -default = ["std", "core", "models", "utils", "websocket-std"] +default = ["std", "core", "models", "utils", "helpers", "websocket-std"] models = ["core", "transactions", "requests", "ledger", "results"] transactions = ["core", "amounts", "currencies"] requests = ["core", "amounts", "currencies"] results = ["core", "amounts", "currencies"] ledger = ["core", "amounts", "currencies"] +helpers = ["account-helpers", "ledger-helpers", "transaction-helpers"] +account-helpers = ["amounts", "currencies", "requests", "results"] +ledger-helpers = ["amounts", "currencies", "requests", "results"] +transaction-helpers = [ + "amounts", + "currencies", + "requests", + "results", + "transactions", + "ledger", +] amounts = ["core"] currencies = ["core"] json-rpc = ["url", "reqwless", "embedded-nal-async"] diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 27aa2801..052c5590 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -21,7 +21,7 @@ path = "src/bin/wallet/generate_wallet.rs" required-features = [] [[bin]] -name = "tungstenite" +name = "websocket-std" path = "src/bin/tokio/net/tungstenite.rs" required-features = ["tokio"] diff --git a/src/asynch/account/mod.rs b/src/asynch/account/mod.rs new file mode 100644 index 00000000..53917f69 --- /dev/null +++ b/src/asynch/account/mod.rs @@ -0,0 +1,52 @@ +use alloc::borrow::Cow; +use anyhow::Result; + +use crate::{ + core::addresscodec::{is_valid_xaddress, xaddress_to_classic_address}, + models::{ledger::AccountRoot, requests::AccountInfo, results}, + Err, +}; + +use super::clients::AsyncClient; + +pub async fn get_next_valid_seq_number( + address: Cow<'_, str>, + client: &impl AsyncClient, + ledger_index: Option>, +) -> Result { + let account_info = + get_account_root(address, client, ledger_index.unwrap_or("current".into())).await?; + Ok(account_info.sequence) +} + +pub async fn get_account_root<'a>( + address: Cow<'a, str>, + client: &impl AsyncClient, + ledger_index: Cow<'a, str>, +) -> Result> { + let mut classic_address = address; + if is_valid_xaddress(&classic_address) { + classic_address = match xaddress_to_classic_address(&classic_address) { + Ok(addr) => addr.0.into(), + Err(e) => return Err!(e), + }; + } + let account_info = client + .request( + AccountInfo::new( + None, + classic_address, + None, + Some(ledger_index), + None, + None, + None, + ) + .into(), + ) + .await?; + + Ok(account_info + .try_into_result::>()? + .account_data) +} diff --git a/src/asynch/clients/async_client.rs b/src/asynch/clients/async_client.rs index f9007f07..af7e8eaf 100644 --- a/src/asynch/clients/async_client.rs +++ b/src/asynch/clients/async_client.rs @@ -1,5 +1,8 @@ -use super::client::Client; -use crate::models::{requests::XRPLRequest, results::XRPLResponse}; +use super::{client::Client, CommonFields}; +use crate::models::{ + requests::{ServerState, XRPLRequest}, + results::{ServerState as ServerStateResult, XRPLResponse}, +}; use anyhow::Result; #[allow(async_fn_in_trait)] @@ -7,6 +10,19 @@ pub trait AsyncClient: Client { async fn request<'a: 'b, 'b>(&self, request: XRPLRequest<'a>) -> Result> { self.request_impl(request).await } + + async fn get_common_fields(&self) -> Result> { + let server_state = self.request(ServerState::new(None).into()).await?; + let state = server_state + .try_into_result::>()? + .state; + let common_fields = CommonFields { + network_id: state.network_id, + build_version: Some(state.build_version), + }; + + Ok(common_fields) + } } impl AsyncClient for T {} diff --git a/src/asynch/clients/mod.rs b/src/asynch/clients/mod.rs index 1d151377..0843eb30 100644 --- a/src/asynch/clients/mod.rs +++ b/src/asynch/clients/mod.rs @@ -5,6 +5,7 @@ pub mod json_rpc; #[cfg(any(feature = "websocket-std", feature = "websocket"))] pub mod websocket; +use alloc::borrow::Cow; use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; pub type MultiExecutorMutex = CriticalSectionRawMutex; pub type SingleExecutorMutex = NoopRawMutex; @@ -13,5 +14,12 @@ pub use async_client::*; pub use client::*; #[cfg(any(feature = "json-rpc-std", feature = "json-rpc"))] pub use json_rpc::*; +use serde::{Deserialize, Serialize}; #[cfg(any(feature = "websocket-std", feature = "websocket"))] pub use websocket::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CommonFields<'a> { + pub build_version: Option>, + pub network_id: Option, +} diff --git a/src/asynch/clients/websocket/mod.rs b/src/asynch/clients/websocket/mod.rs index bd068976..9fc26d28 100644 --- a/src/asynch/clients/websocket/mod.rs +++ b/src/asynch/clients/websocket/mod.rs @@ -19,7 +19,7 @@ use websocket_base::MessageHandler; #[cfg(all(feature = "websocket", not(feature = "websocket-std")))] mod _no_std; -#[cfg(feature = "websocket-codec")] +#[cfg(all(feature = "websocket-codec", feature = "std"))] pub mod codec; mod exceptions; pub use exceptions::XRPLWebsocketException; diff --git a/src/asynch/ledger/mod.rs b/src/asynch/ledger/mod.rs new file mode 100644 index 00000000..e4c5f830 --- /dev/null +++ b/src/asynch/ledger/mod.rs @@ -0,0 +1,71 @@ +use core::{cmp::min, convert::TryInto}; + +use alloc::string::ToString; +use anyhow::Result; + +use crate::models::{ + amount::XRPAmount, + requests::{Fee, Ledger}, + results::{Drops, Fee as FeeResult, Ledger as LedgerResult}, +}; + +use super::clients::AsyncClient; + +pub async fn get_latest_validated_ledger_sequence(client: &impl AsyncClient) -> Result { + let ledger_response = client + .request( + Ledger::new( + None, + None, + None, + None, + None, + None, + Some("validated".into()), + None, + None, + None, + ) + .into(), + ) + .await?; + + Ok(ledger_response + .try_into_result::>()? + .ledger_index) +} + +pub enum FeeType { + Open, + Minimum, + Dynamic, +} + +pub async fn get_fee( + client: &impl AsyncClient, + max_fee: Option, + fee_type: Option, +) -> Result> { + let fee_request = Fee::new(None); + match client.request(fee_request.into()).await { + Ok(response) => { + let drops = response.try_into_result::>()?.drops; + let fee = match_fee_type(fee_type, drops)?; + + if let Some(max_fee) = max_fee { + Ok(XRPAmount::from(min(max_fee, fee).to_string())) + } else { + Ok(XRPAmount::from(fee.to_string())) + } + } + Err(err) => Err(err), + } +} + +fn match_fee_type(fee_type: Option, drops: Drops<'_>) -> Result { + match fee_type { + None | Some(FeeType::Open) => Ok(drops.open_ledger_fee.try_into()?), + Some(FeeType::Minimum) => Ok(drops.minimum_fee.try_into()?), + Some(FeeType::Dynamic) => unimplemented!("Dynamic fee calculation not yet implemented"), + } +} diff --git a/src/asynch/mod.rs b/src/asynch/mod.rs index 92562b27..cc98777f 100644 --- a/src/asynch/mod.rs +++ b/src/asynch/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "account-helpers")] +pub mod account; #[cfg(any( feature = "websocket-std", feature = "websocket", @@ -5,3 +7,7 @@ feature = "json-rpc" ))] pub mod clients; +#[cfg(feature = "ledger-helpers")] +pub mod ledger; +#[cfg(feature = "transaction-helpers")] +pub mod transaction; diff --git a/src/asynch/transaction/exceptions.rs b/src/asynch/transaction/exceptions.rs new file mode 100644 index 00000000..92e8d8f9 --- /dev/null +++ b/src/asynch/transaction/exceptions.rs @@ -0,0 +1,16 @@ +use core::num::ParseIntError; + +use alloc::borrow::Cow; +use thiserror_no_std::Error; + +use crate::models::amount::XRPAmount; + +#[derive(Error, Debug, PartialEq)] +pub enum XRPLTransactionException<'a> { + #[error("Fee of {0:?} Drops is much higher than a typical XRP transaction fee. This may be a mistake. If intentional, please use `check_fee = false`")] + FeeUnusuallyHigh(XRPAmount<'a>), + #[error("Unable to parse rippled version: {0}")] + ParseRippledVersionError(ParseIntError), + #[error("Invalid rippled version: {0}")] + InvalidRippledVersion(Cow<'a, str>), +} diff --git a/src/asynch/transaction/mod.rs b/src/asynch/transaction/mod.rs new file mode 100644 index 00000000..315e81aa --- /dev/null +++ b/src/asynch/transaction/mod.rs @@ -0,0 +1,292 @@ +use super::account::get_next_valid_seq_number; +use super::clients::AsyncClient; +use super::clients::CommonFields; +use super::ledger::get_fee; +use super::ledger::get_latest_validated_ledger_sequence; +use crate::models::amount::XRPAmount; +use crate::models::exceptions::XRPLModelException; +use crate::models::requests::ServerState; +use crate::models::results::ServerState as ServerStateResult; +use crate::models::transactions::Transaction; +use crate::models::transactions::TransactionType; +use crate::models::Model; +use crate::Err; +use alloc::borrow::Cow; +use alloc::string::String; +use alloc::string::ToString; +use alloc::vec::Vec; +use anyhow::Result; +use core::convert::TryInto; +use core::fmt::Debug; +use exceptions::XRPLTransactionException; +use rust_decimal::Decimal; +use serde::Serialize; +use strum::IntoEnumIterator; + +pub mod exceptions; + +const OWNER_RESERVE: &str = "2000000"; // 2 XRP +const RESTRICTED_NETWORKS: u16 = 1024; +const REQUIRED_NETWORKID_VERSION: &str = "1.11.0"; +const LEDGER_OFFSET: u8 = 20; + +pub async fn autofill<'a, 'b, F, T>( + transaction: &mut T, + client: &'a impl AsyncClient, + signers_count: Option, +) -> Result<()> +where + 'a: 'b, + T: Transaction<'b, F> + Model + Clone, + F: IntoEnumIterator + Serialize + Debug + PartialEq, +{ + let txn = transaction.clone(); + let txn_common_fields = transaction.get_mut_common_fields(); + let common_fields = client.get_common_fields().await?; + if txn_common_fields.network_id.is_none() && txn_needs_network_id(common_fields.clone())? { + txn_common_fields.network_id = common_fields.network_id; + } + if txn_common_fields.sequence.is_none() { + txn_common_fields.sequence = + Some(get_next_valid_seq_number(txn_common_fields.account.clone(), client, None).await?); + } + if txn_common_fields.fee.is_none() { + txn_common_fields.fee = + Some(calculate_fee_per_transaction_type(txn, Some(client), signers_count).await?); + } + if txn_common_fields.last_ledger_sequence.is_none() { + let ledger_sequence = get_latest_validated_ledger_sequence(client).await?; + txn_common_fields.last_ledger_sequence = Some(ledger_sequence + LEDGER_OFFSET as u32); + } + + Ok(()) +} + +pub async fn calculate_fee_per_transaction_type<'a, 'b, T, F>( + transaction: T, + client: Option<&'a impl AsyncClient>, + signers_count: Option, +) -> Result> +where + 'a: 'b, + T: Transaction<'b, F>, + F: IntoEnumIterator + Serialize + Debug + PartialEq, +{ + let mut net_fee = XRPAmount::from("10"); + let base_fee; + if let Some(client) = client { + net_fee = get_fee(client, None, None).await?; + base_fee = match transaction.get_transaction_type() { + TransactionType::EscrowFinish => calculate_base_fee_for_escrow_finish( + net_fee.clone(), + transaction + .get_field_value("fulfillment")? + .map(|fulfillment| fulfillment.into()), + )?, + // TODO: same for TransactionType::AMMCreate + TransactionType::AccountDelete => get_owner_reserve_from_response(client).await?, + _ => net_fee.clone(), + }; + } else { + base_fee = match transaction.get_transaction_type() { + TransactionType::EscrowFinish => calculate_base_fee_for_escrow_finish( + net_fee.clone(), + transaction + .get_field_value("fulfillment")? + .map(|fulfillment| fulfillment.into()), + )?, + // TODO: same for TransactionType::AMMCreate + TransactionType::AccountDelete => XRPAmount::from(OWNER_RESERVE), + _ => net_fee.clone(), + }; + } + let mut base_fee_decimal: Decimal = base_fee.try_into()?; + if let Some(signers_count) = signers_count { + let net_fee_decimal: Decimal = net_fee.try_into()?; + let signer_count_fee_decimal: Decimal = (1 + signers_count).into(); + base_fee_decimal += &(net_fee_decimal * signer_count_fee_decimal); + } + + Ok(base_fee_decimal.ceil().into()) +} + +async fn get_owner_reserve_from_response(client: &impl AsyncClient) -> Result> { + let owner_reserve_response = client.request(ServerState::new(None).into()).await?; + match owner_reserve_response + .try_into_result::>()? + .state + .validated_ledger + { + Some(validated_ledger) => Ok(validated_ledger.reserve_base), + None => Err!(XRPLModelException::MissingField("validated_ledger")), + } +} + +fn calculate_base_fee_for_escrow_finish<'a>( + net_fee: XRPAmount<'a>, + fulfillment: Option>, +) -> Result> { + if let Some(fulfillment) = fulfillment { + calculate_based_on_fulfillment(fulfillment, net_fee) + } else { + Ok(net_fee) + } +} + +fn calculate_based_on_fulfillment<'a>( + fulfillment: Cow, + net_fee: XRPAmount<'_>, +) -> Result> { + let fulfillment_bytes: Vec = fulfillment.chars().map(|c| c as u8).collect(); + let net_fee_f64: f64 = net_fee.try_into()?; + let base_fee_string = + (net_fee_f64 * (33.0 + (fulfillment_bytes.len() as f64 / 16.0))).to_string(); + let base_fee: XRPAmount = base_fee_string.into(); + let base_fee_decimal: Decimal = base_fee.try_into()?; + + Ok(base_fee_decimal.ceil().into()) +} + +fn txn_needs_network_id(common_fields: CommonFields<'_>) -> Result { + let is_higher_restricted_networks = if let Some(network_id) = common_fields.network_id { + network_id > RESTRICTED_NETWORKS as u32 + } else { + false + }; + if let Some(build_version) = common_fields.build_version { + match is_not_later_rippled_version(REQUIRED_NETWORKID_VERSION.into(), build_version.into()) + { + Ok(is_not_later_rippled_version) => { + Ok(is_higher_restricted_networks && is_not_later_rippled_version) + } + Err(e) => Err!(e), + } + } else { + Ok(false) + } +} + +fn is_not_later_rippled_version<'a>( + source: String, + target: String, +) -> Result> { + if source == target { + Ok(true) + } else { + let source_decomp = source + .split('.') + .map(|i| i.to_string()) + .collect::>(); + let target_decomp = target + .split('.') + .map(|i| i.to_string()) + .collect::>(); + let (source_major, source_minor) = ( + source_decomp[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, + source_decomp[1] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, + ); + let (target_major, target_minor) = ( + target_decomp[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, + target_decomp[1] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?, + ); + if source_major != target_major { + Ok(source_major < target_major) + } else if source_minor != target_minor { + Ok(source_minor < target_minor) + } else { + let source_patch = source_decomp[2] + .split('-') + .map(|i| i.to_string()) + .collect::>(); + let target_patch = target_decomp[2] + .split('-') + .map(|i| i.to_string()) + .collect::>(); + let source_patch_version = source_patch[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?; + let target_patch_version = target_patch[0] + .parse::() + .map_err(XRPLTransactionException::ParseRippledVersionError)?; + if source_patch_version != target_patch_version { + Ok(source_patch_version < target_patch_version) + } else if source_patch.len() != target_patch.len() { + Ok(source_patch.len() < target_patch.len()) + } else if source_patch.len() == 2 { + if source_patch[1].chars().next().ok_or( + XRPLTransactionException::InvalidRippledVersion("source patch version".into()), + )? != target_patch[1].chars().next().ok_or( + XRPLTransactionException::InvalidRippledVersion("target patch version".into()), + )? { + Ok(source_patch[1] < target_patch[1]) + } else if source_patch[1].starts_with('b') { + Ok(&source_patch[1][1..] < &target_patch[1][1..]) + } else { + Ok(&source_patch[1][2..] < &target_patch[1][2..]) + } + } else { + Ok(false) + } + } + } +} + +#[cfg(all(feature = "websocket-std", feature = "std", not(feature = "websocket")))] +#[cfg(test)] +mod test_autofill { + use super::autofill; + use crate::{ + asynch::clients::{AsyncWebsocketClient, SingleExecutorMutex}, + models::{ + amount::{IssuedCurrencyAmount, XRPAmount}, + transactions::{OfferCreate, Transaction}, + }, + }; + use anyhow::Result; + + #[tokio::test] + async fn test_autofill_txn() -> Result<()> { + let mut txn = OfferCreate::new( + "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(), + None, + None, + None, + None, + None, + None, + None, + None, + None, + XRPAmount::from("1000000").into(), + IssuedCurrencyAmount::new( + "USD".into(), + "rhub8VRN55s94qWKDv6jmDy1pUykJzF3wq".into(), + "0.3".into(), + ) + .into(), + None, + None, + ); + let client = AsyncWebsocketClient::::open( + "wss://testnet.xrpl-labs.com/".parse().unwrap(), + ) + .await + .unwrap(); + autofill(&mut txn, &client, None).await?; + + assert!(txn.get_common_fields().network_id.is_none()); + assert!(txn.get_common_fields().sequence.is_some()); + assert!(txn.get_common_fields().fee.is_some()); + assert!(txn.get_common_fields().last_ledger_sequence.is_some()); + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 481b4225..90792129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,14 @@ pub mod constants; #[cfg(feature = "core")] pub mod core; pub mod macros; -#[cfg(feature = "models")] +#[cfg(any( + feature = "amounts", + feature = "currencies", + feature = "ledger", + feature = "requests", + feature = "results", + feature = "transactions" +))] pub mod models; #[cfg(feature = "utils")] pub mod utils; diff --git a/src/models/amount/exceptions.rs b/src/models/amount/exceptions.rs index 07746a28..bbef37f1 100644 --- a/src/models/amount/exceptions.rs +++ b/src/models/amount/exceptions.rs @@ -1,9 +1,17 @@ +use core::num::ParseFloatError; + use thiserror_no_std::Error; -#[derive(Debug, Clone, PartialEq, Error)] +#[derive(Debug, Error)] pub enum XRPLAmountException { #[error("Unable to convert amount `value` into `Decimal`.")] ToDecimalError(#[from] rust_decimal::Error), + #[error("Unable to convert amount float.")] + ToFloatError(#[from] ParseFloatError), + #[error("Unable to convert amount integer.")] + ToIntError(#[from] core::num::ParseIntError), + #[error("{0:?}")] + FromSerdeError(#[from] serde_json::Error), } #[cfg(feature = "std")] diff --git a/src/models/amount/issued_currency_amount.rs b/src/models/amount/issued_currency_amount.rs index 8a63c07a..6c1d183c 100644 --- a/src/models/amount/issued_currency_amount.rs +++ b/src/models/amount/issued_currency_amount.rs @@ -1,5 +1,5 @@ -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; +use crate::{models::amount::exceptions::XRPLAmountException, Err}; use alloc::borrow::Cow; use core::convert::TryInto; use core::str::FromStr; @@ -26,12 +26,24 @@ impl<'a> IssuedCurrencyAmount<'a> { } impl<'a> TryInto for IssuedCurrencyAmount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_into(self) -> Result { match Decimal::from_str(&self.value) { Ok(decimal) => Ok(decimal), - Err(decimal_error) => Err(XRPLAmountException::ToDecimalError(decimal_error)), + Err(decimal_error) => Err!(XRPLAmountException::ToDecimalError(decimal_error)), } } } + +impl<'a> PartialOrd for IssuedCurrencyAmount<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } +} + +impl<'a> Ord for IssuedCurrencyAmount<'a> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.value.cmp(&other.value) + } +} diff --git a/src/models/amount/mod.rs b/src/models/amount/mod.rs index 85430986..313e7107 100644 --- a/src/models/amount/mod.rs +++ b/src/models/amount/mod.rs @@ -7,7 +7,6 @@ pub use issued_currency_amount::*; use rust_decimal::Decimal; pub use xrp_amount::*; -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; use serde::{Deserialize, Serialize}; use strum_macros::Display; @@ -20,7 +19,7 @@ pub enum Amount<'a> { } impl<'a> TryInto for Amount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_into(self) -> Result { match self { diff --git a/src/models/amount/xrp_amount.rs b/src/models/amount/xrp_amount.rs index 4bb01b54..b4e6a6a5 100644 --- a/src/models/amount/xrp_amount.rs +++ b/src/models/amount/xrp_amount.rs @@ -1,16 +1,33 @@ -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::Model; -use alloc::borrow::Cow; -use core::convert::TryInto; +use crate::{models::amount::exceptions::XRPLAmountException, Err}; +use alloc::{ + borrow::Cow, + string::{String, ToString}, +}; +use anyhow::Result; +use core::convert::{TryFrom, TryInto}; use core::str::FromStr; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; +use serde_json::Value; -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)] +/// Represents an amount of XRP in Drops. +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Default)] pub struct XRPAmount<'a>(pub Cow<'a, str>); impl<'a> Model for XRPAmount<'a> {} +// implement Deserializing from Cow, &str, String, Decimal, f64, u32, and Value +impl<'de, 'a> Deserialize<'de> for XRPAmount<'a> { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let amount_string = Value::deserialize(deserializer)?; + XRPAmount::try_from(amount_string).map_err(serde::de::Error::custom) + } +} + impl<'a> From> for XRPAmount<'a> { fn from(value: Cow<'a, str>) -> Self { Self(value) @@ -23,13 +40,85 @@ impl<'a> From<&'a str> for XRPAmount<'a> { } } +impl<'a> From for XRPAmount<'a> { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl<'a> From for XRPAmount<'a> { + fn from(value: Decimal) -> Self { + Self(value.to_string().into()) + } +} + +impl<'a> From for XRPAmount<'a> { + fn from(value: f64) -> Self { + Self(value.to_string().into()) + } +} + +impl<'a> From for XRPAmount<'a> { + fn from(value: u32) -> Self { + Self(value.to_string().into()) + } +} + +impl<'a> TryFrom for XRPAmount<'a> { + type Error = anyhow::Error; + + fn try_from(value: Value) -> Result { + match serde_json::to_string(&value) { + Ok(amount_string) => { + let amount_string = amount_string.clone().replace("\"", ""); + Ok(Self(amount_string.into())) + } + Err(serde_error) => Err!(XRPLAmountException::FromSerdeError(serde_error)), + } + } +} + +impl<'a> TryInto for XRPAmount<'a> { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match self.0.parse::() { + Ok(f64_value) => Ok(f64_value), + Err(parse_error) => Err!(XRPLAmountException::ToFloatError(parse_error)), + } + } +} + +impl<'a> TryInto for XRPAmount<'a> { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + match self.0.parse::() { + Ok(u32_value) => Ok(u32_value), + Err(parse_error) => Err!(XRPLAmountException::ToIntError(parse_error)), + } + } +} + impl<'a> TryInto for XRPAmount<'a> { - type Error = XRPLAmountException; + type Error = anyhow::Error; fn try_into(self) -> Result { match Decimal::from_str(&self.0) { Ok(decimal) => Ok(decimal), - Err(decimal_error) => Err(XRPLAmountException::ToDecimalError(decimal_error)), + Err(decimal_error) => Err!(XRPLAmountException::ToDecimalError(decimal_error)), } } } + +impl<'a> PartialOrd for XRPAmount<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.cmp(&other.0)) + } +} + +impl<'a> Ord for XRPAmount<'a> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.0.cmp(&other.0) + } +} diff --git a/src/models/exceptions.rs b/src/models/exceptions.rs index 50f3183d..1acd1b47 100644 --- a/src/models/exceptions.rs +++ b/src/models/exceptions.rs @@ -4,15 +4,18 @@ use crate::models::requests::XRPLRequestException; use crate::models::transactions::XRPLTransactionException; use alloc::string::String; use serde::{Deserialize, Serialize}; -use strum_macros::Display; use thiserror_no_std::Error; -#[derive(Debug, PartialEq, Display)] -#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Error)] pub enum XRPLModelException<'a> { + #[error("Issued Currency can not be XRP")] InvalidICCannotBeXRP, + #[error("Transaction Model Error: {0}")] XRPLTransactionError(XRPLTransactionException<'a>), + #[error("Request Model Error: {0}")] XRPLRequestError(XRPLRequestException<'a>), + #[error("Missing Field: {0}")] + MissingField(&'a str), } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] diff --git a/src/models/results/account_info.rs b/src/models/results/account_info.rs new file mode 100644 index 00000000..0a33d78f --- /dev/null +++ b/src/models/results/account_info.rs @@ -0,0 +1,30 @@ +use core::convert::TryFrom; + +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use crate::{ + models::{ledger::AccountRoot, results::exceptions::XRPLResultException}, + Err, +}; + +use super::XRPLResult; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct AccountInfo<'a> { + pub account_data: AccountRoot<'a>, +} + +impl<'a> TryFrom> for AccountInfo<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::AccountInfo(account_info) => Ok(account_info), + res => Err!(XRPLResultException::UnexpectedResultType( + "AccountInfo".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/results/exceptions.rs b/src/models/results/exceptions.rs new file mode 100644 index 00000000..6f5dd316 --- /dev/null +++ b/src/models/results/exceptions.rs @@ -0,0 +1,12 @@ +use alloc::string::String; +use thiserror_no_std::Error; + +#[derive(Debug, Error)] +pub enum XRPLResultException { + #[error("Response error: {0}")] + ResponseError(String), + #[error("Expected result or error in the response.")] + ExpectedResultOrError, + #[error("Unexpected result type (expected {0:?}, got {1:?}).")] + UnexpectedResultType(String, String), +} diff --git a/src/models/results/fee.rs b/src/models/results/fee.rs index 7ee878a1..2e70d6e1 100644 --- a/src/models/results/fee.rs +++ b/src/models/results/fee.rs @@ -1,6 +1,14 @@ +use core::convert::TryFrom; + +use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::models::amount::XRPAmount; +use crate::{ + models::{amount::XRPAmount, results::exceptions::XRPLResultException}, + Err, +}; + +use super::XRPLResult; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Fee<'a> { @@ -14,3 +22,17 @@ pub struct Drops<'a> { pub minimum_fee: XRPAmount<'a>, pub open_ledger_fee: XRPAmount<'a>, } + +impl<'a> TryFrom> for Fee<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::Fee(fee) => Ok(fee), + res => Err!(XRPLResultException::UnexpectedResultType( + "Fee".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/results/ledger.rs b/src/models/results/ledger.rs new file mode 100644 index 00000000..0cf0dd69 --- /dev/null +++ b/src/models/results/ledger.rs @@ -0,0 +1,49 @@ +use core::convert::TryFrom; + +use alloc::{borrow::Cow, vec::Vec}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use crate::{models::results::exceptions::XRPLResultException, Err}; + +use super::XRPLResult; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Ledger<'a> { + pub ledger: LedgerInner<'a>, + pub ledger_hash: Cow<'a, str>, + pub ledger_index: u32, + pub validated: Option, + pub queue_data: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct LedgerInner<'a> { + pub account_hash: Cow<'a, str>, + pub close_flags: u32, + pub close_time: u32, + pub close_time_human: Option>, + pub close_time_resolution: u32, + pub closed: bool, + pub ledger_hash: Cow<'a, str>, + pub ledger_index: Cow<'a, str>, + pub parent_close_time: u32, + pub parent_hash: Cow<'a, str>, + pub total_coins: Cow<'a, str>, + pub transaction_hash: Cow<'a, str>, + pub transactions: Option>>, +} + +impl<'a> TryFrom> for Ledger<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::Ledger(ledger) => Ok(ledger), + res => Err!(XRPLResultException::UnexpectedResultType( + "Ledger".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/results/mod.rs b/src/models/results/mod.rs index 17770767..200b8b2b 100644 --- a/src/models/results/mod.rs +++ b/src/models/results/mod.rs @@ -1,20 +1,38 @@ +use core::convert::{TryFrom, TryInto}; + use alloc::{ borrow::Cow, + format, string::{String, ToString}, vec::Vec, }; +use anyhow::Result; +use exceptions::XRPLResultException; use serde::{Deserialize, Serialize}; - -mod fee; -pub use fee::{Fee, *}; use serde_json::{Map, Value}; +pub mod account_info; +pub mod exceptions; +pub mod fee; +pub mod ledger; +pub mod server_state; + +pub use account_info::*; +pub use fee::*; +pub use ledger::*; +pub use server_state::*; + +use crate::Err; + use super::requests::XRPLRequest; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(untagged)] pub enum XRPLResult<'a> { Fee(Fee<'a>), - Custom(Value), + AccountInfo(AccountInfo<'a>), + Ledger(Ledger<'a>), + ServerState(ServerState<'a>), + Other(Value), } impl<'a> From> for XRPLResult<'a> { @@ -23,6 +41,36 @@ impl<'a> From> for XRPLResult<'a> { } } +impl<'a> From> for XRPLResult<'a> { + fn from(account_info: AccountInfo<'a>) -> Self { + XRPLResult::AccountInfo(account_info) + } +} + +impl<'a> From> for XRPLResult<'a> { + fn from(ledger: Ledger<'a>) -> Self { + XRPLResult::Ledger(ledger) + } +} + +impl<'a> From> for XRPLResult<'a> { + fn from(server_state: ServerState<'a>) -> Self { + XRPLResult::ServerState(server_state) + } +} + +impl XRPLResult<'_> { + pub(crate) fn get_name(&self) -> String { + match self { + XRPLResult::Fee(_) => "Fee".to_string(), + XRPLResult::AccountInfo(_) => "AccountInfo".to_string(), + XRPLResult::Ledger(_) => "Ledger".to_string(), + XRPLResult::ServerState(_) => "ServerState".to_string(), + XRPLResult::Other(_) => "Other".to_string(), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ResponseStatus { @@ -129,6 +177,23 @@ impl<'a> XRPLResponse<'a> { pub fn is_success(&self) -> bool { self.status == Some(ResponseStatus::Success) } + + pub fn try_into_result, Error = anyhow::Error>>(self) -> Result { + match self.result { + Some(result) => result.try_into(), + None => { + if let Some(error) = self.error { + Err!(XRPLResultException::ResponseError(format!( + "{}: {}", + error, + self.error_message.unwrap_or_default() + ))) + } else { + Err!(XRPLResultException::ExpectedResultOrError) + } + } + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/models/results/server_state.rs b/src/models/results/server_state.rs new file mode 100644 index 00000000..f1dbf250 --- /dev/null +++ b/src/models/results/server_state.rs @@ -0,0 +1,48 @@ +use core::convert::TryFrom; + +use alloc::borrow::Cow; +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +use crate::{ + models::{amount::XRPAmount, results::exceptions::XRPLResultException}, + Err, +}; + +use super::XRPLResult; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ServerState<'a> { + pub state: State<'a>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct State<'a> { + pub build_version: Cow<'a, str>, + pub network_id: Option, + pub validated_ledger: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ValidatedLedger<'a> { + pub base_fee: XRPAmount<'a>, + pub close_time: u32, + pub hash: Cow<'a, str>, + pub reserve_base: XRPAmount<'a>, + pub reserve_inc: XRPAmount<'a>, + pub seq: u32, +} + +impl<'a> TryFrom> for ServerState<'a> { + type Error = anyhow::Error; + + fn try_from(result: XRPLResult<'a>) -> Result { + match result { + XRPLResult::ServerState(server_state) => Ok(server_state), + res => Err!(XRPLResultException::UnexpectedResultType( + "ServerState".to_string(), + res.get_name() + )), + } + } +} diff --git a/src/models/transactions/account_delete.rs b/src/models/transactions/account_delete.rs index 26a95147..53036f76 100644 --- a/src/models/transactions/account_delete.rs +++ b/src/models/transactions/account_delete.rs @@ -46,10 +46,18 @@ pub struct AccountDelete<'a> { impl<'a> Model for AccountDelete<'a> {} -impl<'a> Transaction for AccountDelete<'a> { +impl<'a> Transaction<'a, NoFlags> for AccountDelete<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> AccountDelete<'a> { @@ -79,6 +87,9 @@ impl<'a> AccountDelete<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, destination, destination_tag, diff --git a/src/models/transactions/account_set.rs b/src/models/transactions/account_set.rs index 15ae14b8..2984bc77 100644 --- a/src/models/transactions/account_set.rs +++ b/src/models/transactions/account_set.rs @@ -141,7 +141,7 @@ impl<'a: 'static> Model for AccountSet<'a> { } } -impl<'a> Transaction for AccountSet<'a> { +impl<'a> Transaction<'a, AccountSetFlag> for AccountSet<'a> { fn has_flag(&self, flag: &AccountSetFlag) -> bool { self.common_fields.has_flag(flag) } @@ -149,6 +149,14 @@ impl<'a> Transaction for AccountSet<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, AccountSetFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, AccountSetFlag> { + self.common_fields.get_mut_common_fields() + } } impl<'a> AccountSetError for AccountSet<'a> { @@ -313,6 +321,9 @@ impl<'a> AccountSet<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, clear_flag, domain, diff --git a/src/models/transactions/check_cancel.rs b/src/models/transactions/check_cancel.rs index 1f9aa015..adfc0603 100644 --- a/src/models/transactions/check_cancel.rs +++ b/src/models/transactions/check_cancel.rs @@ -41,10 +41,18 @@ pub struct CheckCancel<'a> { impl<'a> Model for CheckCancel<'a> {} -impl<'a> Transaction for CheckCancel<'a> { +impl<'a> Transaction<'a, NoFlags> for CheckCancel<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> CheckCancel<'a> { @@ -73,6 +81,9 @@ impl<'a> CheckCancel<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, check_id, } diff --git a/src/models/transactions/check_cash.rs b/src/models/transactions/check_cash.rs index 83a7485f..eb7c37fe 100644 --- a/src/models/transactions/check_cash.rs +++ b/src/models/transactions/check_cash.rs @@ -56,10 +56,18 @@ impl<'a: 'static> Model for CheckCash<'a> { } } -impl<'a> Transaction for CheckCash<'a> { +impl<'a> Transaction<'a, NoFlags> for CheckCash<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> CheckCashError for CheckCash<'a> { @@ -106,6 +114,9 @@ impl<'a> CheckCash<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, check_id, amount, diff --git a/src/models/transactions/check_create.rs b/src/models/transactions/check_create.rs index 8f2b23f6..1b3dc2ed 100644 --- a/src/models/transactions/check_create.rs +++ b/src/models/transactions/check_create.rs @@ -52,10 +52,18 @@ pub struct CheckCreate<'a> { impl<'a> Model for CheckCreate<'a> {} -impl<'a> Transaction for CheckCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for CheckCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> CheckCreate<'a> { @@ -88,6 +96,9 @@ impl<'a> CheckCreate<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, destination, send_max, diff --git a/src/models/transactions/deposit_preauth.rs b/src/models/transactions/deposit_preauth.rs index 76857280..0a2c808e 100644 --- a/src/models/transactions/deposit_preauth.rs +++ b/src/models/transactions/deposit_preauth.rs @@ -47,10 +47,18 @@ impl<'a: 'static> Model for DepositPreauth<'a> { } } -impl<'a> Transaction for DepositPreauth<'a> { +impl<'a> Transaction<'a, NoFlags> for DepositPreauth<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> DepositPreauthError for DepositPreauth<'a> { @@ -96,6 +104,9 @@ impl<'a> DepositPreauth<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, authorize, unauthorize, diff --git a/src/models/transactions/escrow_cancel.rs b/src/models/transactions/escrow_cancel.rs index 69ddbd04..149e93de 100644 --- a/src/models/transactions/escrow_cancel.rs +++ b/src/models/transactions/escrow_cancel.rs @@ -40,10 +40,18 @@ pub struct EscrowCancel<'a> { impl<'a> Model for EscrowCancel<'a> {} -impl<'a> Transaction for EscrowCancel<'a> { +impl<'a> Transaction<'a, NoFlags> for EscrowCancel<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> EscrowCancel<'a> { @@ -73,6 +81,9 @@ impl<'a> EscrowCancel<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, owner, offer_sequence, diff --git a/src/models/transactions/escrow_create.rs b/src/models/transactions/escrow_create.rs index 379873e1..3e206002 100644 --- a/src/models/transactions/escrow_create.rs +++ b/src/models/transactions/escrow_create.rs @@ -65,10 +65,18 @@ impl<'a: 'static> Model for EscrowCreate<'a> { } } -impl<'a> Transaction for EscrowCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for EscrowCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> EscrowCreateError for EscrowCreate<'a> { @@ -122,6 +130,9 @@ impl<'a> EscrowCreate<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, amount, destination, diff --git a/src/models/transactions/escrow_finish.rs b/src/models/transactions/escrow_finish.rs index 2648498a..e3355f94 100644 --- a/src/models/transactions/escrow_finish.rs +++ b/src/models/transactions/escrow_finish.rs @@ -56,9 +56,17 @@ impl<'a: 'static> Model for EscrowFinish<'a> { } } -impl<'a> Transaction for EscrowFinish<'a> { +impl<'a> Transaction<'a, NoFlags> for EscrowFinish<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -107,6 +115,9 @@ impl<'a> EscrowFinish<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, owner, offer_sequence, diff --git a/src/models/transactions/mod.rs b/src/models/transactions/mod.rs index 2ee57b1b..4c2254b1 100644 --- a/src/models/transactions/mod.rs +++ b/src/models/transactions/mod.rs @@ -25,6 +25,8 @@ pub mod signer_list_set; pub mod ticket_create; pub mod trust_set; +use core::fmt::Debug; + pub use account_delete::*; pub use account_set::*; pub use check_cancel::*; @@ -54,9 +56,10 @@ pub use ticket_create::*; pub use trust_set::*; use crate::models::amount::XRPAmount; +use crate::Err; use crate::{_serde::txn_flags, serde_with_tag}; use alloc::borrow::Cow; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec::Vec; use anyhow::Result; use derive_new::new; @@ -162,6 +165,10 @@ where pub last_ledger_sequence: Option, /// Additional arbitrary information used to identify this transaction. pub memos: Option>, + /// The network ID of the chain this transaction is intended for. + /// MUST BE OMITTED for Mainnet and some test networks. + /// REQUIRED on chains whose network ID is 1025 or higher. + pub network_id: Option, /// The sequence number of the account sending the transaction. /// A transaction is only valid if the Sequence number is exactly /// 1 greater than the previous transaction from the same account. @@ -172,6 +179,10 @@ where /// made. Conventionally, a refund should specify the initial /// payment's SourceTag as the refund payment's DestinationTag. pub signers: Option>>, + /// Hex representation of the public key that corresponds to the + /// private key used to sign this transaction. If an empty string, + /// indicates a multi-signature is present in the Signers field instead. + pub signing_pub_key: Option>, /// Arbitrary integer used to identify the reason for this /// payment, or a sender on whose behalf this transaction /// is made. Conventionally, a refund should specify the initial @@ -181,6 +192,9 @@ where /// of a Sequence number. If this is provided, Sequence must /// be 0. Cannot be used with AccountTxnID. pub ticket_sequence: Option, + /// The signature that verifies this transaction as originating + /// from the account it says it is from. + pub txn_signature: Option>, } impl<'a, T> CommonFields<'a, T> @@ -195,10 +209,13 @@ where flags: Option>, last_ledger_sequence: Option, memos: Option>, + network_id: Option, sequence: Option, signers: Option>>, + signing_pub_key: Option>, source_tag: Option, ticket_sequence: Option, + txn_signature: Option>, ) -> Self { CommonFields { account, @@ -208,15 +225,18 @@ where flags, last_ledger_sequence, memos, + network_id, sequence, signers, + signing_pub_key, source_tag, ticket_sequence, + txn_signature, } } } -impl<'a, T> Transaction for CommonFields<'a, T> +impl<'a, T> Transaction<'a, T> for CommonFields<'a, T> where T: IntoEnumIterator + Serialize + PartialEq + core::fmt::Debug, { @@ -230,6 +250,14 @@ where fn get_transaction_type(&self) -> TransactionType { self.transaction_type.clone() } + + fn get_common_fields(&self) -> &CommonFields<'_, T> { + self + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, T> { + self + } } fn optional_flag_collection_default() -> Option> @@ -273,9 +301,10 @@ pub struct Signer<'a> { } /// Standard functions for transactions. -pub trait Transaction +pub trait Transaction<'a, T> where - T: IntoEnumIterator + Serialize, + Self: Serialize, + T: IntoEnumIterator + Serialize + Debug + PartialEq, { fn has_flag(&self, flag: &T) -> bool { let _txn_flag = flag; @@ -283,6 +312,17 @@ where } fn get_transaction_type(&self) -> TransactionType; + + fn get_common_fields(&self) -> &CommonFields<'_, T>; + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, T>; + + fn get_field_value(&self, field: &str) -> Result> { + match serde_json::to_value(self) { + Ok(value) => Ok(value.get(field).map(|v| v.to_string())), + Err(e) => Err!(e), + } + } } #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)] diff --git a/src/models/transactions/nftoken_accept_offer.rs b/src/models/transactions/nftoken_accept_offer.rs index 2346f031..103bc94a 100644 --- a/src/models/transactions/nftoken_accept_offer.rs +++ b/src/models/transactions/nftoken_accept_offer.rs @@ -7,7 +7,6 @@ use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::amount::XRPAmount; use crate::models::transactions::XRPLNFTokenAcceptOfferException; use crate::models::NoFlags; @@ -70,9 +69,17 @@ impl<'a: 'static> Model for NFTokenAcceptOffer<'a> { } } -impl<'a> Transaction for NFTokenAcceptOffer<'a> { +impl<'a> Transaction<'a, NoFlags> for NFTokenAcceptOffer<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -93,20 +100,14 @@ impl<'a> NFTokenAcceptOfferError for NFTokenAcceptOffer<'a> { } fn _get_nftoken_broker_fee_error(&self) -> Result<()> { if let Some(nftoken_broker_fee) = &self.nftoken_broker_fee { - let nftoken_broker_fee_decimal: Result = - nftoken_broker_fee.clone().try_into(); - match nftoken_broker_fee_decimal { - Ok(nftoken_broker_fee_dec) => { - if nftoken_broker_fee_dec.is_zero() { - Err!(XRPLNFTokenAcceptOfferException::ValueZero { - field: "nftoken_broker_fee".into(), - resource: "".into(), - }) - } else { - Ok(()) - } - } - Err(decimal_error) => Err!(decimal_error), + let nftoken_broker_fee_decimal: Decimal = nftoken_broker_fee.clone().try_into()?; + if nftoken_broker_fee_decimal.is_zero() { + Err!(XRPLNFTokenAcceptOfferException::ValueZero { + field: "nftoken_broker_fee".into(), + resource: "".into(), + }) + } else { + Ok(()) } } else { Ok(()) @@ -142,6 +143,9 @@ impl<'a> NFTokenAcceptOffer<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, nftoken_sell_offer, nftoken_buy_offer, diff --git a/src/models/transactions/nftoken_burn.rs b/src/models/transactions/nftoken_burn.rs index c6e32e5e..6d260dbb 100644 --- a/src/models/transactions/nftoken_burn.rs +++ b/src/models/transactions/nftoken_burn.rs @@ -48,9 +48,17 @@ pub struct NFTokenBurn<'a> { impl<'a> Model for NFTokenBurn<'a> {} -impl<'a> Transaction for NFTokenBurn<'a> { +impl<'a> Transaction<'a, NoFlags> for NFTokenBurn<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -81,6 +89,9 @@ impl<'a> NFTokenBurn<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, nftoken_id, owner, diff --git a/src/models/transactions/nftoken_cancel_offer.rs b/src/models/transactions/nftoken_cancel_offer.rs index 8078e572..f2d13729 100644 --- a/src/models/transactions/nftoken_cancel_offer.rs +++ b/src/models/transactions/nftoken_cancel_offer.rs @@ -56,9 +56,17 @@ impl<'a: 'static> Model for NFTokenCancelOffer<'a> { } } -impl<'a> Transaction for NFTokenCancelOffer<'a> { +impl<'a> Transaction<'a, NoFlags> for NFTokenCancelOffer<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -102,6 +110,9 @@ impl<'a> NFTokenCancelOffer<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, nftoken_offers, } diff --git a/src/models/transactions/nftoken_create_offer.rs b/src/models/transactions/nftoken_create_offer.rs index 09ad25c2..5922a12a 100644 --- a/src/models/transactions/nftoken_create_offer.rs +++ b/src/models/transactions/nftoken_create_offer.rs @@ -13,7 +13,6 @@ use crate::models::{ transactions::{Memo, Signer, Transaction, TransactionType}, }; -use crate::models::amount::exceptions::XRPLAmountException; use crate::models::amount::{Amount, XRPAmount}; use crate::models::transactions::XRPLNFTokenCreateOfferException; use crate::Err; @@ -96,34 +95,34 @@ impl<'a: 'static> Model for NFTokenCreateOffer<'a> { } } -impl<'a> Transaction for NFTokenCreateOffer<'a> { +impl<'a> Transaction<'a, NFTokenCreateOfferFlag> for NFTokenCreateOffer<'a> { fn has_flag(&self, flag: &NFTokenCreateOfferFlag) -> bool { self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NFTokenCreateOfferFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NFTokenCreateOfferFlag> { + self.common_fields.get_mut_common_fields() } } impl<'a> NFTokenCreateOfferError for NFTokenCreateOffer<'a> { fn _get_amount_error(&self) -> Result<()> { - let amount_into_decimal: Result = - self.amount.clone().try_into(); - match amount_into_decimal { - Ok(amount) => { - if !self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) && amount.is_zero() { - Err!(XRPLNFTokenCreateOfferException::ValueZero { - field: "amount".into(), - resource: "".into(), - }) - } else { - Ok(()) - } - } - Err(decimal_error) => { - Err!(decimal_error) - } + let amount_into_decimal: Decimal = self.amount.clone().try_into()?; + if !self.has_flag(&NFTokenCreateOfferFlag::TfSellOffer) && amount_into_decimal.is_zero() { + Err!(XRPLNFTokenCreateOfferException::ValueZero { + field: "amount".into(), + resource: "".into(), + }) + } else { + Ok(()) } } @@ -203,6 +202,9 @@ impl<'a> NFTokenCreateOffer<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, nftoken_id, amount, diff --git a/src/models/transactions/nftoken_mint.rs b/src/models/transactions/nftoken_mint.rs index ab097b55..1477d014 100644 --- a/src/models/transactions/nftoken_mint.rs +++ b/src/models/transactions/nftoken_mint.rs @@ -105,13 +105,21 @@ impl<'a: 'static> Model for NFTokenMint<'a> { } } -impl<'a> Transaction for NFTokenMint<'a> { +impl<'a> Transaction<'a, NFTokenMintFlag> for NFTokenMint<'a> { fn has_flag(&self, flag: &NFTokenMintFlag) -> bool { self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NFTokenMintFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NFTokenMintFlag> { + self.common_fields.get_mut_common_fields() } } @@ -197,6 +205,9 @@ impl<'a> NFTokenMint<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, nftoken_taxon, issuer, diff --git a/src/models/transactions/offer_cancel.rs b/src/models/transactions/offer_cancel.rs index c0471cb3..7834ad68 100644 --- a/src/models/transactions/offer_cancel.rs +++ b/src/models/transactions/offer_cancel.rs @@ -43,9 +43,17 @@ pub struct OfferCancel<'a> { impl<'a> Model for OfferCancel<'a> {} -impl<'a> Transaction for OfferCancel<'a> { +impl<'a> Transaction<'a, NoFlags> for OfferCancel<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -75,6 +83,9 @@ impl<'a> OfferCancel<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, offer_sequence, } diff --git a/src/models/transactions/offer_create.rs b/src/models/transactions/offer_create.rs index 2a356ba5..2e71f511 100644 --- a/src/models/transactions/offer_create.rs +++ b/src/models/transactions/offer_create.rs @@ -81,13 +81,21 @@ pub struct OfferCreate<'a> { impl<'a> Model for OfferCreate<'a> {} -impl<'a> Transaction for OfferCreate<'a> { +impl<'a> Transaction<'a, OfferCreateFlag> for OfferCreate<'a> { fn has_flag(&self, flag: &OfferCreateFlag) -> bool { self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, OfferCreateFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, OfferCreateFlag> { + self.common_fields.get_mut_common_fields() } } @@ -121,6 +129,9 @@ impl<'a> OfferCreate<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, taker_gets, taker_pays, diff --git a/src/models/transactions/payment.rs b/src/models/transactions/payment.rs index 15e73b28..6be16a46 100644 --- a/src/models/transactions/payment.rs +++ b/src/models/transactions/payment.rs @@ -105,13 +105,21 @@ impl<'a: 'static> Model for Payment<'a> { } } -impl<'a> Transaction for Payment<'a> { +impl<'a> Transaction<'a, PaymentFlag> for Payment<'a> { fn has_flag(&self, flag: &PaymentFlag) -> bool { self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, PaymentFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, PaymentFlag> { + self.common_fields.get_mut_common_fields() } } @@ -220,6 +228,9 @@ impl<'a> Payment<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, amount, destination, diff --git a/src/models/transactions/payment_channel_claim.rs b/src/models/transactions/payment_channel_claim.rs index c961c086..68d55bc6 100644 --- a/src/models/transactions/payment_channel_claim.rs +++ b/src/models/transactions/payment_channel_claim.rs @@ -90,13 +90,21 @@ pub struct PaymentChannelClaim<'a> { impl<'a> Model for PaymentChannelClaim<'a> {} -impl<'a> Transaction for PaymentChannelClaim<'a> { +impl<'a> Transaction<'a, PaymentChannelClaimFlag> for PaymentChannelClaim<'a> { fn has_flag(&self, flag: &PaymentChannelClaimFlag) -> bool { self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, PaymentChannelClaimFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, PaymentChannelClaimFlag> { + self.common_fields.get_mut_common_fields() } } @@ -131,6 +139,9 @@ impl<'a> PaymentChannelClaim<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, channel, balance, diff --git a/src/models/transactions/payment_channel_create.rs b/src/models/transactions/payment_channel_create.rs index 6a68935f..68950884 100644 --- a/src/models/transactions/payment_channel_create.rs +++ b/src/models/transactions/payment_channel_create.rs @@ -60,9 +60,17 @@ pub struct PaymentChannelCreate<'a> { impl<'a> Model for PaymentChannelCreate<'a> {} -impl<'a> Transaction for PaymentChannelCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for PaymentChannelCreate<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -97,6 +105,9 @@ impl<'a> PaymentChannelCreate<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, amount, destination, diff --git a/src/models/transactions/payment_channel_fund.rs b/src/models/transactions/payment_channel_fund.rs index 5a33dd8c..85b2528e 100644 --- a/src/models/transactions/payment_channel_fund.rs +++ b/src/models/transactions/payment_channel_fund.rs @@ -52,9 +52,17 @@ pub struct PaymentChannelFund<'a> { impl<'a> Model for PaymentChannelFund<'a> {} -impl<'a> Transaction for PaymentChannelFund<'a> { +impl<'a> Transaction<'a, NoFlags> for PaymentChannelFund<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -86,6 +94,9 @@ impl<'a> PaymentChannelFund<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, amount, channel, diff --git a/src/models/transactions/pseudo_transactions/enable_amendment.rs b/src/models/transactions/pseudo_transactions/enable_amendment.rs index 3a949eab..9d7db323 100644 --- a/src/models/transactions/pseudo_transactions/enable_amendment.rs +++ b/src/models/transactions/pseudo_transactions/enable_amendment.rs @@ -52,13 +52,21 @@ pub struct EnableAmendment<'a> { impl<'a> Model for EnableAmendment<'a> {} -impl<'a> Transaction for EnableAmendment<'a> { +impl<'a> Transaction<'a, EnableAmendmentFlag> for EnableAmendment<'a> { fn has_flag(&self, flag: &EnableAmendmentFlag) -> bool { self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, EnableAmendmentFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, EnableAmendmentFlag> { + self.common_fields.get_mut_common_fields() } } @@ -90,6 +98,9 @@ impl<'a> EnableAmendment<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, amendment, ledger_sequence, diff --git a/src/models/transactions/pseudo_transactions/set_fee.rs b/src/models/transactions/pseudo_transactions/set_fee.rs index 2ec56e47..f861c39b 100644 --- a/src/models/transactions/pseudo_transactions/set_fee.rs +++ b/src/models/transactions/pseudo_transactions/set_fee.rs @@ -41,9 +41,17 @@ pub struct SetFee<'a> { impl<'a> Model for SetFee<'a> {} -impl<'a> Transaction for SetFee<'a> { +impl<'a> Transaction<'a, NoFlags> for SetFee<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -77,6 +85,9 @@ impl<'a> SetFee<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, base_fee, reference_fee_units, diff --git a/src/models/transactions/pseudo_transactions/unl_modify.rs b/src/models/transactions/pseudo_transactions/unl_modify.rs index 3bceedc6..259b8979 100644 --- a/src/models/transactions/pseudo_transactions/unl_modify.rs +++ b/src/models/transactions/pseudo_transactions/unl_modify.rs @@ -50,9 +50,17 @@ pub struct UNLModify<'a> { impl<'a> Model for UNLModify<'a> {} -impl<'a> Transaction for UNLModify<'a> { +impl<'a> Transaction<'a, NoFlags> for UNLModify<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -84,6 +92,9 @@ impl<'a> UNLModify<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, ledger_sequence, unlmodify_disabling, diff --git a/src/models/transactions/set_regular_key.rs b/src/models/transactions/set_regular_key.rs index 0fdae169..f0bec596 100644 --- a/src/models/transactions/set_regular_key.rs +++ b/src/models/transactions/set_regular_key.rs @@ -47,9 +47,17 @@ pub struct SetRegularKey<'a> { impl<'a> Model for SetRegularKey<'a> {} -impl<'a> Transaction for SetRegularKey<'a> { +impl<'a> Transaction<'a, NoFlags> for SetRegularKey<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -79,6 +87,9 @@ impl<'a> SetRegularKey<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, regular_key, } diff --git a/src/models/transactions/signer_list_set.rs b/src/models/transactions/signer_list_set.rs index 837963ea..3b34833a 100644 --- a/src/models/transactions/signer_list_set.rs +++ b/src/models/transactions/signer_list_set.rs @@ -74,9 +74,17 @@ impl<'a> Model for SignerListSet<'a> { } } -impl<'a> Transaction for SignerListSet<'a> { +impl<'a> Transaction<'a, NoFlags> for SignerListSet<'a> { fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() } } @@ -192,6 +200,9 @@ impl<'a> SignerListSet<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, signer_quorum, signer_entries, diff --git a/src/models/transactions/ticket_create.rs b/src/models/transactions/ticket_create.rs index 77ececd3..3ae9bddb 100644 --- a/src/models/transactions/ticket_create.rs +++ b/src/models/transactions/ticket_create.rs @@ -41,10 +41,18 @@ pub struct TicketCreate<'a> { impl<'a> Model for TicketCreate<'a> {} -impl<'a> Transaction for TicketCreate<'a> { +impl<'a> Transaction<'a, NoFlags> for TicketCreate<'a> { fn get_transaction_type(&self) -> TransactionType { self.common_fields.get_transaction_type() } + + fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> { + self.common_fields.get_mut_common_fields() + } } impl<'a> TicketCreate<'a> { @@ -73,6 +81,9 @@ impl<'a> TicketCreate<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, ticket_count, } diff --git a/src/models/transactions/trust_set.rs b/src/models/transactions/trust_set.rs index 86a2f2c9..31397e7d 100644 --- a/src/models/transactions/trust_set.rs +++ b/src/models/transactions/trust_set.rs @@ -72,13 +72,21 @@ pub struct TrustSet<'a> { impl<'a> Model for TrustSet<'a> {} -impl<'a> Transaction for TrustSet<'a> { +impl<'a> Transaction<'a, TrustSetFlag> for TrustSet<'a> { fn has_flag(&self, flag: &TrustSetFlag) -> bool { self.common_fields.has_flag(flag) } fn get_transaction_type(&self) -> TransactionType { - self.common_fields.transaction_type.clone() + self.common_fields.get_transaction_type() + } + + fn get_common_fields(&self) -> &CommonFields<'_, TrustSetFlag> { + self.common_fields.get_common_fields() + } + + fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, TrustSetFlag> { + self.common_fields.get_mut_common_fields() } } @@ -111,6 +119,9 @@ impl<'a> TrustSet<'a> { signers, source_tag, ticket_sequence, + network_id: None, + signing_pub_key: None, + txn_signature: None, }, limit_amount, quality_in, diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 5b604d0a..c243b8c0 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -5,19 +5,12 @@ mod integration; use anyhow::Result; -#[cfg(any( - feature = "websocket-std", - all(feature = "websocket", feature = "std") -))] +#[cfg(any(feature = "websocket-std", all(feature = "websocket", feature = "std")))] #[tokio::test] async fn test_asynch_clients() -> Result<()> { #[cfg(all(feature = "websocket-std", not(feature = "websocket")))] return integration::clients::test_websocket_tungstenite_test_net().await; - #[cfg(all( - feature = "websocket", - feature = "std", - not(feature = "websocket-std") - ))] + #[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] return integration::clients::test_embedded_websocket_echo().await; #[allow(unreachable_code)] Ok(()) @@ -26,17 +19,9 @@ async fn test_asynch_clients() -> Result<()> { #[cfg(any(feature = "websocket-std", feature = "websocket", feature = "std"))] #[tokio::test] async fn test_asynch_clients_request() -> Result<()> { - #[cfg(all( - feature = "websocket-std", - feature = "std", - not(feature = "websocket") - ))] + #[cfg(all(feature = "websocket-std", feature = "std", not(feature = "websocket")))] return integration::clients::test_websocket_tungstenite_request().await; - #[cfg(all( - feature = "websocket", - feature = "std", - not(feature = "websocket-std") - ))] + #[cfg(all(feature = "websocket", feature = "std", not(feature = "websocket-std")))] return integration::clients::test_embedded_websocket_request().await; #[allow(unreachable_code)] Ok(())